ccjgmwz 4 anos atrás
pai
commit
74217db3f7
100 arquivos alterados com 18408 adições e 3 exclusões
  1. 1 3
      .gitignore
  2. 57 0
      common/date.js
  3. 22 0
      common/share.js
  4. 119 0
      common/uni-ui.scss
  5. BIN
      components/.DS_Store
  6. 289 0
      components/VKeyboard/VKeyboard.vue
  7. 61 0
      components/VKeyboard/css.scss
  8. 22 0
      components/VKeyboard/index.js
  9. 61 0
      components/VKeyboard/utils.js
  10. 107 0
      components/Winglau14-lotusAddress/Winglau14-lotusAddress.css
  11. 0 0
      components/Winglau14-lotusAddress/Winglau14-lotusAddress.js
  12. 216 0
      components/Winglau14-lotusAddress/Winglau14-lotusAddress.vue
  13. 176 0
      components/WuLiu-step.vue
  14. 136 0
      components/addressBook.vue
  15. 360 0
      components/chat/footer-input.vue
  16. 90 0
      components/chat/im-drawer.vue
  17. 258 0
      components/chat/left-bubble.vue
  18. 19 0
      components/chat/more.vue
  19. 84 0
      components/chat/red-card.vue
  20. 262 0
      components/chat/right-bubble.vue
  21. 19 0
      components/chat/system-bubble.vue
  22. 22 0
      components/chat/tttt.vue
  23. 147 0
      components/chatItem.vue
  24. 184 0
      components/colorui/animation.css
  25. 65 0
      components/colorui/components/cu-custom.vue
  26. 36 0
      components/colorui/icon.css
  27. 3926 0
      components/colorui/main.css
  28. 158 0
      components/drag-button/drag-button.vue
  29. 81 0
      components/emotion/index.vue
  30. 18 0
      components/empty.vue
  31. 3 0
      components/icon/icon.css
  32. 86 0
      components/img-cache/README.md
  33. 47 0
      components/img-cache/download.js
  34. 154 0
      components/img-cache/img-cache.vue
  35. 75 0
      components/img-cache/index.js
  36. 36 0
      components/img-cache/storage.js
  37. 0 0
      components/jweixin-module/index.js
  38. 51 0
      components/jweixin-module/jwx.js
  39. 179 0
      components/lyg-popup/lyg-popup.vue
  40. 212 0
      components/mehaotian-search-revision.vue
  41. 135 0
      components/mix-list-cell.vue
  42. 264 0
      components/neil-modal/neil-modal.vue
  43. 84 0
      components/neil-modal/readme.md
  44. 143 0
      components/ossutil/base64.js
  45. 9 0
      components/ossutil/config.js
  46. 178 0
      components/ossutil/crypto.js
  47. 34 0
      components/ossutil/hmac.js
  48. 79 0
      components/ossutil/sha1.js
  49. 394 0
      components/ossutil/signature.js
  50. 86 0
      components/ossutil/uploadFile.js
  51. 228 0
      components/redenvelope/index.vue
  52. 40 0
      components/searchInput/index.vue
  53. 158 0
      components/selectInput/selectInput.vue
  54. 134 0
      components/swiperup.vue
  55. 104 0
      components/tarbar.vue
  56. 283 0
      components/u-avatar-cropper/u-avatar-cropper.vue
  57. 1265 0
      components/u-avatar-cropper/weCropper.js
  58. 27 0
      components/u-parse/components/wxParseAudio.vue
  59. 86 0
      components/u-parse/components/wxParseImg.vue
  60. 107 0
      components/u-parse/components/wxParseTemplate0.vue
  61. 99 0
      components/u-parse/components/wxParseTemplate1.vue
  62. 97 0
      components/u-parse/components/wxParseTemplate10.vue
  63. 87 0
      components/u-parse/components/wxParseTemplate11.vue
  64. 98 0
      components/u-parse/components/wxParseTemplate2.vue
  65. 98 0
      components/u-parse/components/wxParseTemplate3.vue
  66. 98 0
      components/u-parse/components/wxParseTemplate4.vue
  67. 98 0
      components/u-parse/components/wxParseTemplate5.vue
  68. 98 0
      components/u-parse/components/wxParseTemplate6.vue
  69. 98 0
      components/u-parse/components/wxParseTemplate7.vue
  70. 98 0
      components/u-parse/components/wxParseTemplate8.vue
  71. 98 0
      components/u-parse/components/wxParseTemplate9.vue
  72. 15 0
      components/u-parse/components/wxParseVideo.vue
  73. 261 0
      components/u-parse/libs/html2json.js
  74. 156 0
      components/u-parse/libs/htmlparser.js
  75. 195 0
      components/u-parse/libs/wxDiscode.js
  76. 102 0
      components/u-parse/readme.md
  77. 232 0
      components/u-parse/u-parse.css
  78. 118 0
      components/u-parse/u-parse.vue
  79. 19 0
      components/uni-cell.vue
  80. 129 0
      components/uni-list.vue
  81. 26 0
      components/uni-load-more.vue
  82. 194 0
      components/uni-load-more/uni-load-more.vue
  83. 202 0
      components/uni-number-box.vue
  84. 65 0
      components/uni-refresh.vue
  85. 85 0
      components/uni-refresh.wxs
  86. 249 0
      components/uni-table/uni-table.vue
  87. 74 0
      components/uni-td/uni-td.vue
  88. 78 0
      components/uni-th/uni-th.vue
  89. 117 0
      components/uni-tr/uni-tr.vue
  90. 226 0
      components/upload-images.vue
  91. 600 0
      components/upload.vue
  92. 21 0
      components/uview-ui/LICENSE
  93. 106 0
      components/uview-ui/README.md
  94. 190 0
      components/uview-ui/components/u-action-sheet/u-action-sheet.vue
  95. 256 0
      components/uview-ui/components/u-alert-tips/u-alert-tips.vue
  96. 290 0
      components/uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
  97. 1265 0
      components/uview-ui/components/u-avatar-cropper/weCropper.js
  98. 24 0
      components/uview-ui/components/u-avatar/u-avatar.vue
  99. 153 0
      components/uview-ui/components/u-back-top/u-back-top.vue
  100. 216 0
      components/uview-ui/components/u-badge/u-badge.vue

+ 1 - 3
.gitignore

@@ -41,9 +41,7 @@ mvnw.cmd
 
 ### target ###
 /target/
-/unpackage/dist/
-/unpackage/resources/
-/unpackage/debug/
+/unpackage/
 
 ###node_modules###
 /node_modules/

+ 57 - 0
common/date.js

@@ -0,0 +1,57 @@
+/**
+ * @desc 格式化日期字符串
+ * @param { Nubmer} - 时间戳
+ * @returns { String } 格式化后的日期字符串
+ */
+export function formatDate(timestamp) {
+  // 补全为13位
+  let arrTimestamp = (timestamp + '').split('');
+  for (let start = 0; start < 13; start++) {
+    if (!arrTimestamp[start]) {
+      arrTimestamp[start] = '0';
+    }
+  }
+  timestamp = arrTimestamp.join('') * 1;
+  let minute = 1000 * 60;
+  let hour = minute * 60;
+  let day = hour * 24;
+  let month = day * 30;
+  let now = new Date().getTime();
+  let diffValue = now - timestamp;
+  // 如果本地时间反而小于变量时间
+  if (diffValue < 0) {
+    return '不久前';
+  }
+  // 计算差异时间的量级
+  let monthC = diffValue / month;
+  let weekC = diffValue / (7 * day);
+  let dayC = diffValue / day;
+  let hourC = diffValue / hour;
+  let minC = diffValue / minute;
+  // 数值补0方法
+  let zero = function (value) {
+    if (value < 10) {
+      return '0' + value;
+    }
+    return value;
+  };
+  // 使用
+  if (monthC > 4) {
+    // 超过1年,直接显示年月日
+    return (function () {
+      let date = new Date(timestamp);
+      return date.getFullYear() + '年' + zero(date.getMonth() + 1) + '月' + zero(date.getDate()) + '日';
+    })();
+  } else if (monthC >= 1) {
+    return parseInt(monthC) + '月前';
+  } else if (weekC >= 1) {
+    return parseInt(weekC) + '周前';
+  } else if (dayC >= 1) {
+    return parseInt(dayC) + '天前';
+  } else if (hourC >= 1) {
+    return parseInt(hourC) + '小时前';
+  } else if (minC >= 1) {
+    return parseInt(minC) + '分钟前';
+  }
+  return '刚刚';
+}

+ 22 - 0
common/share.js

@@ -0,0 +1,22 @@
+export default {
+    data() {
+       return {
+            share:{
+               title: '方便快捷的粮食流通行业ERP平台,给你大智慧。',
+               imageUrl:'https://taohaoliang.oss-cn-beijing.aliyuncs.com/shareLogo.png',
+            }
+       }
+    },
+    onShareAppMessage(res) { //发送给朋友
+        return {
+            title: this.share.title,
+            imageUrl: this.share.imageUrl,
+        }
+    },
+    onShareTimeline(res) {//分享到朋友圈
+        return {
+            title: this.share.title,
+            imageUrl: this.share.imageUrl,
+        }
+    },
+}

+ 119 - 0
common/uni-ui.scss

@@ -0,0 +1,119 @@
+
+.uni-flex {
+	display: flex;
+}
+
+.uni-flex-row {
+	@extend .uni-flex;
+	flex-direction: row;
+	box-sizing: border-box;
+}
+
+.uni-flex-column {
+	@extend .uni-flex;
+	flex-direction: column;
+}
+
+.uni-color-gary {
+	color: #3b4144;
+}
+
+/* 标题 */
+.uni-title {
+	display: flex;
+	margin-bottom: $uni-spacing-col-base;
+	font-size: $uni-font-size-lg;
+	font-weight: bold;
+	color: #3b4144;
+}
+
+.uni-title-sub {
+	display: flex;
+	font-size: $uni-font-size-base;
+	font-weight: 500;
+	color: #3b4144;
+}
+
+/* 描述 额外文本 */
+.uni-note {
+	margin-top: 10px;
+	color: #999;
+	font-size: $uni-font-size-sm;
+}
+
+/* 列表内容 */
+.uni-list-box {
+	@extend .uni-flex-row;
+	flex: 1;
+	margin-top: 10px;
+}
+
+/* 略缩图 */
+.uni-thumb {
+	flex-shrink: 0;
+	margin-right: $uni-spacing-row-base;
+	width: 125px;
+	height: 75px;
+	border-radius: $uni-border-radius-lg;
+	overflow: hidden;
+	border: 1px #f5f5f5 solid;
+	image {
+		width: 100%;
+		height: 100%;
+	}
+}
+
+.uni-media-box {
+	@extend .uni-flex-row;
+	border-radius: $uni-border-radius-lg;
+	overflow: hidden;
+	.uni-thumb {
+		margin: 0;
+		margin-left: 4px;
+		flex-shrink: 1;
+		width: 33%;
+		border-radius:0;
+		&:first-child {
+			margin: 0;
+		}
+	}
+}
+
+/* 内容 */
+.uni-content {
+	@extend .uni-flex-column;
+	justify-content: space-between;
+}
+
+/* 列表footer */
+.uni-footer {
+	@extend .uni-flex-row;
+	justify-content: space-between;
+	margin-top: $uni-spacing-col-lg;
+}
+.uni-footer-text {
+	font-size: $uni-font-size-sm;
+	color: $uni-text-color-grey;
+	margin-left: 5px;
+}
+
+/* 标签 */
+
+.uni-tag {
+	flex-shrink: 0;
+	padding: 0 5px;
+	border: 1px $uni-border-color solid;
+	margin-right: $uni-spacing-row-sm;
+	border-radius: $uni-border-radius-base;
+	background: $uni-bg-color-grey;
+	color: $uni-text-color;
+	font-size: $uni-font-size-sm;
+}
+
+/* 链接 */
+.uni-link {
+	margin-left: 10px;
+	color: $uni-text-color;
+	text-decoration: underline;
+}
+

BIN
components/.DS_Store


+ 289 - 0
components/VKeyboard/VKeyboard.vue

@@ -0,0 +1,289 @@
+<template>
+	<view :class="keyboardStyle">
+		<view class="digit-keyboard" v-if="mode === 'digit' || digital">
+			<view class="digit-button-box">
+				<template v-for="(digit, index) in digits">
+					<view :key="index" class="key-button digit" @tap="typing('.')" v-if="index === 9">.</view>
+					<view :key="index" class="key-button digit" @tap="typing(digit)">{{digit}}</view>
+				</template>
+				<view class="key-button digit">
+					<i class="iconfont icon-shouqijianpan middle" v-if="digital" @tap="deactivate"></i>
+					<i class="iconfont icon-ABC middle" v-else @tap="typingLetter"></i>
+				</view>
+			</view>
+			<view class="special-button-box">
+				<view class="key-button special-button gray" @tap="backspace"><i class="iconfont icon-backspace large"></i></view>
+				<view class="key-button special-button gray" @tap="enter"><i class="iconfont icon-huiche large"></i></view>
+			</view>
+		</view>
+		<view class="full-keyboard" v-else>
+			<view class="line" v-for="(letters, index) in lines" :key="index">
+				<view class="letter key-button special-key gray" v-if="index === 3 && mode === 'letter'" @tap="toggleCase"><i :class="'iconfont ' + (lowercase ? 'icon-xiaoxie' : 'icon-daxie')"></i></view>
+				<view class="letter key-button normal" v-for="letter in letters" @tap="typing(letter)" :key="letter">{{letter}}</view>
+				<view class="letter key-button special-key gray" v-if="index === 3" @tap="backspace"><i class="iconfont icon-backspace"></i></view>
+			</view>
+			<view class="line special-line">
+				<view class="letter key-button swith-key gray">
+					<i class="iconfont icon-fuhao" @tap="typingSymbol" v-if="mode === 'letter'"></i>
+					<i class="iconfont icon-ABC" @tap="typingLetter" v-if="mode === 'symbol'"></i>
+				</view>
+				<view class="letter key-button space" @tap="typing(' ')"><text class="logo">Magician 安全键盘</text></view>
+				<view class="letter key-button swith-key gray" @tap="typingDigit"><i class="iconfont icon-shuzi"></i></view>
+				<view class="letter key-button swith-key gray" @tap="enter"><i class="iconfont icon-huiche"></i></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import './css.scss'
+	import { natural, order, disorder, symbols, digits, KEYBOARD_MODE } from './utils'
+	export default {
+		props: {
+			//是否为纯数字键盘
+			digital: {
+				type: [ Boolean, String ],
+				default: false
+			},
+			//是否无序的排序键盘
+			disorderly: {
+				type: [ Boolean, String ],
+				default: false
+			}/* ,
+			value: String */
+		},
+		//app中不生效,不知道为什么
+		/* model: {
+			prop: 'value',
+			event: 'typing'
+		}, */
+		computed: {
+			keyboardStyle() {
+				return 'v-keyboard ' + this.cls;
+			}
+		},
+		data() {
+			return {
+				cls: '',							
+				visible: false,					//是否显示
+				digits: [],						//自然数数组
+				lines: [],						//字母+数字数组
+				lowercase: true,				//是否小写输入状态
+				mode: KEYBOARD_MODE.LETTER,		//键盘模式
+				keys: []						//键入的键值
+			}
+		},
+		methods: {
+			//大小写转换
+			toggleCase() {
+				this.lowercase = !this.lowercase;
+			},
+			//输入符号
+			typingSymbol() {
+				this.mode = KEYBOARD_MODE.SYMBOL;
+				this.lines = symbols;
+			},
+			//输入字母
+			typingLetter() {
+				this.mode = KEYBOARD_MODE.LETTER;
+				this.lines = this.disorderly ? disorder() : order;
+			},
+			//键入数字
+			typingDigit() {
+				this.mode = KEYBOARD_MODE.DIGIT;
+			},
+			//键盘键入
+			typing(input) {
+				this.keys.push(input);
+				//app中v-model不生效,改用事件方式在外处理
+				//this.$emit('typing', this.keys.join(''));
+				
+				this.$emit('typing', {
+					backspace: false,
+					char: input
+				})
+			},
+			//退格键
+			backspace() {
+				if (this.keys.length) {
+					this.keys.pop()
+				}
+				//this.$emit('typing', this.keys.join(''));
+				this.$emit('typing', {
+					backspace: true
+				})
+			},
+			//键入回车
+			enter() {
+				//this.deactivate();
+				this.$emit('enter');
+			},
+			//激活键盘
+			activate() {
+				// #ifdef APP-PLUS
+				plus.key.hideSoftKeybord();
+				// #endif
+				this.visible = true;
+			},
+			//隐藏键盘
+			deactivate() {
+				this.visible = false;
+			}
+		},
+		watch: {
+			lowercase(val) {
+				let [...temp ] = this.lines;
+				temp.forEach(line => {
+					line.forEach((letter, index) => {
+						line[index] = val ? letter.toLowerCase() : letter.toUpperCase();
+					});
+				});
+				this.lines = temp;
+			},
+			visible(val) {
+				this.cls = val ? 'slideup' : 'slidedown';
+			}
+		},
+		created() {
+			this.lines = this.disorderly ? disorder() : order;
+			this.digits = this.disorderly ? digits() : natural;
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.key-button {
+		display: inline-block;
+		overflow: hidden;
+		vertical-align: middle;
+		border: 1px solid #e6e6e6;
+		color: #333;
+		background-color: #fff;
+		box-shadow: 0 2px 2px rgba(230,230,230,.7);
+		border-radius: 0.35em;
+		text-align: center;
+		white-space: nowrap;
+		user-select: none;
+		cursor: pointer;
+		
+		&:active {
+			background: #d6d6d6;
+			scale: 0.7;
+		}
+	}
+	
+	.v-keyboard {
+		transform: translateY(100%);
+		width: 100%;
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		background: #f5f5f5;
+		padding: .5em 0;
+		z-index: 100;
+		
+		/*全键盘*/
+		.full-keyboard {
+			.line {
+				text-align: center;
+				
+				&:not(:last-child) {
+					margin-bottom: 0.5em;
+				}
+				
+				.letter {
+					height: 1.7em;
+					line-height: 1.7em;
+					font-size: 1em;
+					
+					&:not(:last-child) {
+						margin-right: 0.2em;
+					}
+				}
+				
+				.normal {
+					width: 1.65em;
+				}
+				
+				.special-key {
+					width: 2.6em;
+				}
+			}
+				
+			.special-line {
+				padding: 0 0.35em;
+				display: flex!important;
+				justify-content: space-around;
+				
+				.space {
+					flex: 1;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+				}
+				
+				.swith-key {
+					width: 2.6em;
+				}
+				
+				.logo {
+					font-size: 12px;
+				}
+			}
+		}
+		
+		/*数字键盘*/
+		.digit-keyboard {
+			display: flex;
+			flex-direction: row;
+			.digit-button-box {
+				padding: 0 .35em;
+				flex: 80;
+				.digit {
+					width: 4.46em;
+					height: 2.5em;
+					line-height: 2.5em;
+					margin-bottom: .35em;
+					&:nth-child(10),
+					&:nth-child(11),
+					&:nth-child(12) {
+						margin-bottom: 0;
+					}
+					&:not(:last-child) {
+						margin-right: .35em;
+					}
+				}
+			}
+			
+			.special-button-box {
+				flex: 20;
+				padding: 0 .35em 0 0;
+				.special-button {
+					display: block;
+					line-height: 5.5em;
+					height: 5.5em;
+					width: 4em;
+					&:not(:last-child) {
+						margin-bottom: .35em;
+					}
+				}
+			}
+		}
+		
+		.gray {
+			background: #e1e1e1!important;
+			&:active {
+				background: #fff!important;
+			}
+		}
+		
+		.large {
+			font-size: 2em!important;
+		}
+		
+		.middle {
+			font-size: 1.2em!important;
+			display: block;
+		}
+	}
+</style>

+ 61 - 0
components/VKeyboard/css.scss

@@ -0,0 +1,61 @@
+@mixin transform($trans) {
+  -webkit-transform: $trans;
+  -moz-transform: $trans;
+  -ms-transform: $trans;
+  -o-transform: $trans;
+  transform: $trans;
+}
+
+@mixin transition($trans) {
+  -moz-transition: $trans;
+  -ms-transition: $trans;
+  transition: $trans;
+}
+
+/*平滑动画,向上滑入,向下滑出*/
+@keyframes slidedown {
+  from {
+    transform: translateY(0);
+  }
+  to {
+    transform: translateY(100%);
+  }
+}
+
+@-webkit-keyframes slidedown {
+  from {
+    -webkit-transform: translateY(0);
+  }
+  to {
+    -webkit-transform: translateY(100%);
+  }
+}
+
+@-webkit-keyframes slideup {
+  from {
+    -webkit-transform: translateY(100%);
+  }
+  to {
+    -webkit-transform: translateY(0);
+  }
+}
+
+@keyframes slideup {
+  from {
+    transform: translateY(100%);
+  }
+  to {
+    transform: translateY(0);
+  }
+}
+
+.slidedown {
+  animation: slidedown 0.3s linear;
+  animation-fill-mode:forwards;
+  @include transform(translateY(100%));
+}
+.slideup {
+  animation: slideup 0.3s linear;
+  animation-fill-mode:forwards;
+  @include transform(translateY(0));
+}

+ 22 - 0
components/VKeyboard/index.js

@@ -0,0 +1,22 @@
+import Vue from 'vue'
+import vKeyboard from './VKeyboard'
+
+//extend创建Vue组件类
+const vKeyboardClass = Vue.extend(vKeyboard)
+
+let instance;
+
+export default {
+  activate(options) {
+    options = options || {};
+    instance = new vKeyboardClass({propsData: options});
+    instance.vm = instance.$mount();
+    document.body.appendChild(instance.vm.$el);
+    /*document.body.appendChild(instance.vm.$el);*/
+    instance.activate();
+    return instance;
+  },
+  deactivate() {
+    instance.deactivate();
+  }
+}

+ 61 - 0
components/VKeyboard/utils.js

@@ -0,0 +1,61 @@
+//所有自然数
+export const natural = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
+//所有英文字母
+export const chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
+	'v', 'w', 'x', 'y', 'z'
+];
+
+//顺序排序全键盘
+export const order = [
+	['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
+	['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
+	['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
+	['z', 'x', 'c', 'v', 'b', 'n', 'm']
+];
+
+//随机排序全键盘
+export const disorder = () => {
+	let lines = [10, 9, 7];
+	let array = [];
+	array.push(digits());
+
+	let [...temp] = chars;
+
+	const random = (length) => {
+		let randoms = [];
+		for (let i = 0; i < length; i++) {
+			let index = Math.floor(Math.random() * temp.length);
+			randoms.push(temp[index]);
+			temp.splice(index, 1);
+		}
+		return randoms;
+	};
+
+	for (let i = 0; i < lines.length; i++) {
+		array.push(random(lines[i]));
+	}
+	return array;
+};
+
+//所有符号
+export const symbols = [
+	['~', '`', '!', '@', '#', '$', '%', '^', '&', '*'],
+	['(', ')', '-', '+', '=', '{', '}', '[', ']'],
+	['_', '|', '\\', ':', ';', '\'', '<', ',', '>'],
+	['"', '?', '.', '/', '€', '£', '¥']
+];
+
+//所有数字
+export const digits = () => {
+	let [...temp] = natural;
+	return temp.sort(function() {
+		return Math.random() > 0.5 ? -1 : 1; //用Math.random()函数生成0~1之间的随机数与0.5比较,返回-1或1
+	});
+};
+
+//键盘模式
+export const KEYBOARD_MODE = {
+	SYMBOL: 'symbol', //符号键盘
+	DIGIT: 'digit', //数字键盘
+	LETTER: 'letter' //字母键盘
+};

+ 107 - 0
components/Winglau14-lotusAddress/Winglau14-lotusAddress.css

@@ -0,0 +1,107 @@
+.lotus-address-picker {
+  font-size: 26rpx;
+  padding-top: 30rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  line-height: normal;
+  padding-right: 30rpx;
+  box-sizing: border-box;
+}
+.lotus-address-picker-box {
+  /*display: -webkit-box;
+  display: -webkit-flex;*/
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  justify-content: flex-start;
+  padding-top: 10rpx;
+  padding-bottom: 10rpx;
+}
+.lotus-address-picker-box-item {
+  height: 600upx;
+  overflow-y: auto;
+  width: 33.333%;
+  padding-left: 20rpx;
+  padding-right: 20rpx;
+  box-sizing: border-box;
+}
+.lotus-address-picker2 {
+  color: #e93b3d;
+  position: relative;
+}
+.lotus-address-picker2:after {
+  content: '';
+  position: absolute;
+  right: 0;
+  top: 65%;
+  transform: translateY(-35%) rotate(-45deg);
+  width: 20rpx;
+  height: 10rpx;
+  border-left-width: 4rpx;
+  border-bottom-width: 4rpx;
+  border-left-style: solid;
+  border-bottom-style: solid;
+  border-left-color: #e93b3d;
+  border-bottom-color: #e93b3d;
+}
+.lotus-address-mask {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 999;
+  background: rgba(0, 0, 0, 0.5);
+}
+.lotus-address-box {
+  background: #fff;
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  height: auto;
+}
+.lotus-address-action {
+  font-size: 30rpx;
+  /*display: -webkit-box;
+  display: -webkit-flex;*/
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  justify-content: space-between;
+  padding: 25rpx 30rpx;
+  position: relative;
+}
+.lotus-address-action:after {
+  content: " ";
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  height: 1px;
+  border-top: 1px solid #eee;
+  color: #eee;
+  transform-origin: 0 0;
+  transform: scaleY(0.5);
+}
+.lotus-address-action:before {
+  content: " ";
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  height: 1px;
+  border-bottom: 1px solid #eee;
+  color: #eee;
+  transform-origin: 0 100%;
+  transform: scaleY(0.5);
+}
+.lotus-address-action-cancel {
+  color: #969696;
+}
+.lotus-address-action-affirm {
+  color: #e93b3d;
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
components/Winglau14-lotusAddress/Winglau14-lotusAddress.js


+ 216 - 0
components/Winglau14-lotusAddress/Winglau14-lotusAddress.vue

@@ -0,0 +1,216 @@
+<template>
+	<!--地址picker-->
+	<view :status="checkStatus" v-if="lotusAddressData.visible" class="lotus-address-mask">
+		<view class="lotus-address-box">
+			<view class="lotus-address-action">
+				<text @tap="cancelPicker" class="lotus-address-action-cancel">取消</text>
+				<text @tap="chosedVal" class="lotus-address-action-affirm">确认</text>
+			</view>
+			<view class="lotus-address-picker-box">
+				<!--省-->
+				<scroll-view scroll-y :scroll-into-view="'pid'+pChoseIndex" class="lotus-address-picker-box-item">
+					<view @tap="clickPicker(0,pIndex,pItem);" :id="'pid'+pIndex" :class="pIndex === pChoseIndex?'lotus-address-picker lotus-address-picker2':'lotus-address-picker'"  v-for="(pItem,pIndex) in province" :key="pIndex">{{pItem}}</view>
+				</scroll-view>
+				<!--市-->
+				<scroll-view scroll-y :scroll-into-view="'cid'+cChoseIndex" class="lotus-address-picker-box-item">
+					<view @tap="clickPicker(1,cIndex,cItem);" :id="'cid'+cIndex" :class="cIndex === cChoseIndex?'lotus-address-picker lotus-address-picker2':'lotus-address-picker'" v-for="(cItem,cIndex) in city" :key="cIndex">{{cItem}}</view>
+				</scroll-view>
+				<!--区-->
+				<scroll-view scroll-y :scroll-into-view="'tid'+tChoseIndex" class="lotus-address-picker-box-item">
+					<view @tap="clickPicker(2,tIndex,tItem);" :id="'tid'+tIndex" :class="tIndex === tChoseIndex?'lotus-address-picker lotus-address-picker2':'lotus-address-picker'" v-for="(tItem,tIndex) in town" :key="tIndex">{{tItem}}</view>
+				</scroll-view>
+				<!--区END-->
+			</view>
+		</view>
+	</view>
+	<!--地址picker END-->
+</template>
+
+<script>
+	import {lotusAddressJson} from  "./Winglau14-lotusAddress.js";
+	export default {
+		props:['lotusAddressData'],
+		data() {
+			return {
+				visible: false,
+				province:[],
+				city:[],
+				town:[],
+				provinceName:'',
+				cityName:'',
+				townName:'',
+				type:0,//0新增1编辑
+				pChoseIndex:-1,
+				cChoseIndex:-1,
+				tChoseIndex:-1,
+			};
+		},
+		methods:{
+			//取消
+			cancelPicker(){
+				const provinceCode = this.getTarId(this.provinceName);
+				const cityCode = this.getTarId(this.cityName);
+				const townCode = this.getTarId(this.townName);
+				this.$emit("choseVal",{
+					provice:this.provinceName,
+					provinceCode,
+					city:this.cityName,
+					cityCode,
+					town:this.townName,
+					townCode,
+					isChose:0,
+					visible:false
+				});
+			},
+			//获取最后选择的省市区的值
+			chosedVal() {
+				this.type = 1;
+				const provinceCode = this.getTarId(this.provinceName);
+				const cityCode = this.getTarId(this.cityName);
+				const townCode = this.getTarId(this.townName);
+				this.$emit("choseVal",{
+					provice:this.provinceName,
+					provinceCode,
+					city:this.cityName,
+					cityCode,
+					town:this.townName,
+					townCode,
+					isChose:1,
+					visible:false
+				});
+			},
+			//获取省市区value
+			getTarId(name,type){
+			    let id = 0;
+				const _this = this;
+			    lotusAddressJson.map((item,index)=>{
+			        if(item.name === name){
+			            id = item.value;
+			        }
+			    });
+			    return id;
+			},
+			//获取市数据
+			getCityArr(parentId){
+			    let city = [];
+				
+			    lotusAddressJson.map((item,index)=>{
+			        if(item.parent === parentId){
+			            city.push(item.name);
+			        }
+			    });
+			    return city;
+			},
+			//获取区数据
+			getTownArr(parentId){
+			    let town = [];
+			    lotusAddressJson.map((item,index)=>{
+			        if(index>34&&item.parent === parentId){
+			            town.push(item.name);
+			        }
+			    });
+			    return town;
+			},
+			//初始化数据
+			initFn(){
+				console.log(1);
+			    lotusAddressJson.map((item,index)=>{
+			        if(index<=34){
+			            this.province.push(item.name);
+			        }
+			    });
+				//已选择省市区,高亮显示对应选择省市区
+				const p = this._props.lotusAddressData.provinceName;
+				const c = this._props.lotusAddressData.cityName;
+				const t = this._props.lotusAddressData.townName;
+				if(p){
+					this.pChoseIndex = this.getTarIndex(this.province,p);
+				}
+				if(p&&c){
+					const pid = this.getTarId(p);
+					this.city = this.getCityArr(pid);
+					this.cChoseIndex = this.getTarIndex(this.city,c);
+				}
+				if(p&&c&&t){
+					const cid= this.getTarId(c);
+					this.town = this.getTownArr(cid);
+					this.tChoseIndex = this.getTarIndex(this.town,t);
+				}
+			},
+			//获取已选省市区
+			getChosedData(){
+				const pid = this.getTarId(this.provinceName,'provice');
+				this.city = this.getCityArr(pid);
+				const cid= this.getTarId(this.cityName,'city');
+				this.town = this.getTownArr(cid);
+				//已选省市区获取对应index
+				if(this.provinceName){
+					this.pChoseIndex = this.getTarIndex(this.province,this.provinceName);
+				}
+				if(this.cityName){
+					this.cChoseIndex = this.getTarIndex(this.city,this.cityName);
+				}
+				if(this.townName){
+					this.tChoseIndex = this.getTarIndex(this.town,this.townName);
+				}
+			},
+			//选择省市区交互
+			clickPicker(type,index,name){
+				//省
+				if(type === 0){
+					this.pChoseIndex = index;
+					this.provinceName = name;
+					this.cChoseIndex = -1;
+					this.tChoseIndex = -1;
+					this.cityName = '';
+					this.townName = '';
+				}
+				//市
+				if(type ===1){
+					this.cChoseIndex = index;
+					this.cityName = name;
+					this.tChoseIndex = -1;
+					this.townName = '';
+				}
+				//区
+				if(type === 2){
+					this.tChoseIndex = index;
+					this.townName = name;
+				}
+				//获取省市区数据
+				this.getChosedData();
+			},
+			//获取已选省市区index
+			getTarIndex(arr,tarName){
+			    let cIndex = 0;
+			    arr.map((item,index)=>{
+			        if(item === tarName){
+			            cIndex = index;
+			        }
+			    });
+			    return cIndex;
+			}
+		},
+		created() {
+			this.provinceName = this._props.lotusAddressData.provinceName;
+			this.cityName = this._props.lotusAddressData.cityName;
+			this.townName = this._props.lotusAddressData.townName;
+		},
+		computed:{
+			checkStatus(){
+				let t = null;
+				const _this = this;
+				if(!_this.visible){
+					_this.initFn();
+					_this.visible = _this._props.lotusAddressData.visible;
+					t = _this.visible;
+				}
+				return t;
+			}
+		}
+	}
+</script>
+
+<style lang="less">
+@import "./Winglau14-lotusAddress.css";
+</style>

+ 176 - 0
components/WuLiu-step.vue

@@ -0,0 +1,176 @@
+<template>
+	<view class="ali">
+		<view class="ali_state">
+			<block v-for="(item,i) in datas" :key="i">
+				<view class="State_Four">
+					<view class="FourBox">
+						<text class="line" :class="[i == 0?'noline':'']"></text>
+						<!-- =====如果不够,可以再方法和过滤器中同时添加判断-或者如下的-[v-else-if]操作 -->
+						<!-- <text class="ali_top_icon" style="margin: 0 0;" v-else-if="item.station.includes('丰巢智能快递柜') && i!= datas.length-1">待</text> -->
+						<text class="ali_top_icon" :class="[item.station.includes('签收')?'isOk':'']" style="margin: 0 0;" v-if="Type(item.station) == true && i!= datas.length-1">{{item.station | Type}}</text>
+						<view class="ali_top_icons" style="margin: 0 0;" v-else>
+							<text class="line" :class="[i == 0?'noline':'']"></text>
+							<text class="ali_top_iconss"></text>
+							<text class="line" :class="[i == 0?'noline':'']"></text>
+						</view>
+						<text class="line" :class="[i == datas.length -1?'noline':'']"></text>
+					</view>
+					<view class="station">
+						<text class="a14"  v-if="i == datas.length-1">已发货</text>
+						<text class="a14"  v-else>{{item.station}}</text>
+						<text class="a12" style="padding-top: 10upx;">{{item.time}}</text>
+					</view>
+				</view>
+			</block>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default{
+		props:["datas"],
+		data(){
+			return{
+				
+			}
+		},
+		//过滤返回关键字
+		filters:{
+			Type(item){
+				let Info;
+				let Phone = /\d{11}/;
+				if(Phone.test(item) && item.includes('正在派')){
+					Info = "派";
+				}else if(item.includes('已收取快件')){
+					Info = "揽";
+				}else if(item.includes('营业')){
+					Info = "运";
+				}else if(item.includes('丰巢智能快递柜')){
+					Info = "待";
+				}else if(item.includes('签收')){
+					Info = "收";
+				}else{
+					Info = "";
+				}
+				return Info
+			}
+		},
+		methods: {
+			//判断哪个该展示关键字--还是默认
+			Type(item){
+				
+				let Info;
+				let Phone = /\d{11}/;
+				if(Phone.test(item) && item.includes('正在派')){
+					Info = true;
+				}else if(item.includes('已收取快件')){
+					Info = true;
+				}else if(item.includes('营业')){
+					Info = true;
+				}else if(item.includes('丰巢智能快递柜')){
+					Info = true;
+				}else if(item.includes('签收')){
+					Info = true;
+				}else{
+					Info = false;
+				}
+				return Info
+			}
+		},
+	}
+</script>
+
+<style>
+	.isOk{
+		background-color: #F0AD4E !important;
+	}
+	.ali_top_iconss{
+		width: 12upx;
+		height: 12upx;
+		display: block;
+		background-color: #999;
+		padding-right: 2upx;
+		border-radius: 50%;
+	}
+	.noline{
+		background-color: #fff !important;
+	}
+	.State_Four{
+		display: flex;
+		/* border: 1upx solid red; */
+	}
+	.FourBox{
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding-right: 24upx;
+	}
+	.line{
+		width: 2upx;
+		display: flex;
+		flex-direction: column;
+		flex: 1;
+		background-color: #999;
+	}
+	/* ============= */
+	.station{
+		display: flex;
+		flex-direction: column;
+		width: 90%;
+		padding: 24upx 0;
+	}
+	.borders{
+		border-bottom: 1upx solid #F2F2F2;
+		padding: 24upx 0;
+	}
+	.ali_top_icons{
+		width: 46upx;
+		height: 46upx;
+		display: block;
+		background-color: #FFF;
+		border-radius: 50%;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		align-content: center;
+	}
+	.ali_top_icon{
+		width: 46upx;
+		height: 46upx;
+		display: block;
+		background-color: #83AECF;
+		color: #fff;
+		font-size: 28upx;
+		border-radius: 50%;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		margin-right: 20upx;
+	}
+	.ali_top{
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+	}
+	/* ========公共========= */
+	.a14{
+		font-size: 28upx;
+		color: #000000;
+	}
+	.a12{
+		font-size: 24upx;
+		color: #999;
+	}
+	.ali{
+		/* width: 100%; */
+		/* border: 1upx solid #F76260; */
+		background-color: #FFFFFF;
+		margin: 24upx;
+		border-radius:18upx;
+		padding: 0 24upx;
+		display: flex;
+		flex: 1;
+		flex-direction: column;
+	}
+</style>

+ 136 - 0
components/addressBook.vue

@@ -0,0 +1,136 @@
+<template>
+	<view style='margin-bottom:120rpx;'>
+		<u-index-list :scrollTop="scrollTop" :index-list="indexList" :active-color="'#3CC51F'">
+			<view v-if="isShowMenu">
+				<u-index-anchor index="#" />
+				<!-- <view class="list-cell" hover-class="message-hover-class" @tap="linkToNewFriend">
+					<u-image width="70rpx" height="70rpx" src="/static/image/friend_1.png"></u-image>
+					<view class="list-cell-name">新的粮友</view>
+				</view>
+				<view class="list-cell " hover-class="message-hover-class"  @tap="linkToGroupItem">
+					<u-image width="70rpx" height="70rpx" src="/static/image/group_1.png"></u-image>
+					<view  class="list-cell-name">我的群聊</view>
+				</view> -->
+			</view>
+			<view v-if="isSearch">
+				<u-index-anchor index="#" />
+				<view class="list-cell" hover-class="message-hover-class">
+					<u-search v-model="keyword" placeholder="搜索" shape="square" :show-action="false" :bg-color="'#ffffff'"></u-search>
+				</view>	
+			</view>
+			<view v-for="(item, index) in list" :key="index">
+				<u-index-anchor :index="item.name" v-if="item.members&&item.members.length"/>
+				<view v-for="user in item.members" :key="user.id"  class="list-cell " @tap="linkToCard(user)" hover-class="message-hover-class">
+					<img-cache :src="user.avatar"></img-cache>
+					<view  class="list-cell-name">{{user.nickName}}</view>
+				</view>
+			</view>
+		</u-index-list>
+	</view>
+</template>
+
+<script>
+	import ImgCache from '@/components/img-cache/img-cache.vue';
+	export default {
+		name:'u-addressBook',
+		components:{ ImgCache },
+		props:{
+			list:{
+				type:Array,
+				default () {
+					return [];
+				}
+			},
+			isShowMenu:{
+				type: Boolean,
+				default () {
+					return false;
+				}
+			},
+			isSearch:{
+				type: Boolean,
+				default () {
+					return false;
+				}
+			},
+			scrollTop:{
+				type:Number,
+				default: 0
+			}
+		},
+		watch:{
+			keyword:function(val){
+				let arr = this.tList;
+				if(val!=''){
+					this.list = arr.filter(v => {
+						let flag = false
+						if(v.members.length>0){
+							v.members.forEach(m=>{
+								if(m.groupNickName.includes(val)){
+									flag = true
+								}
+							})
+						}
+						return flag
+					})
+				}else {
+					this.list = this.tList
+				}
+			}
+		},
+		data() {
+			return {
+				tList: this.list,
+				keyword:'',
+				// url1:require('@/static/image/friend_1.png'),
+				// url2:require('@/static/image/group_1.png'),
+				$url:'',
+				indexList: ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+			};
+		},
+		methods:{
+			linkToCard(user){
+				console.log("点击了用户")
+				console.log(user)
+				
+				this.$emit('linkTo',user);
+			},
+			linkToNewFriend(){
+				this.$u.route({
+					url: 'pageA/newFriend/newFriend'
+				});
+			},
+			linkToGroupItem(){
+				this.$u.route({
+					url: 'pageD/groupItem/groupItem'
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+.list-cell {
+	display: flex;
+	box-sizing: border-box;
+	width: 100%;
+	padding: 10px 24rpx;
+	overflow: hidden;
+	color: #323233;
+	font-size: 28rpx;
+	font-weight: 800;
+	line-height: 48rpx;
+	background-color: #fff;
+	border-bottom: solid 3rpx #eeeeee;
+	align-items: center;
+	image {
+		width: 76rpx;
+		height: 76rpx;
+		border-radius: 12rpx;
+		flex: 0 0 76rpx;
+	}
+	&-name{
+		padding-left: 20rpx;
+	}
+}
+</style>

+ 360 - 0
components/chat/footer-input.vue

@@ -0,0 +1,360 @@
+<template>
+	<view>
+		<!-- 底部输入栏 -->
+		<view :style='{bottom:bottomHeight+"px;"}' class="input-box" :class="popupLayerClass" @touchmove.stop.prevent="discard">
+			<!-- H5下不能录音,输入栏布局改动一下 -->
+			<!-- #ifndef H5 -->
+			<view class="voice">
+				<view class="cuIcon-notification" :class="isVoice?'cuIcon-community':'cuIcon-notification'" @tap="switchVoice"></view>
+			</view>
+			<!-- #endif -->
+			<!-- #ifdef H5 -->
+			<view class="more" @tap="showMore">
+				<view class="cuIcon-roundadd"></view>
+			</view>
+			<!-- #endif -->
+			<!-- 录音 -->
+			<view class="textbox" >
+				<view class="voice-mode" :class="[isVoice?'':'hidden',recording?'recording':'']" @touchstart="voiceBegin"
+				 @touchmove.stop.prevent="voiceIng" @touchend="voiceEnd" @touchcancel="voiceCancel">{{voiceTis}}</view>
+				<view class="text-mode" :class="isVoice?'hidden':''">
+					<view class="box">
+						 <textarea id='textarea' :confirm-type="'send'" :confirm-hold="false" :show-confirm-bar="false" @confirm="sendMsg(0, textMsg)"
+						  auto-height="true" :adjust-position='false' :disabled="disabledSay===1" :hold-keyboard='true'
+						   v-model="textMsg" @focus="textareaFocus" @blur='textareaBlur' />
+					</view>
+					<view class="em" @tap.stop="chooseEmoji(e)">
+						<view class="cuIcon-emoji"></view>
+					</view>
+				</view>
+			</view>
+			<!-- #ifndef H5 -->
+			<view v-if='!show' class="more" @tap="showMore">
+				<view class="cuIcon-roundadd"></view>
+			</view>
+			<button v-else @tap="btnSend(0, textMsg)"  class="cu-btn bg-green shadow textmsg">发送</button>
+			<!-- <view class='textmsg' v-show='show'>
+				<view @tap="">发送</view>
+			</view> -->
+			<!-- #endif -->
+			<!-- #ifdef H5 -->
+			<view class="send" @tap="sendMsg(0, textMsg)" :class="isVoice?'hidden':''">
+				<view class="iconfont icontuiguang-weixuan"></view>
+			</view>
+			<!-- #endif -->
+		</view>
+		
+		<!-- 录音UI效果 -->
+		<view class="record" :class="recording?'':'hidden'">
+			<view class="ing" :class="willStop?'hidden':''">
+				<view class="voice_an">
+					<view class="voice_an_icon">
+						<view id="one" class="wave"></view>
+						<view id="two" class="wave"></view>
+						<view id="three" class="wave"></view>
+						<view id="four" class="wave"></view>
+						<view id="five" class="wave"></view>
+						<view id="six" class="wave"></view>
+						<view id="seven" class="wave"></view>
+					</view>
+				</view>
+			</view>
+			<view class="cancel" :class="willStop?'':'hidden'"><view class="icon chehui" ></view></view>
+			<view class="tis" :class="willStop?'change':''">{{recordTis}}</view>
+		</view>
+		<!-- <v-keyboard ref="keyboard" :disorderly="false" @typing="typing" @enter="enter"></v-keyboard> -->
+	</view>
+</template>
+
+<script>
+	export default {
+		name:'footer-input',
+		components: {  } ,
+		props: {
+			inputOffsetBottom: {
+				type: Number,
+				default: 0
+			},
+			isVoice: {
+				type: Boolean,
+				default: false
+			},
+			focus: {
+				type: Boolean,
+				default: false
+			},
+			disabledSay: {
+				type: Number,
+				default: 0
+			},
+			popupLayerClass: {
+				type: String,
+				default: ''
+			},
+			textMsg2:{
+				type:String,
+				default:''
+			}
+		},
+		data() {
+			return {
+				placeholder: '',
+				initPoint:{identifier:0,Y:0},
+				//录音相关参数
+				// #ifndef H5
+				//H5不能录音
+				RECORDER:uni.getRecorderManager(),
+				// #endif
+				recordTis:"手指上滑 取消发送",
+				voiceTis:'按住 说话',
+				recording:false,
+				willStop:false,
+				recordTimer:null,
+				recordLength:0,
+				textMsg:'',
+				bottomHeight:0,
+				show:false
+			};
+		},
+		watch:{
+			textMsg:function(v){
+				console.log(v)
+				if(v.length>0){
+					this.show=true
+				}else{
+					this.show=false
+				}
+				this.$emit('textMsgFunc',v)
+				if(this.textMsg.indexOf('@')!=-1){
+				  if (this.chatObj.chatType==1){
+					  this.$u.route({
+					  	url:'pageC/chat/remind',
+					  	params:{ msg :this.textMsg }
+					  });	
+				  }
+				}
+			},
+			textMsg2:function(v){
+				this.textMsg = v
+			}
+		},
+		mounted() {
+			// #ifndef H5
+			this.RECORDER.onStart((e)=>{
+				this.recordBegin(e);
+			})
+			//录音结束事件
+			this.RECORDER.onStop((e)=>{
+				this.recordEnd(e);
+			})
+			// #endif
+		},
+		methods:{
+			discard(){
+				return;
+			},
+			btnSend(index, msg){
+				uni.hideKeyboard()
+				console.log('this.focus',this.focus)
+				this.focus=false  
+				this.sendMsg(index, msg)
+			},
+			// sendmsg(){
+			// 	console.log(uni.createSelectorQuery().select("#iamnode"))
+			// },
+			sendMsg(index, msg){
+				this.bottomHeight=0
+				this.show=false
+				this.$emit('sendMsg', index,msg);
+				this.textMsg = ''
+			},
+			textareaBlur(e){
+				console.log('textareaBlur',e)
+				this.bottomHeight=0
+			},
+			textareaFocus(e){
+				this.$refs.keyboard.activate()
+				console.log('textareaFocus',this.focus)
+				this.bottomHeight=e.detail.height
+				this.$emit('textareaFocus', true);
+			},
+			// 选择表情
+			chooseEmoji(e){
+				e.preventDefault();
+				this.bottomHeight=0
+				uni.hideKeyboard()
+				this.$emit('chooseEmoji', true);
+			},
+			// 切换语音/文字输入
+			switchVoice(){
+				this.$emit('switchVoice', true);
+			},
+			// 录音开始
+			voiceBegin(e){
+				if(e.touches.length>1){
+					return ;
+				}
+				this.recording = true;
+				this.initPoint.Y = e.touches[0].clientY;
+				this.initPoint.identifier = e.touches[0].identifier;
+				this.RECORDER.start({format:"mp3"});//录音开始,
+			},
+			//录音开始UI效果
+			recordBegin(e){
+				this.recording = true;
+				this.voiceTis='松开 结束';
+				this.recordLength = 0;
+				this.recordTimer = setInterval(()=>{
+					this.recordLength++;
+				},1000)
+			},
+			// 录音被打断
+			voiceCancel(){
+				this.recording = false;
+				this.voiceTis='按住 说话';
+				this.recordTis = '手指上滑 取消发送'
+				this.willStop = true;//不发送录音
+				this.RECORDER.stop();//录音结束
+			},
+			// 录音中(判断是否触发上滑取消发送)
+			voiceIng(e){
+				if(!this.recording){
+					return;
+				}
+				let touche = e.touches[0];
+				//上滑一个导航栏的高度触发上滑取消发送
+				if(this.initPoint.Y - touche.clientY>=uni.upx2px(100)){
+					this.willStop = true;
+					this.recordTis = '松开手指 取消发送'
+				}else{
+					this.willStop = false;
+					this.recordTis = '手指上滑 取消发送'
+				}
+			},
+			// 结束录音
+			voiceEnd(e){
+				if(!this.recording){
+					return;
+				}
+				this.recording = false;
+				this.voiceTis='按住 说话';
+				this.recordTis = '手指上滑 取消发送'
+				this.RECORDER.stop();//录音结束
+			},
+			//录音结束(回调文件)
+			recordEnd(e){
+				console.log('--------到此一游------1-')
+				clearInterval(this.recordTimer);
+				if(this.recordLength == 0){
+					this.$api.msg("录音时长过短")
+					return
+				}
+				if(!this.willStop){
+			      let tempFilePaths =e.tempFilePath;
+				  let that=this;
+					uni.uploadFile({
+						url: this.$uploadUrl, //仅为示例,非真实的接口地址
+						filePath: tempFilePaths,
+						header: {
+						'merchcode':'md5'
+						},
+						name: 'file',
+						formData: {
+							'user': 'test'
+						},
+						success: (res) => {
+							let data =JSON.parse(res.data)
+							let msg = {
+								length:0,
+								url:data.data
+							}
+							let min = parseInt(this.recordLength/60);
+							let sec = this.recordLength%60;
+							min = min<10?'0'+min:min;
+							sec = sec<10?'0'+sec:sec;
+							msg.length = min+':'+sec;
+							console.log('--------到此一游------2-')
+							this.$emit('sendMsg', 3,JSON.stringify(msg));
+						}
+					});
+				}else{
+					// console.log('取消发送录音');
+				}
+				this.willStop = false;
+				console.log('--------到此一游------3-')
+			},
+			//更多功能(点击+弹出)
+			showMore(){
+				this.$emit('showMore', true);
+			},
+			// 打开抽屉
+			openDrawer(){
+				this.$emit('openDrawer', true);
+			},
+			// 隐藏抽屉
+			hideDrawer(){
+				this.$emit('hideDrawer', true);
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+@import "@/pageC/chat/style.scss";
+[class^='cuIcon']{
+	font-size:50rpx;
+}
+.input-box{
+	position:fixed;
+}
+.textmsg{
+	width: 160upx;
+	padding: 5upx;
+	margin-top: 10upx
+}
+.textmsg view{
+	border: 1px solid;
+	background: #39b54a;
+	color: #fff;
+	padding: 10px;
+}
+.voice_an_icon{
+	position: relative;
+}
+.voice_an_icon .wave:nth-child(1){
+    left: 0px;
+    animation: musicWave 0.5s infinite linear both alternate;
+}
+.voice_an_icon .wave:nth-child(2){
+    left: 10px;
+    animation: musicWave 0.2s infinite linear both alternate;
+}
+.voice_an_icon .wave:nth-child(3){
+    left: 20px;
+    animation: musicWave 0.6s infinite linear both alternate;
+}
+.voice_an_icon .wave:nth-child(4){
+    left: 40px;
+    animation: musicWave 0.3s infinite linear both alternate;
+}
+.voice_an_icon .wave:nth-child(5){
+    left: 50px;
+    animation: musicWave 0.4s infinite linear both alternate;
+}
+.voice_an_icon .wave:nth-child(6){
+    left: 50px;
+    animation: musicWave 0.7s infinite linear both alternate;
+}
+.voice_an_icon .wave:nth-child(7){
+    left: 50px;
+    animation: musicWave 0.1s infinite linear both alternate;
+}
+@keyframes musicWave{
+    0%{
+        height: 10px;
+    }
+    100%{
+        height: 30px;
+    }
+}
+</style>

+ 90 - 0
components/chat/im-drawer.vue

@@ -0,0 +1,90 @@
+<template>
+	<view>
+		<!-- 抽屉栏 -->
+		<view class="popup-layer" :class="popupLayerClass" @touchmove.stop.prevent="discard">
+			<!-- 表情 -->
+			<emotion @addEmoji="addEmoji" @send="sendMsg(0,textMsg)" :class="{hidden:hideEmoji}"></emotion>
+			<!-- 更多功能 相册-拍照-红包 -->
+			<view class="more-layer" :class="{hidden:hideMore}">
+				<!-- <view class="list">
+					<view class="box" @tap="chooseImage">
+						<image class="box-xx" src="../../static/img/more/tupian.png"></image>
+					</view>
+					<view class="box" @tap="camera">
+						<image class="box-xx" src="../../static/img/more/paizhao.png"></image>
+					</view>
+					<view class="box" @tap="redShow">
+						<image class="box-xx" src="../../static/img/more/hongbao.png"></image>
+					</view>
+				</view> -->
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import emojiData from "@/pageC/static/emoji/emojiData.js"
+	import emotion from '@/components/emotion/index.vue'
+	export default {
+		name:'im-drawer',
+		components:{
+			emotion
+		},
+		data() {
+			return {
+				inputOffsetBottom: 0, //键盘的高度
+				viewOffsetBottom: 0, //视窗距离页面的距离
+			};
+		},
+		props: {
+			hideMore: {
+				type: Boolean,
+				default: true
+			},
+			hideEmoji: {
+				type: Boolean,
+				default: true
+			},
+			popupLayerClass: {
+				type: String,
+				default: ''
+			},
+			textMsg: {
+				type: String,
+				default: ''
+			},
+		},
+		methods:{
+			sendMsg(index,textMsg){
+				this.$emit('sendMsg', index , textMsg);
+			},
+			redShow(){
+				this.$emit('redShow', true);
+			},
+			weizhi(){
+			},
+			discard(){
+				return;
+			},
+			getImage(type){
+				this.$emit('getImage', type);
+			},
+			// 选择图片发送
+			chooseImage(){
+				this.getImage('album');
+			},
+			//拍照发送
+			camera(){
+				this.getImage('camera');
+			},
+			//添加表情
+			addEmoji(em, del){
+				this.$emit('addEmoji', em, del);
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+@import "@/pageC/chat/style.scss";
+</style>

+ 258 - 0
components/chat/left-bubble.vue

@@ -0,0 +1,258 @@
+<template>
+	<view>
+		<!-- 别人发出的消息 -->
+		<view class="other" v-if="row.sendUid!=userData.user.operId">
+			<!-- 右键 -->
+			<view class="left-click" v-show="row.id==lClickId">
+				<view @tap="copyFunc(row.msgContext)">复制</view>
+				<view @tap="deleteFunc(row.id,index)" v-if="row.msgType!=1">删除</view>
+				<view @tap="forwardFunc(row)" v-if="row.msgType!=7">转发</view>
+				<view @tap="collectFunc(row)" v-if="row.msgType==1">收藏</view>
+				<!-- <text @tap="rollBackFunc(row)">撤销</text> -->
+			</view>
+			<!-- 左-头像 -->
+			<view :class="row.msgType==0?'left text':'left'" @tap="linkToCard(row.sendUid)">
+				<img-cache :src="row.avatar"></img-cache>
+			</view>
+			<!-- 右-用户名称-时间-消息 -->
+			<view class="right">
+				<view class="username">
+					<view class="name">{{row.nickName}}</view>
+					<view class="time">{{row.operTime|formatDate}}</view>
+				</view>
+				<!-- 文字消息 -->
+				<view @longtap="openLeft(row)" v-if="row.msgType==0" class="bubble">
+					<rich-text :nodes="transformFace(row.msgContext)"></rich-text>
+				</view>
+				<!-- 语音消息 -->
+				<view @longtap="openLeft(row)" v-if="row.msgType==3" class="bubble voice" @tap="playVoice(row)" :class="playMsgid == row.id?'play':''">
+					<view class="icon other-voice"></view>
+					<view class="length">{{recordToJson(row.msgContext).length}}</view>
+				</view>
+				<!-- 图片消息 -->
+				<view @longtap="openLeft(row)" v-if="row.msgType==1" class="bubble img" @tap="showPic(`${$url}/${row.msgContext}`)">
+					<image :src="`${$url}/scale_${row.msgContext}`" style="width:100px;height:100px"></image>
+				</view>
+				<!-- 红包 -->
+				<view v-if="row.msgType==7" @tap="openRedPacket(row,index)">
+					<div class="message-red-packet-left" :style="redProcess(row.msgContext).surplusMoney===0?'background:#F7DFC3':'background:#F09D47'">
+						<div class="text">
+						   <span class="packet">恭喜发财,大吉大利</span>
+						   <!-- <image :src="redProcess(row.msgContext).surplusMoney===0?'../../static/img/red-chai.png':'../../static/img/red.png'"></image> -->
+						</div>
+						<div :class="redProcess(row.msgContext).surplusMoney===0?'footer2':'footer'">红包</div>
+						<div class="arrow-org" :style="redProcess(row.msgContext).surplusMoney===0?'background:#F7DFC3':'background:#F09D47'"></div>
+					  </div>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { transform } from "@/pageC/static/emoji/ChatUtils.js";
+	export default {
+		name: 'left-bubble',
+		components:{
+		},
+		props: {
+			row: {
+				type: Object,
+				default() {
+					return {};
+				}
+			},
+			lClickId: {
+				type: Number,
+				default: 0
+			},
+			
+			index:{
+				type:Number,
+				defalut:0
+			},
+		},
+		data() {
+			return {
+				//播放语音相关参数
+				AUDIO:uni.createInnerAudioContext(),
+				$url:'',
+				playMsgid: {
+					type: Number,
+					default: 0
+				},
+			};
+		},
+		mounted() {
+			//语音自然播放结束
+			this.AUDIO.onEnded((res)=>{
+			});
+		},
+		filters: {
+			formatDate: function (e) {
+				// 获取js 时间戳
+				let time = new Date().getTime();
+				// 去掉 js 时间戳后三位
+				time = parseInt((time - e) / 1000);
+				// 存储转换值
+				let s;
+				if (time < 60 * 10) {
+				  // 十分钟内
+				  return '刚刚';
+				} else if (time < 60 * 60 && time >= 60 * 10) {
+				  // 超过十分钟少于1小时
+				  s = Math.floor(time / 60);
+				  return s + '分钟前';
+				} else if (time < 60 * 60 * 24 && time >= 60 * 60) {
+				  // 超过1小时少于24小时
+				  s = Math.floor(time / 60 / 60);
+				  return s + '小时前';
+				} else if (time < 60 * 60 * 24 * 3 && time >= 60 * 60 * 24) {
+				  // 超过1天少于3天内
+				  s = Math.floor(time / 60 / 60 / 24);
+				  return s + '天前';
+				} else {
+				  // 超过3天
+				 var date = new Date(e);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
+				 var Y = date.getFullYear() + '-';
+				 var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
+				 var D = date.getDate() + ' ';
+				 var h = date.getHours() + ':';
+				 var m = date.getMinutes() + ':';
+				 var ss = date.getSeconds();
+				 return Y+M+D+h+m+ss;
+				}   
+			}
+		},
+		methods:{
+			openLeft(row){
+				this.$emit('openLeft', row);
+			},
+			deleteFunc(id,index){
+				this.$emit('deleteF', id, index);
+			},
+			// 打开红包
+			openRedPacket(msg){
+				this.$emit('openRedPacket',msg);
+			},
+			//收藏表情
+			collectFunc({msgContext}){
+				this.$socket.addEmoticon(this.userData.user.operId, msgContext, res => {
+					if(res.success){
+						uni.showToast({
+							icon:'none',
+							title:'添加成功'
+						})
+					}
+				  });
+			},
+			//处理红包数据
+			redProcess(msgContext){
+				let packets = JSON.parse(msgContext).Packets;
+				let msg = {
+					description:'好友暂不支持发红包',
+					money:0,
+					number:0,
+					userAvatar:'defalut.jpg',
+					surplusMoney:0,
+					Records:[]
+				}
+				if(packets==undefined){
+					return msg;
+				}
+				if(packets.length==0){
+					return msg;
+				}
+				return packets[0];
+			},
+			// 预览图片
+			showPic(msg){
+				uni.previewImage({
+					indicator:"none",
+					current: msg,
+					urls: [msg]
+				});
+			},
+			// 播放语音
+			playVoice(msg){
+				let s =JSON.parse(msg.msgContext);
+				this.playMsgid= msg.id;
+				this.AUDIO.src = this.$url + s.url;
+				this.$nextTick(function() {
+					this.AUDIO.play();
+				});
+			},
+			playMp3(){
+				let s =JSON.parse(msg.msgContext);
+				const innerAudioContext = uni.createInnerAudioContext();
+				innerAudioContext.autoplay = true;
+				innerAudioContext.src = this.$url + s.url;
+				innerAudioContext.onPlay(() => {
+				  console.log('开始播放');
+				});
+				innerAudioContext.onError((res) => {
+				  console.log(res.errMsg);
+				  console.log(res.errCode);
+				});
+			},
+			recordToJson(msg){
+				let s =JSON.parse(msg);
+				return s
+			},
+			//复制
+			copyFunc(content){
+				uni.setClipboardData({
+				data:content,
+				success:()=>{
+				  uni.showToast({
+					title:"复制成功"
+				  })
+				}
+			  });
+			},
+			//转发
+			forwardFunc({msgType,msgContext}){
+				this.$u.route({
+					url:'pageC/chat/forward',
+					params:{msgType,msgContext}
+				});	
+			},
+			//撤销
+			rollBackFunc({id,operTime}){
+				let _this = this
+				uni.showActionSheet({
+				    itemList: ['确认'],
+				    success: function (res) {
+				        if(res.tapIndex==0){
+							let type = _this.chatObj.chatType;
+							let arr = ['deleteFriendMsg','deleteGroupMsg']
+							_this.$socket[arr[type]](_this.userData.user.operId, id, _this.chatObj.chatId, res => {
+								if (res.success) {
+								  _this.sendMsg(6, id);
+								}
+							})
+						}
+				    },
+				    fail: function (res) {
+				        console.log(res.errMsg);
+				    }
+				});
+			},
+			//名片
+			linkToCard(id){
+				this.$u.route({
+					url: 'pageC/businessCard/businessCard',
+					params:{ id: id, source: 1}
+				})
+			},
+			//表情转换
+			transformFace(text){
+				return transform(text)
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+@import "@/pageC/chat/style.scss";
+</style>

+ 19 - 0
components/chat/more.vue

@@ -0,0 +1,19 @@
+<template>
+	<view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style lang="less">
+
+</style>

+ 84 - 0
components/chat/red-card.vue

@@ -0,0 +1,84 @@
+<template>
+	<view>
+		<!-- 红包弹窗 -->
+		<view class="windows" :class="winState">
+			<!-- 遮罩层 -->
+			<view class="mask" @touchmove.stop.prevent="discard" @tap="closeRed"></view>
+			<view class="layer" @touchmove.stop.prevent="discard">
+				<view class="open-redenvelope">
+					<view class="from">
+						<image :src="packet.userAvatar"></image>  
+						{{ packet.userName }}的红包
+					</view>
+					<template v-if="packet.Records">
+						<template v-for="(r,index) in packet.Records">
+							<view class="money" v-if="r.robUid===userData.user.operId">
+								{{r.money}}
+							</view>
+						</template>
+					</template>
+					<view class="blessing">恭喜发财,大吉大利</view>
+					<view class="top">
+						<view class="close-btn">
+							<view class="icon close" @tap="closeRed"></view>
+						</view>
+						<template v-if="packet.Records">
+							<view v-if="!xxx()" class="img" @tap="robRed">开</view>
+							<view v-else-if="packet.Records.length===0" class="img" @tap="robRed">开</view>
+						</template>
+					</view>
+					<view class="showDetails" @tap="toDetails">
+						查看领取详情
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:'red-card',
+		data() {
+			return {
+				$url:''
+			};
+		},
+		props: {
+			winState: {
+				type: String,
+				default: ''
+			},
+		},
+		methods:{
+			xxx(){
+				let isRob = false;
+				for(var i in this.packet.Records){
+					if(this.packet.Records[i].robUid === this.userData.user.operId){
+						isRob = true;
+					}
+				}
+				return isRob;
+			},
+			discard(){
+				return;
+			},
+			toDetails(){
+				this.$u.vuex('packet',this.packet)
+				uni.navigateTo({
+					url:'./detail'
+				})
+			},
+			robRed(){
+				this.$emit('robRed',true);
+			},
+			closeRed(){
+				this.$emit("closeRed", true)
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+@import "@/pageC/chat/style.scss";
+</style>

+ 262 - 0
components/chat/right-bubble.vue

@@ -0,0 +1,262 @@
+<template>
+	<view>
+		<!-- 自己发出的消息 -->
+		<view class="my" v-if="row.sendUid==userData.user.operId">
+			<!-- 右键 -->
+			<view class="right-click" v-show="row.id==rClickId">
+				<view @tap="copyFunc(row.msgContext)">复制</view>
+				<view @tap="deleteFunc(row.id,index)" v-if="row.msgType!=1">删除</view>
+				<view @tap="forwardFunc(row)" v-if="row.msgType!=7">转发</view>
+				<view @tap="collectFunc(row)" v-if="row.msgType==1">收藏</view>
+				<text @tap="rollBackFunc(row)">撤销</text>
+			</view>
+			
+			<!-- 左-消息 -->
+			<view class="left">
+				<!-- 文字消息 -->
+				<view @longpress="openRight(row)" v-if="row.msgType==0" class="bubble text">
+					<rich-text :nodes="transformFace(row.msgContext)"></rich-text>
+				</view>
+				<!-- 图片消息 -->
+				<view @longpress="openRight(row)" v-if="row.msgType==1" class="bubble img" @tap="showPic(`/${$url}/${row.msgContext}`)">
+					<image :src="$url+'/scale_'+ row.msgContext" style="width:100px;height:100px"></image>
+				</view>
+				<!-- 语言消息 -->
+				<view @longpress="openRight(row)" v-if="row.msgType==3" class="bubble voice" @tap="playVoice(row)" :class="playMsgId == row.id?'play':''">
+					<view class="length">{{recordToJson(row.msgContext).length}}</view>
+					<view class="icon my-voice"></view>
+				</view>
+				<!-- 红包 -->
+				<view v-if="row.msgType==7" @tap="openRedPacket(row)">
+					<div class="message-red-packet-right" :style="redProcess(row.msgContext).surplusMoney===0?'background:#F7DFC3':'background:#F09D47'">
+						<div class="text">
+						  <!-- <image :src="redProcess(row.msgContext).surplusMoney===0?'../../static/img/red-chai.png':'../../static/img/red.png'"></image> -->
+						  <span class="packet">恭喜发财,大吉大利</span>
+						</div>
+						<div :class="redProcess(row.msgContext).surplusMoney===0?'footer2':'footer'">红包</div>
+						<div class="arrow-org" :style="redProcess(row.msgContext).surplusMoney===0?'background:#F7DFC3':'background:#F09D47'"></div>
+					</div>
+				</view>
+			</view>
+			
+			<!-- 右-头像 -->
+			<view :class="row.msgType==0?'right text':'right'" @tap="linkToCard(row.sendUid)">
+				<img-cache :src="row.avatar"></img-cache>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import ImgCache from '@/components/img-cache/img-cache.vue';
+	import { transform } from "@/pageC/static/emoji/ChatUtils.js";
+	
+	export default {
+		name: 'right-bubble',
+		components:{
+			ImgCache
+		},
+		props: {
+			row: {
+				type: Object,
+				default() {
+					return {};
+				}
+			},
+			rClickId: {
+				type: Number,
+				default: 0
+			},
+			
+			index:{
+				type:Number,
+				defalut:0
+			},
+		},
+		data() {
+			return {
+				//播放语音相关参数
+				AUDIO:uni.createInnerAudioContext(),
+				$url:'',
+				playMsgId: {
+					type: Number,
+					default: 0
+				},
+			};
+		},
+		mounted() {
+			//语音自然播放结束
+			this.AUDIO.onEnded((res)=>{
+			});
+		},
+		filters: {
+			formatDate: function (e) {
+				// 获取js 时间戳
+				let time = new Date().getTime();
+				// 去掉 js 时间戳后三位
+				time = parseInt((time - e) / 1000);
+				// 存储转换值
+				let s;
+				if (time < 60 * 10) {
+				  // 十分钟内
+				  return '刚刚';
+				} else if (time < 60 * 60 && time >= 60 * 10) {
+				  // 超过十分钟少于1小时
+				  s = Math.floor(time / 60);
+				  return s + '分钟前';
+				} else if (time < 60 * 60 * 24 && time >= 60 * 60) {
+				  // 超过1小时少于24小时
+				  s = Math.floor(time / 60 / 60);
+				  return s + '小时前';
+				} else if (time < 60 * 60 * 24 * 3 && time >= 60 * 60 * 24) {
+				  // 超过1天少于3天内
+				  s = Math.floor(time / 60 / 60 / 24);
+				  return s + '天前';
+				} else {
+				  // 超过3天
+				 var date = new Date(e);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
+				 var Y = date.getFullYear() + '-';
+				 var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
+				 var D = date.getDate() + ' ';
+				 var h = date.getHours() + ':';
+				 var m = date.getMinutes() + ':';
+				 var ss = date.getSeconds();
+				 return Y+M+D+h+m+ss;
+				}   
+			}
+		},
+		methods:{
+			openRight(row){
+				this.$emit('openRight', row);
+			},
+			deleteFunc(id,index){
+				this.$emit('deleteF', id, index);
+			},
+			//收藏表情
+			collectFunc({msgContext}){
+				this.$socket.addEmoticon(this.userData.user.operId, msgContext, res => {
+					if(res.success){
+						uni.showToast({
+							icon:'none',
+							title:'添加成功'
+						})
+					}
+				  });
+			},
+			// 打开红包
+			openRedPacket(msg){
+				this.$emit('openRedPacket',msg);
+			},
+			//处理红包数据
+			redProcess(msgContext){
+				let packets = JSON.parse(msgContext).Packets;
+				let msg = {
+					description:'好友暂不支持发红包',
+					money:0,
+					number:0,
+					userAvatar:'defalut.jpg',
+					surplusMoney:0,
+					Records:[]
+				}
+				if(packets==undefined){
+					return msg;
+				}
+				if(packets.length==0){
+					return msg;
+				}
+				return packets[0];
+			},
+			// 预览图片
+			showPic(msg){
+				uni.previewImage({
+					indicator:"none",
+					current: msg,
+					urls: [msg]
+				});
+			},
+			// 播放语音
+			playVoice(msg){
+				let s =JSON.parse(msg.msgContext);
+				this.playMsgId= msg.id;
+				this.AUDIO.src = this.$url + s.url;
+				this.$nextTick(function() {
+					this.AUDIO.play();
+				});
+			},
+			playMp3(){
+				let s =JSON.parse(msg.msgContext);
+				const innerAudioContext = uni.createInnerAudioContext();
+				innerAudioContext.autoplay = true;
+				innerAudioContext.src = this.$url + s.url;
+				innerAudioContext.onPlay(() => {
+				  console.log('开始播放');
+				});
+				innerAudioContext.onError((res) => {
+				  console.log(res.errMsg);
+				  console.log(res.errCode);
+				});
+			},
+			recordToJson(msg){
+				let s =JSON.parse(msg);
+				return s
+			},
+			//复制
+			copyFunc(content){
+				uni.setClipboardData({
+				data:content,
+				success:()=>{
+				  uni.showToast({
+					title:"复制成功"
+				  })
+				}
+			  });
+			},
+			//转发
+			forwardFunc({msgType,msgContext}){
+				this.$u.route({
+					url:'pageC/chat/forward',
+					params:{msgType,msgContext}
+				});	
+			},
+			//撤销
+			rollBackFunc({id,operTime}){
+				let _this = this
+				uni.showActionSheet({
+				    itemList: ['确认'],
+				    success: function (res) {
+				        if(res.tapIndex==0){
+							let type = _this.chatObj.chatType;
+							let arr = ['deleteFriendMsg','deleteGroupMsg']
+							_this.$socket[arr[type]](_this.userData.user.operId, id, _this.chatObj.chatId, res => {
+								if (res.success) {
+								  _this.sendMsg(6, id)
+								}
+							})
+						}
+				    },
+				    fail: function (res) {
+				        console.log(res.errMsg);
+				    }
+				});
+			},
+			sendMsg(index, msg){
+				this.$emit('sendMsg',index,msg);
+			},
+			//名片	
+			linkToCard(id){
+				this.$u.route({
+					url: 'pageC/businessCard/businessCard',
+					params:{ id: id, source: 1}
+				})
+			},
+			//表情转换
+			transformFace(text){
+				return transform(text)
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+@import "@/pageC/chat/style.scss";
+</style>

+ 19 - 0
components/chat/system-bubble.vue

@@ -0,0 +1,19 @@
+<template>
+	<view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:'system-bubble',
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style lang="scss">
+@import "@/pageC/chat/style.scss";
+</style>

+ 22 - 0
components/chat/tttt.vue

@@ -0,0 +1,22 @@
+<template>
+	<view>
+		<!-- @功能 -->
+		<!-- <view class="process" v-show="_call_s.length>0" @click="processFunc">
+			<text>当前{{_call_s.length}}人@我</text>
+		</view> -->
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style lang="scss">
+
+</style>

+ 147 - 0
components/chatItem.vue

@@ -0,0 +1,147 @@
+<template>
+	<view class="item u-border-bottom" :class="value.isTop? 'bg_view' : ''" hover-class="message-hover-class" @tap="linkTo(value)">
+		<img-cache :src="value.avatar|| value.imgUrl"></img-cache>
+		<u-badge v-if="value.unreadNumber" :count="value.unreadNumber"  type="error" class="badge" :offset="offset"></u-badge>
+		<view class="right title-wrap">
+			<view class="right_top">
+				<view class="right_top_name u-line-1">{{ value.chatName || value.nickName}}</view>
+				<view class="right_top_time ">{{value.lastOperTime || value.lastOpenTime | format}}</view>
+			</view>
+			<view class="right_btm ">
+				<view class="u-line-1">{{value.msgType==0?value.content:message[value.msgType]}}</view>
+				<view class="" v-show="voiceIcon">
+					<u-icon color="#c4c7cf" v-if="index%2==0" name="bell" size="22"></u-icon>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import ImgCache from '@/components/img-cache/img-cache.vue';
+	export default {
+		name: 'u-chatItem',
+		components:{
+			ImgCache
+		},
+		props: {
+			value: {
+				type: Object,
+				default() {
+					return {};
+				}
+			},
+			index: {
+				type: Number,
+				default: -1
+			},
+			voiceIcon:{
+				type: Boolean,
+				default () {
+					return false;
+				}
+			}
+		},
+		data() {
+			return {
+				offset:[10, 620],
+				message:['文字', '图片', '表情', '语音', '视频',
+				 '签到', '已撤销一条消息', '发红包', '抢红包'],
+				$url:''
+			};
+		},
+		filters: {
+		   format: function (e) {
+			  // 获取js 时间戳
+			  let time = new Date().getTime();
+			  // 去掉 js 时间戳后三位
+			  time = parseInt((time - e) / 1000);
+			  // 存储转换值
+			  let s;
+			  if (time < 60 * 10) {
+			    // 十分钟内
+			    return '刚刚';
+			  } else if (time < 60 * 60 && time >= 60 * 10) {
+			    // 超过十分钟少于1小时
+			    s = Math.floor(time / 60);
+			    return s + '分钟前';
+			  } else if (time < 60 * 60 * 24 && time >= 60 * 60) {
+			    // 超过1小时少于24小时
+			    s = Math.floor(time / 60 / 60);
+			    return s + '小时前';
+			  } else if (time < 60 * 60 * 24 * 3 && time >= 60 * 60 * 24) {
+			    // 超过1天少于3天内
+			    s = Math.floor(time / 60 / 60 / 24);
+			    return s + '天前';
+			  } else {
+			    // 超过3天
+			   var date = new Date(e);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
+			   var Y = date.getFullYear() + '-';
+			   var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
+			   var D = date.getDate() + ' ';
+			   var h = date.getHours() + ':';
+			   var m = date.getMinutes() + ':';
+			   var ss = date.getSeconds();
+			   return h+m+ss;
+			  }  
+		   }
+		},
+		methods:{
+			linkTo(item) {
+				this.$emit('linkTo',item)
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+.item {
+		width: 750rpx;
+		height: 140rpx;
+		display: flex;
+		align-items: center;
+		image {
+			width: 96rpx;
+			height: 96rpx;
+			margin: 20rpx;
+			border-radius: 12rpx;
+			flex: 0 0 96rpx;
+		}
+		.right {
+			overflow: hidden;
+			flex: 1 0;
+			padding: 20rpx 20rpx 40rpx 0;
+			&_top {
+				display: flex;
+				justify-content: space-between;
+				&_name {
+					font-size: 28rpx;
+					font-weight: 800;
+					color: $u-main-color;
+					flex: 0 0 450rpx;
+					overflow: hidden;
+				}
+				&_time {
+					font-size: 22rpx;
+					color: $u-light-color;
+				}
+			}
+			&_btm {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				font-size: 22rpx;
+				color: $u-tips-color;
+				padding-top: 10rpx;
+			}
+		}
+	}
+	.bg_view {
+		background-color: #fafafa;
+	}
+	.slot-wrap {
+		display: flex;
+		align-items: center;
+		padding: 0 30rpx; 
+	}
+</style>

+ 184 - 0
components/colorui/animation.css

@@ -0,0 +1,184 @@
+/* 
+  Animation 微动画  
+  基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
+ */
+
+/* css 滤镜 控制黑白底色gif的 */
+.gif-black{  
+  mix-blend-mode: screen;  
+}
+.gif-white{  
+  mix-blend-mode: multiply; 
+}
+
+
+/* Animation css */
+[class*=animation-] {
+    animation-duration: .5s;
+    animation-timing-function: ease-out;
+    animation-fill-mode: both
+}
+
+.animation-fade {
+    animation-name: fade;
+    animation-duration: .8s;
+    animation-timing-function: linear
+}
+
+.animation-scale-up {
+    animation-name: scale-up
+}
+
+.animation-scale-down {
+    animation-name: scale-down
+}
+
+.animation-slide-top {
+    animation-name: slide-top
+}
+
+.animation-slide-bottom {
+    animation-name: slide-bottom
+}
+
+.animation-slide-left {
+    animation-name: slide-left
+}
+
+.animation-slide-right {
+    animation-name: slide-right
+}
+
+.animation-shake {
+    animation-name: shake
+}
+
+.animation-reverse {
+    animation-direction: reverse
+}
+
+@keyframes fade {
+    0% {
+        opacity: 0
+    }
+
+    100% {
+        opacity: 1
+    }
+}
+
+@keyframes scale-up {
+    0% {
+        opacity: 0;
+        transform: scale(.2)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes scale-down {
+    0% {
+        opacity: 0;
+        transform: scale(1.8)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes slide-top {
+    0% {
+        opacity: 0;
+        transform: translateY(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes slide-bottom {
+    0% {
+        opacity: 0;
+        transform: translateY(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes shake {
+
+    0%,
+    100% {
+        transform: translateX(0)
+    }
+
+    10% {
+        transform: translateX(-9px)
+    }
+
+    20% {
+        transform: translateX(8px)
+    }
+
+    30% {
+        transform: translateX(-7px)
+    }
+
+    40% {
+        transform: translateX(6px)
+    }
+
+    50% {
+        transform: translateX(-5px)
+    }
+
+    60% {
+        transform: translateX(4px)
+    }
+
+    70% {
+        transform: translateX(-3px)
+    }
+
+    80% {
+        transform: translateX(2px)
+    }
+
+    90% {
+        transform: translateX(-1px)
+    }
+}
+
+@keyframes slide-left {
+    0% {
+        opacity: 0;
+        transform: translateX(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}
+
+@keyframes slide-right {
+    0% {
+        opacity: 0;
+        transform: translateX(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}

+ 65 - 0
components/colorui/components/cu-custom.vue

@@ -0,0 +1,65 @@
+<template>
+	<view>
+		<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
+			<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
+				<view class="action" @tap="BackPage" v-if="isBack">
+					<text class="cuIcon-back"></text>
+					<slot name="backText"></slot>
+				</view>
+				<view class="content" :style="[{top:StatusBar + 'px'}]">
+					<slot name="content"></slot>
+				</view>
+				<slot name="right"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				StatusBar: this.StatusBar,
+				CustomBar: this.CustomBar
+			};
+		},
+		name: 'cu-custom',
+		computed: {
+			style() {
+				var StatusBar= this.StatusBar;
+				var CustomBar= this.CustomBar;
+				var bgImage = this.bgImage;
+				var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+				if (this.bgImage) {
+					style = `${style}background-image:url(${bgImage});`;
+				}
+				return style
+			}
+		},
+		props: {
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			isBack: {
+				type: [Boolean, String],
+				default: false
+			},
+			bgImage: {
+				type: String,
+				default: ''
+			},
+		},
+		methods: {
+			BackPage() {
+				uni.navigateBack({
+					delta: 1
+				});
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 36 - 0
components/colorui/icon.css


+ 3926 - 0
components/colorui/main.css

@@ -0,0 +1,3926 @@
+/*
+  ColorUi for uniApp  v2.1.6 | by 文晓港 2019-05-31 10:44:24
+  仅供学习交流,如作它用所承受的法律责任一概与作者无关  
+  
+  *使用ColorUi开发扩展与插件时,请注明基于ColorUi开发 
+  
+  (QQ交流群:240787041)
+*/
+
+/* ==================
+        初始化
+ ==================== */
+body {
+	background-color: #f8f8f8;
+	font-size: 28upx;
+	color: #333333;
+	font-family: Helvetica Neue, Helvetica, sans-serif;
+}
+
+view,
+scroll-view,
+swiper,
+button,
+input,
+textarea,
+label,
+navigator,
+image {
+	box-sizing: border-box;
+}
+
+.round {
+	border-radius: 5000upx;
+}
+
+.radius {
+	border-radius: 6upx;
+}
+
+/* ==================
+          图片
+ ==================== */
+
+image {
+	max-width: 100%;
+	display: inline-block;
+	position: relative;
+	z-index: 0;
+}
+
+image.loading::before {
+	content: "";
+	background-color: #f5f5f5;
+	display: block;
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	z-index: -2;
+}
+
+image.loading::after {
+	content: "\e7f1";
+	font-family: "cuIcon";
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 32upx;
+	height: 32upx;
+	line-height: 32upx;
+	right: 0;
+	bottom: 0;
+	z-index: -1;
+	font-size: 32upx;
+	margin: auto;
+	color: #ccc;
+	-webkit-animation: cuIcon-spin 2s infinite linear;
+	animation: cuIcon-spin 2s infinite linear;
+	display: block;
+}
+
+.response {
+	width: 100%;
+}
+
+/* ==================
+         开关
+ ==================== */
+
+switch,
+checkbox,
+radio {
+	position: relative;
+}
+
+switch::after,
+switch::before {
+	font-family: "cuIcon";
+	content: "\e645";
+	position: absolute;
+	color: #ffffff !important;
+	top: 0%;
+	left: 0upx;
+	font-size: 26upx;
+	line-height: 26px;
+	width: 50%;
+	text-align: center;
+	pointer-events: none;
+	transform: scale(0, 0);
+	transition: all 0.3s ease-in-out 0s;
+	z-index: 9;
+	bottom: 0;
+	height: 26px;
+	margin: auto;
+}
+
+switch::before {
+	content: "\e646";
+	right: 0;
+	transform: scale(1, 1);
+	left: auto;
+}
+
+switch[checked]::after,
+switch.checked::after {
+	transform: scale(1, 1);
+}
+
+switch[checked]::before,
+switch.checked::before {
+	transform: scale(0, 0);
+}
+
+/* #ifndef MP-ALIPAY */
+radio::before,
+checkbox::before {
+	font-family: "cuIcon";
+	content: "\e645";
+	position: absolute;
+	color: #ffffff !important;
+	top: 50%;
+	margin-top: -8px;
+	right: 5px;
+	font-size: 32upx;
+	line-height: 16px;
+	pointer-events: none;
+	transform: scale(1, 1);
+	transition: all 0.3s ease-in-out 0s;
+	z-index: 9;
+}
+
+radio .wx-radio-input,
+checkbox .wx-checkbox-input,
+radio .uni-radio-input,
+checkbox .uni-checkbox-input {
+	margin: 0;
+	width: 24px;
+	height: 24px;
+}
+
+checkbox.round .wx-checkbox-input,
+checkbox.round .uni-checkbox-input {
+	border-radius: 100upx;
+}
+
+/* #endif */
+
+switch[checked]::before {
+	transform: scale(0, 0);
+}
+
+switch .wx-switch-input,
+switch .uni-switch-input {
+	border: none;
+	padding: 0 24px;
+	width: 48px;
+	height: 26px;
+	margin: 0;
+	border-radius: 100upx;
+}
+
+switch .wx-switch-input:not([class*="bg-"]),
+switch .uni-switch-input:not([class*="bg-"]) {
+	background: #8799a3 !important;
+}
+
+switch .wx-switch-input::after,
+switch .uni-switch-input::after {
+	margin: auto;
+	width: 26px;
+	height: 26px;
+	border-radius: 100upx;
+	left: 0upx;
+	top: 0upx;
+	bottom: 0upx;
+	position: absolute;
+	transform: scale(0.9, 0.9);
+	transition: all 0.1s ease-in-out 0s;
+}
+
+switch .wx-switch-input.wx-switch-input-checked::after,
+switch .uni-switch-input.uni-switch-input-checked::after {
+	margin: auto;
+	left: 22px;
+	box-shadow: none;
+	transform: scale(0.9, 0.9);
+}
+
+radio-group {
+	display: inline-block;
+}
+
+
+
+switch.radius .wx-switch-input::after,
+switch.radius .wx-switch-input,
+switch.radius .wx-switch-input::before,
+switch.radius .uni-switch-input::after,
+switch.radius .uni-switch-input,
+switch.radius .uni-switch-input::before {
+	border-radius: 10upx;
+}
+
+switch .wx-switch-input::before,
+radio.radio::before,
+checkbox .wx-checkbox-input::before,
+radio .wx-radio-input::before,
+switch .uni-switch-input::before,
+radio.radio::before,
+checkbox .uni-checkbox-input::before,
+radio .uni-radio-input::before {
+	display: none;
+}
+
+radio.radio[checked]::after,
+radio.radio .uni-radio-input-checked::after {
+	content: "";
+	background-color: transparent;
+	display: block;
+	position: absolute;
+	width: 8px;
+	height: 8px;
+	z-index: 999;
+	top: 0upx;
+	left: 0upx;
+	right: 0;
+	bottom: 0;
+	margin: auto;
+	border-radius: 200upx;
+	/* #ifndef MP */
+	border: 7px solid #ffffff !important;
+	/* #endif */
+
+	/* #ifdef MP */
+	border: 8px solid #ffffff !important;
+	/* #endif */
+}
+
+.switch-sex::after {
+	content: "\e71c";
+}
+
+.switch-sex::before {
+	content: "\e71a";
+}
+
+.switch-sex .wx-switch-input,
+.switch-sex .uni-switch-input {
+	background: #e54d42 !important;
+	border-color: #e54d42 !important;
+}
+
+.switch-sex[checked] .wx-switch-input,
+.switch-sex.checked .uni-switch-input {
+	background: #0081ff !important;
+	border-color: #0081ff !important;
+}
+
+switch.red[checked] .wx-switch-input.wx-switch-input-checked,
+checkbox.red[checked] .wx-checkbox-input,
+radio.red[checked] .wx-radio-input,
+switch.red.checked .uni-switch-input.uni-switch-input-checked,
+checkbox.red.checked .uni-checkbox-input,
+radio.red.checked .uni-radio-input {
+	background-color: #e54d42 !important;
+	border-color: #e54d42 !important;
+	color: #ffffff !important;
+}
+
+switch.orange[checked] .wx-switch-input,
+checkbox.orange[checked] .wx-checkbox-input,
+radio.orange[checked] .wx-radio-input,
+switch.orange.checked .uni-switch-input,
+checkbox.orange.checked .uni-checkbox-input,
+radio.orange.checked .uni-radio-input {
+	background-color: #f37b1d !important;
+	border-color: #f37b1d !important;
+	color: #ffffff !important;
+}
+
+switch.yellow[checked] .wx-switch-input,
+checkbox.yellow[checked] .wx-checkbox-input,
+radio.yellow[checked] .wx-radio-input,
+switch.yellow.checked .uni-switch-input,
+checkbox.yellow.checked .uni-checkbox-input,
+radio.yellow.checked .uni-radio-input {
+	background-color: #fbbd08 !important;
+	border-color: #fbbd08 !important;
+	color: #333333 !important;
+}
+
+switch.olive[checked] .wx-switch-input,
+checkbox.olive[checked] .wx-checkbox-input,
+radio.olive[checked] .wx-radio-input,
+switch.olive.checked .uni-switch-input,
+checkbox.olive.checked .uni-checkbox-input,
+radio.olive.checked .uni-radio-input {
+	background-color: #8dc63f !important;
+	border-color: #8dc63f !important;
+	color: #ffffff !important;
+}
+
+switch.green[checked] .wx-switch-input,
+switch[checked] .wx-switch-input,
+checkbox.green[checked] .wx-checkbox-input,
+checkbox[checked] .wx-checkbox-input,
+radio.green[checked] .wx-radio-input,
+radio[checked] .wx-radio-input,
+switch.green.checked .uni-switch-input,
+switch.checked .uni-switch-input,
+checkbox.green.checked .uni-checkbox-input,
+checkbox.checked .uni-checkbox-input,
+radio.green.checked .uni-radio-input,
+radio.checked .uni-radio-input {
+	background-color: #39b54a !important;
+	border-color: #39b54a !important;
+	color: #ffffff !important;
+	border-color: #39B54A !important;
+}
+
+switch.cyan[checked] .wx-switch-input,
+checkbox.cyan[checked] .wx-checkbox-input,
+radio.cyan[checked] .wx-radio-input,
+switch.cyan.checked .uni-switch-input,
+checkbox.cyan.checked .uni-checkbox-input,
+radio.cyan.checked .uni-radio-input {
+	background-color: #1cbbb4 !important;
+	border-color: #1cbbb4 !important;
+	color: #ffffff !important;
+}
+
+switch.blue[checked] .wx-switch-input,
+checkbox.blue[checked] .wx-checkbox-input,
+radio.blue[checked] .wx-radio-input,
+switch.blue.checked .uni-switch-input,
+checkbox.blue.checked .uni-checkbox-input,
+radio.blue.checked .uni-radio-input {
+	background-color: #0081ff !important;
+	border-color: #0081ff !important;
+	color: #ffffff !important;
+}
+
+switch.purple[checked] .wx-switch-input,
+checkbox.purple[checked] .wx-checkbox-input,
+radio.purple[checked] .wx-radio-input,
+switch.purple.checked .uni-switch-input,
+checkbox.purple.checked .uni-checkbox-input,
+radio.purple.checked .uni-radio-input {
+	background-color: #6739b6 !important;
+	border-color: #6739b6 !important;
+	color: #ffffff !important;
+}
+
+switch.mauve[checked] .wx-switch-input,
+checkbox.mauve[checked] .wx-checkbox-input,
+radio.mauve[checked] .wx-radio-input,
+switch.mauve.checked .uni-switch-input,
+checkbox.mauve.checked .uni-checkbox-input,
+radio.mauve.checked .uni-radio-input {
+	background-color: #9c26b0 !important;
+	border-color: #9c26b0 !important;
+	color: #ffffff !important;
+}
+
+switch.pink[checked] .wx-switch-input,
+checkbox.pink[checked] .wx-checkbox-input,
+radio.pink[checked] .wx-radio-input,
+switch.pink.checked .uni-switch-input,
+checkbox.pink.checked .uni-checkbox-input,
+radio.pink.checked .uni-radio-input {
+	background-color: #e03997 !important;
+	border-color: #e03997 !important;
+	color: #ffffff !important;
+}
+
+switch.brown[checked] .wx-switch-input,
+checkbox.brown[checked] .wx-checkbox-input,
+radio.brown[checked] .wx-radio-input,
+switch.brown.checked .uni-switch-input,
+checkbox.brown.checked .uni-checkbox-input,
+radio.brown.checked .uni-radio-input {
+	background-color: #a5673f !important;
+	border-color: #a5673f !important;
+	color: #ffffff !important;
+}
+
+switch.grey[checked] .wx-switch-input,
+checkbox.grey[checked] .wx-checkbox-input,
+radio.grey[checked] .wx-radio-input,
+switch.grey.checked .uni-switch-input,
+checkbox.grey.checked .uni-checkbox-input,
+radio.grey.checked .uni-radio-input {
+	background-color: #8799a3 !important;
+	border-color: #8799a3 !important;
+	color: #ffffff !important;
+}
+
+switch.gray[checked] .wx-switch-input,
+checkbox.gray[checked] .wx-checkbox-input,
+radio.gray[checked] .wx-radio-input,
+switch.gray.checked .uni-switch-input,
+checkbox.gray.checked .uni-checkbox-input,
+radio.gray.checked .uni-radio-input {
+	background-color: #f0f0f0 !important;
+	border-color: #f0f0f0 !important;
+	color: #333333 !important;
+}
+
+switch.black[checked] .wx-switch-input,
+checkbox.black[checked] .wx-checkbox-input,
+radio.black[checked] .wx-radio-input,
+switch.black.checked .uni-switch-input,
+checkbox.black.checked .uni-checkbox-input,
+radio.black.checked .uni-radio-input {
+	background-color: #333333 !important;
+	border-color: #333333 !important;
+	color: #ffffff !important;
+}
+
+switch.white[checked] .wx-switch-input,
+checkbox.white[checked] .wx-checkbox-input,
+radio.white[checked] .wx-radio-input,
+switch.white.checked .uni-switch-input,
+checkbox.white.checked .uni-checkbox-input,
+radio.white.checked .uni-radio-input {
+	background-color: #ffffff !important;
+	border-color: #ffffff !important;
+	color: #333333 !important;
+}
+
+/* ==================
+          边框
+ ==================== */
+
+/* -- 实线 -- */
+
+.solid,
+.solid-top,
+.solid-right,
+.solid-bottom,
+.solid-left,
+.solids,
+.solids-top,
+.solids-right,
+.solids-bottom,
+.solids-left,
+.dashed,
+.dashed-top,
+.dashed-right,
+.dashed-bottom,
+.dashed-left {
+	position: relative;
+}
+
+.solid::after,
+.solid-top::after,
+.solid-right::after,
+.solid-bottom::after,
+.solid-left::after,
+.solids::after,
+.solids-top::after,
+.solids-right::after,
+.solids-bottom::after,
+.solids-left::after,
+.dashed::after,
+.dashed-top::after,
+.dashed-right::after,
+.dashed-bottom::after,
+.dashed-left::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border-radius: inherit;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+}
+
+.solid::after {
+	border: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-top::after {
+	border-top: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-right::after {
+	border-right: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-bottom::after {
+	border-bottom: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-left::after {
+	border-left: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solids::after {
+	border: 8upx solid #eee;
+}
+
+.solids-top::after {
+	border-top: 8upx solid #eee;
+}
+
+.solids-right::after {
+	border-right: 8upx solid #eee;
+}
+
+.solids-bottom::after {
+	border-bottom: 8upx solid #eee;
+}
+
+.solids-left::after {
+	border-left: 8upx solid #eee;
+}
+
+/* -- 虚线 -- */
+
+.dashed::after {
+	border: 1upx dashed #ddd;
+}
+
+.dashed-top::after {
+	border-top: 1upx dashed #ddd;
+}
+
+.dashed-right::after {
+	border-right: 1upx dashed #ddd;
+}
+
+.dashed-bottom::after {
+	border-bottom: 1upx dashed #ddd;
+}
+
+.dashed-left::after {
+	border-left: 1upx dashed #ddd;
+}
+
+/* -- 阴影 -- */
+
+.shadow[class*='white'] {
+	--ShadowSize: 0 1upx 6upx;
+}
+
+.shadow-lg {
+	--ShadowSize: 0upx 40upx 100upx 0upx;
+}
+
+.shadow-warp {
+	position: relative;
+	box-shadow: 0 0 10upx rgba(0, 0, 0, 0.1);
+}
+
+.shadow-warp:before,
+.shadow-warp:after {
+	position: absolute;
+	content: "";
+	top: 20upx;
+	bottom: 30upx;
+	left: 20upx;
+	width: 50%;
+	box-shadow: 0 30upx 20upx rgba(0, 0, 0, 0.2);
+	transform: rotate(-3deg);
+	z-index: -1;
+}
+
+.shadow-warp:after {
+	right: 20upx;
+	left: auto;
+	transform: rotate(3deg);
+}
+
+.shadow-blur {
+	position: relative;
+}
+
+.shadow-blur::before {
+	content: "";
+	display: block;
+	background: inherit;
+	filter: blur(10upx);
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	top: 10upx;
+	left: 10upx;
+	z-index: -1;
+	opacity: 0.4;
+	transform-origin: 0 0;
+	border-radius: inherit;
+	transform: scale(1, 1);
+}
+
+/* ==================
+          按钮
+ ==================== */
+
+.cu-btn {
+	position: relative;
+	border: 0upx;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	box-sizing: border-box;
+	padding: 0 30upx;
+	font-size: 28upx;
+	height: 64upx;
+	line-height: 1;
+	text-align: center;
+	text-decoration: none;
+	overflow: visible;
+	margin-left: initial;
+	transform: translate(0upx, 0upx);
+	margin-right: initial;
+}
+
+.cu-btn::after {
+	display: none;
+}
+
+.cu-btn:not([class*="bg-"]) {
+	background-color: #f0f0f0;
+}
+
+.cu-btn[class*="line"] {
+	background-color: transparent;
+}
+
+.cu-btn[class*="line"]::after {
+	content: " ";
+	display: block;
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border: 1upx solid currentColor;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	box-sizing: border-box;
+	border-radius: 12upx;
+	z-index: 1;
+	pointer-events: none;
+}
+
+.cu-btn.round[class*="line"]::after {
+	border-radius: 1000upx;
+}
+
+.cu-btn[class*="lines"]::after {
+	border: 6upx solid currentColor;
+}
+
+.cu-btn[class*="bg-"]::after {
+	display: none;
+}
+
+.cu-btn.sm {
+	padding: 0 20upx;
+	font-size: 20upx;
+	height: 48upx;
+}
+
+.cu-btn.lg {
+	padding: 0 40upx;
+	font-size: 32upx;
+	height: 80upx;
+}
+
+.cu-btn.cuIcon.sm {
+	width: 48upx;
+	height: 48upx;
+}
+
+.cu-btn.cuIcon {
+	width: 64upx;
+	height: 64upx;
+	border-radius: 500upx;
+	padding: 0;
+}
+
+button.cuIcon.lg {
+	width: 80upx;
+	height: 80upx;
+}
+
+.cu-btn.shadow-blur::before {
+	top: 4upx;
+	left: 4upx;
+	filter: blur(6upx);
+	opacity: 0.6;
+}
+
+.cu-btn.button-hover {
+	transform: translate(1upx, 1upx);
+}
+
+.block {
+	display: block;
+}
+
+.cu-btn.block {
+	display: flex;
+}
+
+.cu-btn[disabled] {
+	opacity: 0.6;
+	color: #ffffff;
+}
+
+/* ==================
+          徽章
+ ==================== */
+
+.cu-tag {
+	font-size: 22upx;
+	vertical-align: middle;
+	position: relative;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	box-sizing: border-box;
+	padding: 0upx 16upx;
+	height: 40upx;
+	font-family: Helvetica Neue, Helvetica, sans-serif;
+	white-space: nowrap;
+}
+
+.cu-tag:not([class*="bg"]):not([class*="line"]) {
+	background-color: #f1f1f1;
+}
+
+.cu-tag[class*="line-"]::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border: 1upx solid currentColor;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	box-sizing: border-box;
+	border-radius: inherit;
+	z-index: 1;
+	pointer-events: none;
+}
+
+.cu-tag.radius[class*="line"]::after {
+	border-radius: 12upx;
+}
+
+.cu-tag.round[class*="line"]::after {
+	border-radius: 1000upx;
+}
+
+.cu-tag[class*="line-"]::after {
+	border-radius: 0;
+}
+
+.cu-tag+.cu-tag {
+	margin-left: 10upx;
+}
+
+.cu-tag.sm {
+	font-size: 20upx;
+	padding: 0upx 12upx;
+	height: 32upx;
+}
+
+.cu-capsule {
+	display: inline-flex;
+	vertical-align: middle;
+}
+
+.cu-capsule+.cu-capsule {
+	margin-left: 10upx;
+}
+
+.cu-capsule .cu-tag {
+	margin: 0;
+}
+
+.cu-capsule .cu-tag[class*="line-"]:last-child::after {
+	border-left: 0upx solid transparent;
+}
+
+.cu-capsule .cu-tag[class*="line-"]:first-child::after {
+	border-right: 0upx solid transparent;
+}
+
+.cu-capsule.radius .cu-tag:first-child {
+	border-top-left-radius: 6upx;
+	border-bottom-left-radius: 6upx;
+}
+
+.cu-capsule.radius .cu-tag:last-child::after,
+.cu-capsule.radius .cu-tag[class*="line-"] {
+	border-top-right-radius: 12upx;
+	border-bottom-right-radius: 12upx;
+}
+
+.cu-capsule.round .cu-tag:first-child {
+	border-top-left-radius: 200upx;
+	border-bottom-left-radius: 200upx;
+	text-indent: 4upx;
+}
+
+.cu-capsule.round .cu-tag:last-child::after,
+.cu-capsule.round .cu-tag:last-child {
+	border-top-right-radius: 200upx;
+	border-bottom-right-radius: 200upx;
+	text-indent: -4upx;
+}
+
+.cu-tag.badge {
+	border-radius: 200upx;
+	position: absolute;
+	top: -10upx;
+	right: -10upx;
+	font-size: 20upx;
+	padding: 0upx 10upx;
+	height: 28upx;
+	color: #ffffff;
+}
+
+.cu-tag.badge:not([class*="bg-"]) {
+	background-color: #dd514c;
+}
+
+.cu-tag:empty:not([class*="cuIcon-"]) {
+	padding: 0upx;
+	width: 16upx;
+	height: 16upx;
+	top: -4upx;
+	right: -4upx;
+}
+
+.cu-tag[class*="cuIcon-"] {
+	width: 32upx;
+	height: 32upx;
+	top: -4upx;
+	right: -4upx;
+}
+
+/* ==================
+          头像
+ ==================== */
+
+.cu-avatar {
+	font-variant: small-caps;
+	margin: 0;
+	padding: 0;
+	display: inline-flex;
+	text-align: center;
+	justify-content: center;
+	align-items: center;
+	background-color: #ccc;
+	color: #ffffff;
+	white-space: nowrap;
+	position: relative;
+	width: 64upx;
+	height: 64upx;
+	background-size: cover;
+	background-position: center;
+	vertical-align: middle;
+	font-size: 1.5em;
+}
+
+.cu-avatar.sm {
+	width: 48upx;
+	height: 48upx;
+	font-size: 1em;
+}
+
+.cu-avatar.lg {
+	width: 96upx;
+	height: 96upx;
+	font-size: 2em;
+}
+
+.cu-avatar.xl {
+	width: 128upx;
+	height: 128upx;
+	font-size: 2.5em;
+}
+
+.cu-avatar .avatar-text {
+	font-size: 0.4em;
+}
+
+.cu-avatar-group {
+	direction: rtl;
+	unicode-bidi: bidi-override;
+	padding: 0 10upx 0 40upx;
+	display: inline-block;
+}
+
+.cu-avatar-group .cu-avatar {
+	margin-left: -30upx;
+	border: 4upx solid #f1f1f1;
+	vertical-align: middle;
+}
+
+.cu-avatar-group .cu-avatar.sm {
+	margin-left: -20upx;
+	border: 1upx solid #f1f1f1;
+}
+
+/* ==================
+         进度条
+ ==================== */
+
+.cu-progress {
+	overflow: hidden;
+	height: 28upx;
+	background-color: #ebeef5;
+	display: inline-flex;
+	align-items: center;
+	width: 100%;
+}
+
+.cu-progress+view,
+.cu-progress+text {
+	line-height: 1;
+}
+
+.cu-progress.xs {
+	height: 10upx;
+}
+
+.cu-progress.sm {
+	height: 20upx;
+}
+
+.cu-progress view {
+	width: 0;
+	height: 100%;
+	align-items: center;
+	display: flex;
+	justify-items: flex-end;
+	justify-content: space-around;
+	font-size: 20upx;
+	color: #ffffff;
+	transition: width 0.6s ease;
+}
+
+.cu-progress text {
+	align-items: center;
+	display: flex;
+	font-size: 20upx;
+	color: #333333;
+	text-indent: 10upx;
+}
+
+.cu-progress.text-progress {
+	padding-right: 60upx;
+}
+
+.cu-progress.striped view {
+	background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+	background-size: 72upx 72upx;
+}
+
+.cu-progress.active view {
+	animation: progress-stripes 2s linear infinite;
+}
+
+@keyframes progress-stripes {
+	from {
+		background-position: 72upx 0;
+	}
+
+	to {
+		background-position: 0 0;
+	}
+}
+
+/* ==================
+          加载
+ ==================== */
+
+.cu-load {
+	display: block;
+	line-height: 3em;
+	text-align: center;
+}
+
+.cu-load::before {
+	font-family: "cuIcon";
+	display: inline-block;
+	margin-right: 6upx;
+}
+
+.cu-load.loading::before {
+	content: "\e67a";
+	animation: cuIcon-spin 2s infinite linear;
+}
+
+.cu-load.loading::after {
+	content: "加载中...";
+}
+
+.cu-load.over::before {
+	content: "\e64a";
+}
+
+.cu-load.over::after {
+	content: "没有更多了";
+}
+
+.cu-load.erro::before {
+	content: "\e658";
+}
+
+.cu-load.erro::after {
+	content: "加载失败";
+}
+
+.cu-load.load-cuIcon::before {
+	font-size: 32upx;
+}
+
+.cu-load.load-cuIcon::after {
+	display: none;
+}
+
+.cu-load.load-cuIcon.over {
+	display: none;
+}
+
+.cu-load.load-modal {
+	position: fixed;
+	top: 0;
+	right: 0;
+	bottom: 140upx;
+	left: 0;
+	margin: auto;
+	width: 260upx;
+	height: 260upx;
+	background-color: #ffffff;
+	border-radius: 10upx;
+	box-shadow: 0 0 0upx 2000upx rgba(0, 0, 0, 0.5);
+	display: flex;
+	align-items: center;
+	flex-direction: column;
+	justify-content: center;
+	font-size: 28upx;
+	z-index: 9999;
+	line-height: 2.4em;
+}
+
+.cu-load.load-modal [class*="cuIcon-"] {
+	font-size: 60upx;
+}
+
+.cu-load.load-modal image {
+	width: 70upx;
+	height: 70upx;
+}
+
+.cu-load.load-modal::after {
+	content: "";
+	position: absolute;
+	background-color: #ffffff;
+	border-radius: 50%;
+	width: 200upx;
+	height: 200upx;
+	font-size: 10px;
+	border-top: 6upx solid rgba(0, 0, 0, 0.05);
+	border-right: 6upx solid rgba(0, 0, 0, 0.05);
+	border-bottom: 6upx solid rgba(0, 0, 0, 0.05);
+	border-left: 6upx solid #f37b1d;
+	animation: cuIcon-spin 1s infinite linear;
+	z-index: -1;
+}
+
+.load-progress {
+	pointer-events: none;
+	top: 0;
+	position: fixed;
+	width: 100%;
+	left: 0;
+	z-index: 2000;
+}
+
+.load-progress.hide {
+	display: none;
+}
+
+.load-progress .load-progress-bar {
+	position: relative;
+	width: 100%;
+	height: 4upx;
+	overflow: hidden;
+	transition: all 200ms ease 0s;
+}
+
+.load-progress .load-progress-spinner {
+	position: absolute;
+	top: 10upx;
+	right: 10upx;
+	z-index: 2000;
+	display: block;
+}
+
+.load-progress .load-progress-spinner::after {
+	content: "";
+	display: block;
+	width: 24upx;
+	height: 24upx;
+	-webkit-box-sizing: border-box;
+	box-sizing: border-box;
+	border: solid 4upx transparent;
+	border-top-color: inherit;
+	border-left-color: inherit;
+	border-radius: 50%;
+	-webkit-animation: load-progress-spinner 0.4s linear infinite;
+	animation: load-progress-spinner 0.4s linear infinite;
+}
+
+@-webkit-keyframes load-progress-spinner {
+	0% {
+		-webkit-transform: rotate(0);
+		transform: rotate(0);
+	}
+
+	100% {
+		-webkit-transform: rotate(360deg);
+		transform: rotate(360deg);
+	}
+}
+
+@keyframes load-progress-spinner {
+	0% {
+		-webkit-transform: rotate(0);
+		transform: rotate(0);
+	}
+
+	100% {
+		-webkit-transform: rotate(360deg);
+		transform: rotate(360deg);
+	}
+}
+
+/* ==================
+          列表
+ ==================== */
+.grayscale {
+	filter: grayscale(1);
+}
+
+.cu-list+.cu-list {
+	margin-top: 30upx
+}
+
+.cu-list>.cu-item {
+	transition: all .6s ease-in-out 0s;
+	transform: translateX(0upx)
+}
+
+.cu-list>.cu-item.move-cur {
+	transform: translateX(-180upx)
+}
+
+.cu-list>.cu-item .move {
+	position: absolute;
+	right: 0;
+	display: flex;
+	width: 260upx;
+	height: 100%;
+	transform: translateX(100%)
+}
+
+.cu-list>.cu-item .move view {
+	display: flex;
+	flex: 1;
+	justify-content: center;
+	align-items: center
+}
+
+.cu-list.menu-avatar {
+	overflow: hidden;
+}
+
+.cu-list.menu-avatar>.cu-item {
+	position: relative;
+	display: flex;
+	padding-right: 10upx;
+	height: 140upx;
+	background-color: #ffffff;
+	justify-content: flex-end;
+	align-items: center
+}
+
+.cu-list.menu-avatar>.cu-item>.cu-avatar {
+	position: absolute;
+	left: 30upx
+}
+
+.cu-list.menu-avatar>.cu-item .flex .text-cut {
+	max-width: 510upx
+}
+
+.cu-list.menu-avatar>.cu-item .content {
+	position: absolute;
+	left: 146upx;
+	width: calc(100% - 96upx - 60upx - 120upx - 20upx);
+	line-height: 1.6em;
+}
+
+.cu-list.menu-avatar>.cu-item .content.flex-sub {
+	width: calc(100% - 96upx - 60upx - 20upx);
+}
+
+.cu-list.menu-avatar>.cu-item .content>view:first-child {
+	font-size: 30upx;
+	display: flex;
+	align-items: center
+}
+
+.cu-list.menu-avatar>.cu-item .content .cu-tag.sm {
+	display: inline-block;
+	margin-left: 10upx;
+	height: 28upx;
+	font-size: 16upx;
+	line-height: 32upx
+}
+
+.cu-list.menu-avatar>.cu-item .action {
+	width: 100upx;
+	text-align: center
+}
+
+.cu-list.menu-avatar>.cu-item .action view+view {
+	margin-top: 10upx
+}
+
+.cu-list.menu-avatar.comment>.cu-item .content {
+	position: relative;
+	left: 0;
+	width: auto;
+	flex: 1;
+}
+
+.cu-list.menu-avatar.comment>.cu-item {
+	padding: 30upx 30upx 30upx 120upx;
+	height: auto
+}
+
+.cu-list.menu-avatar.comment .cu-avatar {
+	align-self: flex-start
+}
+
+.cu-list.menu>.cu-item {
+	position: relative;
+	display: flex;
+	padding: 0 30upx;
+	min-height: 100upx;
+	background-color: #ffffff;
+	justify-content: space-between;
+	align-items: center
+}
+
+.cu-list.menu>.cu-item:last-child:after {
+	border: none
+}
+
+.cu-list.menu-avatar>.cu-item:after,
+.cu-list.menu>.cu-item:after {
+	position: absolute;
+	top: 0;
+	left: 0;
+	box-sizing: border-box;
+	width: 200%;
+	height: 200%;
+	border-bottom: 1upx solid #ddd;
+	border-radius: inherit;
+	content: " ";
+	transform: scale(.5);
+	transform-origin: 0 0;
+	pointer-events: none
+}
+
+.cu-list.menu>.cu-item.grayscale {
+	background-color: #f5f5f5
+}
+
+.cu-list.menu>.cu-item.cur {
+	background-color: #fcf7e9
+}
+
+.cu-list.menu>.cu-item.arrow {
+	padding-right: 90upx
+}
+
+.cu-list.menu>.cu-item.arrow:before {
+	position: absolute;
+	top: 0;
+	right: 30upx;
+	bottom: 0;
+	display: block;
+	margin: auto;
+	width: 30upx;
+	height: 30upx;
+	color: #8799a3;
+	content: "\e6a3";
+	text-align: center;
+	font-size: 34upx;
+	font-family: cuIcon;
+	line-height: 30upx
+}
+
+.cu-list.menu>.cu-item button.content {
+	padding: 0;
+	background-color: transparent;
+	justify-content: flex-start
+}
+
+.cu-list.menu>.cu-item button.content:after {
+	display: none
+}
+
+.cu-list.menu>.cu-item .cu-avatar-group .cu-avatar {
+	border-color: #ffffff
+}
+
+.cu-list.menu>.cu-item .content>view:first-child {
+	display: flex;
+	align-items: center
+}
+
+.cu-list.menu>.cu-item .content>text[class*=cuIcon] {
+	display: inline-block;
+	margin-right: 10upx;
+	width: 1.6em;
+	text-align: center
+}
+
+.cu-list.menu>.cu-item .content>image {
+	display: inline-block;
+	margin-right: 10upx;
+	width: 1.6em;
+	height: 1.6em;
+	vertical-align: middle
+}
+
+.cu-list.menu>.cu-item .content {
+	font-size: 30upx;
+	line-height: 1.6em;
+	flex: 1
+}
+
+.cu-list.menu>.cu-item .content .cu-tag.sm {
+	display: inline-block;
+	margin-left: 10upx;
+	height: 28upx;
+	font-size: 16upx;
+	line-height: 32upx
+}
+
+.cu-list.menu>.cu-item .action .cu-tag:empty {
+	right: 10upx
+}
+
+.cu-list.menu {
+	display: block;
+	overflow: hidden
+}
+
+.cu-list.menu.sm-border>.cu-item:after {
+	left: 30upx;
+	width: calc(200% - 120upx)
+}
+
+.cu-list.grid>.cu-item {
+	position: relative;
+	display: flex;
+	padding: 20upx 0 30upx;
+	transition-duration: 0s;
+	flex-direction: column
+}
+
+.cu-list.grid>.cu-item:after {
+	position: absolute;
+	top: 0;
+	left: 0;
+	box-sizing: border-box;
+	width: 200%;
+	height: 200%;
+	border-right: 1px solid rgba(0, 0, 0, .1);
+	border-bottom: 1px solid rgba(0, 0, 0, .1);
+	border-radius: inherit;
+	content: " ";
+	transform: scale(.5);
+	transform-origin: 0 0;
+	pointer-events: none
+}
+
+.cu-list.grid>.cu-item text {
+	display: block;
+	margin-top: 10upx;
+	color: #888;
+	font-size: 26upx;
+	line-height: 40upx
+}
+
+.cu-list.grid>.cu-item [class*=cuIcon] {
+	position: relative;
+	display: block;
+	margin-top: 20upx;
+	width: 100%;
+	font-size: 48upx
+}
+
+.cu-list.grid>.cu-item .cu-tag {
+	right: auto;
+	left: 50%;
+	margin-left: 20upx
+}
+
+.cu-list.grid {
+	background-color: #ffffff;
+	text-align: center
+}
+
+.cu-list.grid.no-border>.cu-item {
+	padding-top: 10upx;
+	padding-bottom: 20upx
+}
+
+.cu-list.grid.no-border>.cu-item:after {
+	border: none
+}
+
+.cu-list.grid.no-border {
+	padding: 20upx 10upx
+}
+
+.cu-list.grid.col-3>.cu-item:nth-child(3n):after,
+.cu-list.grid.col-4>.cu-item:nth-child(4n):after,
+.cu-list.grid.col-5>.cu-item:nth-child(5n):after {
+	border-right-width: 0
+}
+
+.cu-list.card-menu {
+	overflow: hidden;
+	margin-right: 30upx;
+	margin-left: 30upx;
+	border-radius: 20upx
+}
+
+
+/* ==================
+          操作条
+ ==================== */
+
+.cu-bar {
+	display: flex;
+	position: relative;
+	align-items: center;
+	min-height: 100upx;
+	justify-content: space-between;
+}
+
+.cu-bar .action {
+	display: flex;
+	align-items: center;
+	height: 100%;
+	justify-content: center;
+	max-width: 100%;
+}
+
+.cu-bar .action.border-title {
+	position: relative;
+	top: -10upx;
+}
+
+.cu-bar .action.border-title text[class*="bg-"]:last-child {
+	position: absolute;
+	bottom: -0.5rem;
+	min-width: 2rem;
+	height: 6upx;
+	left: 0;
+}
+
+.cu-bar .action.sub-title {
+	position: relative;
+	top: -0.2rem;
+}
+
+.cu-bar .action.sub-title text {
+	position: relative;
+	z-index: 1;
+}
+
+.cu-bar .action.sub-title text[class*="bg-"]:last-child {
+	position: absolute;
+	display: inline-block;
+	bottom: -0.2rem;
+	border-radius: 6upx;
+	width: 100%;
+	height: 0.6rem;
+	left: 0.6rem;
+	opacity: 0.3;
+	z-index: 0;
+}
+
+.cu-bar .action.sub-title text[class*="text-"]:last-child {
+	position: absolute;
+	display: inline-block;
+	bottom: -0.7rem;
+	left: 0.5rem;
+	opacity: 0.2;
+	z-index: 0;
+	text-align: right;
+	font-weight: 900;
+	font-size: 36upx;
+}
+
+.cu-bar.justify-center .action.border-title text:last-child,
+.cu-bar.justify-center .action.sub-title text:last-child {
+	left: 0;
+	right: 0;
+	margin: auto;
+	text-align: center;
+}
+
+.cu-bar .action:first-child {
+	margin-left: 30upx;
+	font-size: 30upx;
+}
+
+.cu-bar .action text.text-cut {
+	text-align: left;
+	width: 100%;
+}
+
+.cu-bar .cu-avatar:first-child {
+	margin-left: 20upx;
+}
+
+.cu-bar .action:first-child>text[class*="cuIcon-"] {
+	margin-left: -0.3em;
+	margin-right: 0.3em;
+}
+
+.cu-bar .action:last-child {
+	margin-right: 30upx;
+}
+
+.cu-bar .action>text[class*="cuIcon-"],
+.cu-bar .action>view[class*="cuIcon-"] {
+	font-size: 36upx;
+}
+
+.cu-bar .action>text[class*="cuIcon-"]+text[class*="cuIcon-"] {
+	margin-left: 0.5em;
+}
+
+.cu-bar .content {
+	position: absolute;
+	text-align: center;
+	width: calc(100% - 340upx);
+	left: 0;
+	right: 0;
+	bottom: 0;
+	top: 0;
+	margin: auto;
+	height: 60upx;
+	font-size: 32upx;
+	line-height: 60upx;
+	cursor: none;
+	pointer-events: none;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+}
+
+.cu-bar.ios .content {
+	bottom: 7px;
+	height: 30px;
+	font-size: 32upx;
+	line-height: 30px;
+}
+
+.cu-bar.btn-group {
+	justify-content: space-around;
+}
+
+.cu-bar.btn-group button {
+	padding: 20upx 32upx;
+}
+
+.cu-bar.btn-group button {
+	flex: 1;
+	margin: 0 20upx;
+	max-width: 50%;
+}
+
+.cu-bar .search-form {
+	background-color: #f5f5f5;
+	line-height: 64upx;
+	height: 64upx;
+	font-size: 24upx;
+	color: #333333;
+	flex: 1;
+	display: flex;
+	align-items: center;
+	margin: 0 30upx;
+}
+
+.cu-bar .search-form+.action {
+	margin-right: 30upx;
+}
+
+.cu-bar .search-form input {
+	flex: 1;
+	padding-right: 30upx;
+	height: 64upx;
+	line-height: 64upx;
+	font-size: 26upx;
+	background-color: transparent;
+}
+
+.cu-bar .search-form [class*="cuIcon-"] {
+	margin: 0 0.5em 0 0.8em;
+}
+
+.cu-bar .search-form [class*="cuIcon-"]::before {
+	top: 0upx;
+}
+
+.cu-bar.fixed,
+.nav.fixed {
+	position: fixed;
+	width: 100%;
+	top: 0;
+	z-index: 1024;
+	box-shadow: 0 1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.foot {
+	position: fixed;
+	width: 100%;
+	bottom: 0;
+	z-index: 1024;
+	box-shadow: 0 -1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.tabbar {
+	padding: 0;
+	height: calc(100upx + env(safe-area-inset-bottom) / 2);
+	padding-bottom: calc(env(safe-area-inset-bottom) / 2);
+}
+
+.cu-tabbar-height {
+	min-height: 100upx;
+	height: calc(100upx + env(safe-area-inset-bottom) / 2);
+}
+
+.cu-bar.tabbar.shadow {
+	box-shadow: 0 -1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.tabbar .action {
+	font-size: 22upx;
+	position: relative;
+	flex: 1;
+	text-align: center;
+	padding: 0;
+	display: block;
+	height: auto;
+	line-height: 1;
+	margin: 0;
+	background-color: inherit;
+	overflow: initial;
+}
+
+.cu-bar.tabbar.shop .action {
+	width: 140upx;
+	flex: initial;
+}
+
+.cu-bar.tabbar .action.add-action {
+	position: relative;
+	z-index: 2;
+	padding-top: 52upx;
+}
+
+.cu-bar.tabbar .action.add-action [class*="cuIcon-"] {
+	position: absolute;
+	width: 70upx;
+	z-index: 2;
+	height: 70upx;
+	border-radius: 50%;
+	line-height: 70upx;
+	font-size: 50upx;
+	top: -35upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	padding: 0;
+}
+
+.cu-bar.tabbar .action.add-action::after {
+	content: "";
+	position: absolute;
+	width: 100upx;
+	height: 100upx;
+	top: -52upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	box-shadow: 0 -3upx 8upx rgba(0, 0, 0, 0.08);
+	border-radius: 50upx;
+	background-color: inherit;
+	z-index: 0;
+}
+
+.cu-bar.tabbar .action.add-action::before {
+	content: "";
+	position: absolute;
+	width: 100upx;
+	height: 30upx;
+	bottom: 30upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	background-color: inherit;
+	z-index: 1;
+}
+
+.cu-bar.tabbar .btn-group {
+	flex: 1;
+	display: flex;
+	justify-content: space-around;
+	align-items: center;
+	padding: 0 10upx;
+}
+
+.cu-bar.tabbar button.action::after {
+	border: 0;
+}
+
+.cu-bar.tabbar .action [class*="cuIcon-"] {
+	width: 100upx;
+	position: relative;
+	display: block;
+	height: auto;
+	margin: 0 auto 10upx;
+	text-align: center;
+	font-size: 40upx;
+}
+
+.cu-bar.tabbar .action .cuIcon-cu-image {
+	margin: 0 auto;
+}
+
+.cu-bar.tabbar .action .cuIcon-cu-image image {
+	width: 50upx;
+	height: 50upx;
+	display: inline-block;
+}
+
+.cu-bar.tabbar .submit {
+	align-items: center;
+	display: flex;
+	justify-content: center;
+	text-align: center;
+	position: relative;
+	flex: 2;
+	align-self: stretch;
+}
+
+.cu-bar.tabbar .submit:last-child {
+	flex: 2.6;
+}
+
+.cu-bar.tabbar .submit+.submit {
+	flex: 2;
+}
+
+.cu-bar.tabbar.border .action::before {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	border-right: 1upx solid rgba(0, 0, 0, 0.1);
+	z-index: 3;
+}
+
+.cu-bar.tabbar.border .action:last-child:before {
+	display: none;
+}
+
+.cu-bar.input {
+	padding-right: 20upx;
+	background-color: #ffffff;
+}
+
+.cu-bar.input input {
+	overflow: initial;
+	line-height: 64upx;
+	height: 64upx;
+	min-height: 64upx;
+	flex: 1;
+	font-size: 30upx;
+	margin: 0 20upx;
+}
+
+.cu-bar.input .action {
+	margin-left: 20upx;
+}
+
+.cu-bar.input .action [class*="cuIcon-"] {
+	font-size: 48upx;
+}
+
+.cu-bar.input input+.action {
+	margin-right: 20upx;
+	margin-left: 0upx;
+}
+
+.cu-bar.input .action:first-child [class*="cuIcon-"] {
+	margin-left: 0upx;
+}
+
+.cu-custom {
+	display: block;
+	position: relative;
+}
+
+.cu-custom .cu-bar .content {
+	width: calc(100% - 440upx);
+}
+
+/* #ifdef MP-ALIPAY */
+.cu-custom .cu-bar .action .cuIcon-back {
+	opacity: 0;
+}
+
+/* #endif */
+
+.cu-custom .cu-bar .content image {
+	height: 60upx;
+	width: 240upx;
+}
+
+.cu-custom .cu-bar {
+	min-height: 0px;
+	/* #ifdef MP-WEIXIN */
+	padding-right: 220upx;
+	/* #endif */
+	/* #ifdef MP-ALIPAY */
+	padding-right: 150upx;
+	/* #endif */
+	box-shadow: 0upx 0upx 0upx;
+	z-index: 9999;
+}
+
+.cu-custom .cu-bar .border-custom {
+	position: relative;
+	background: rgba(0, 0, 0, 0.15);
+	border-radius: 1000upx;
+	height: 30px;
+}
+
+.cu-custom .cu-bar .border-custom::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border-radius: inherit;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+	border: 1upx solid #ffffff;
+	opacity: 0.5;
+}
+
+.cu-custom .cu-bar .border-custom::before {
+	content: " ";
+	width: 1upx;
+	height: 110%;
+	position: absolute;
+	top: 22.5%;
+	left: 0;
+	right: 0;
+	margin: auto;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+	opacity: 0.6;
+	background-color: #ffffff;
+}
+
+.cu-custom .cu-bar .border-custom text {
+	display: block;
+	flex: 1;
+	margin: auto !important;
+	text-align: center;
+	font-size: 34upx;
+}
+
+/* ==================
+         导航栏
+ ==================== */
+
+.nav {
+	white-space: nowrap;
+}
+
+::-webkit-scrollbar {
+	display: none;
+}
+
+.nav .cu-item {
+	height: 80upx;
+	display: inline-block;
+	line-height: 80upx;
+/* 	margin: 0 10upx;
+	padding: 0 20upx; */
+}
+
+.nav .cu-item.cur {
+	border-bottom: 4upx solid;
+}
+
+/* ==================
+         时间轴
+ ==================== */
+
+.cu-timeline {
+	display: block;
+	background-color: #ffffff;
+}
+
+.cu-timeline .cu-time {
+	width: 120upx;
+	text-align: center;
+	padding: 20upx 0;
+	font-size: 26upx;
+	color: #888;
+	display: block;
+}
+
+.cu-timeline>.cu-item {
+	padding: 30upx 30upx 30upx 120upx;
+	position: relative;
+	display: block;
+	z-index: 0;
+}
+
+.cu-timeline>.cu-item:not([class*="text-"]) {
+	color: #ccc;
+}
+
+.cu-timeline>.cu-item::after {
+	content: "";
+	display: block;
+	position: absolute;
+	width: 1upx;
+	background-color: #ddd;
+	left: 60upx;
+	height: 100%;
+	top: 0;
+	z-index: 8;
+}
+
+.cu-timeline>.cu-item::before {
+	font-family: "cuIcon";
+	display: block;
+	position: absolute;
+	top: 36upx;
+	z-index: 9;
+	background-color: #ffffff;
+	width: 50upx;
+	height: 50upx;
+	text-align: center;
+	border: none;
+	line-height: 50upx;
+	left: 36upx;
+}
+
+.cu-timeline>.cu-item:not([class*="cuIcon-"])::before {
+	content: "\e763";
+}
+
+.cu-timeline>.cu-item[class*="cuIcon-"]::before {
+	background-color: #ffffff;
+	width: 50upx;
+	height: 50upx;
+	text-align: center;
+	border: none;
+	line-height: 50upx;
+	left: 36upx;
+}
+
+.cu-timeline>.cu-item>.content {
+	padding: 30upx;
+	border-radius: 6upx;
+	display: block;
+	line-height: 1.6;
+}
+
+.cu-timeline>.cu-item>.content:not([class*="bg-"]) {
+	background-color: #f1f1f1;
+	color: #333333;
+}
+
+.cu-timeline>.cu-item>.content+.content {
+	margin-top: 20upx;
+}
+
+/* ==================
+         聊天
+ ==================== */
+
+.cu-chat {
+	display: flex;
+	flex-direction: column;
+}
+
+.cu-chat .cu-item {
+	display: flex;
+	padding: 30upx 30upx 70upx;
+	position: relative;
+}
+
+.cu-chat .cu-item>.cu-avatar {
+	width: 80upx;
+	height: 80upx;
+}
+
+.cu-chat .cu-item>.main {
+	max-width: calc(100% - 260upx);
+	margin: 0 40upx;
+	display: flex;
+	align-items: center;
+}
+
+.cu-chat .cu-item>image {
+	height: 320upx;
+}
+
+.cu-chat .cu-item>.main .content {
+	padding: 20upx;
+	border-radius: 6upx;
+	display: inline-flex;
+	max-width: 100%;
+	align-items: center;
+	font-size: 30upx;
+	position: relative;
+	min-height: 80upx;
+	line-height: 40upx;
+	text-align: left;
+}
+
+.cu-chat .cu-item>.main .content:not([class*="bg-"]) {
+	background-color: #ffffff;
+	color: #333333;
+}
+
+.cu-chat .cu-item .date {
+	position: absolute;
+	font-size: 24upx;
+	color: #8799a3;
+	width: calc(100% - 320upx);
+	bottom: 20upx;
+	left: 160upx;
+}
+
+.cu-chat .cu-item .action {
+	padding: 0 30upx;
+	display: flex;
+	align-items: center;
+}
+
+.cu-chat .cu-item>.main .content::after {
+	content: "";
+	top: 27upx;
+	transform: rotate(45deg);
+	position: absolute;
+	z-index: 100;
+	display: inline-block;
+	overflow: hidden;
+	width: 24upx;
+	height: 24upx;
+	left: -12upx;
+	right: initial;
+	background-color: inherit;
+}
+
+.cu-chat .cu-item.self>.main .content::after {
+	left: auto;
+	right: -12upx;
+}
+
+.cu-chat .cu-item>.main .content::before {
+	content: "";
+	top: 30upx;
+	transform: rotate(45deg);
+	position: absolute;
+	z-index: -1;
+	display: inline-block;
+	overflow: hidden;
+	width: 24upx;
+	height: 24upx;
+	left: -12upx;
+	right: initial;
+	background-color: inherit;
+	filter: blur(5upx);
+	opacity: 0.3;
+}
+
+.cu-chat .cu-item>.main .content:not([class*="bg-"])::before {
+	background-color: #333333;
+	opacity: 0.1;
+}
+
+.cu-chat .cu-item.self>.main .content::before {
+	left: auto;
+	right: -12upx;
+}
+
+.cu-chat .cu-item.self {
+	justify-content: flex-end;
+	text-align: right;
+}
+
+.cu-chat .cu-info {
+	display: inline-block;
+	margin: 20upx auto;
+	font-size: 24upx;
+	padding: 8upx 12upx;
+	background-color: rgba(0, 0, 0, 0.2);
+	border-radius: 6upx;
+	color: #ffffff;
+	max-width: 400upx;
+	line-height: 1.4;
+}
+
+/* ==================
+         卡片
+ ==================== */
+
+.cu-card {
+	display: block;
+	overflow: hidden;
+}
+
+.cu-card>.cu-item {
+	display: block;
+	background-color: #ffffff;
+	overflow: hidden;
+	border-radius: 10upx;
+	margin: 30upx;
+}
+
+.cu-card>.cu-item.shadow-blur {
+	overflow: initial;
+}
+
+.cu-card.no-card>.cu-item {
+	margin: 0upx;
+	border-radius: 0upx;
+}
+
+.cu-card .grid.grid-square {
+	margin-bottom: -20upx;
+}
+
+.cu-card.case .image {
+	position: relative;
+}
+
+.cu-card.case .image image {
+	width: 100%;
+}
+
+.cu-card.case .image .cu-tag {
+	position: absolute;
+	right: 0;
+	top: 0;
+}
+
+.cu-card.case .image .cu-bar {
+	position: absolute;
+	bottom: 0;
+	width: 100%;
+	background-color: transparent;
+	padding: 0upx 30upx;
+}
+
+.cu-card.case.no-card .image {
+	margin: 30upx 30upx 0;
+	overflow: hidden;
+	border-radius: 10upx;
+}
+
+.cu-card.dynamic {
+	display: block;
+}
+
+.cu-card.dynamic>.cu-item {
+	display: block;
+	background-color: #ffffff;
+	overflow: hidden;
+}
+
+.cu-card.dynamic>.cu-item>.text-content {
+	padding: 0 30upx 0;
+	max-height: 6.4em;
+	overflow: hidden;
+	font-size: 24upx;
+	margin-bottom: 20upx;
+}
+
+.cu-card.dynamic>.cu-item .square-img {
+	width: 100%;
+	height: 200upx;
+	border-radius: 6upx;
+}
+
+.cu-card.dynamic>.cu-item .only-img {
+	width: 100%;
+	height: 320upx;
+	border-radius: 6upx;
+}
+
+/* card.dynamic>.cu-item .comment {
+  padding: 20upx;
+  background-color: #f1f1f1;
+  margin: 0 30upx 30upx;
+  border-radius: 6upx;
+} */
+
+.cu-card.article {
+	display: block;
+}
+
+.cu-card.article>.cu-item {
+	/* padding-bottom: 30upx; */
+}
+
+.cu-card.article>.cu-item .title {
+	font-size: 30upx;
+	font-weight: 900;
+	color: #333333;
+	line-height: 80upx;
+	/* padding: 0 30upx; */
+}
+
+.cu-card.article>.cu-item .content {
+	display: flex;
+	/* padding: 0 30upx; */
+}
+
+.cu-card.article>.cu-item .content>image {
+	width: 240upx;
+	height: 6.4em;
+	margin-right: 20upx;
+	margin-bottom: 20upx;
+	border-radius: 6upx;
+}
+
+.cu-card.article>.cu-item .content .desc {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+
+.cu-card.article>.cu-item .content .text-content {
+	font-size: 24upx;
+	color: #888;
+	height: 4.8em;
+	overflow: hidden;
+}
+
+/* ==================
+         表单
+ ==================== */
+
+.cu-form-group {
+	background-color: #ffffff;
+	padding: 1upx 30upx;
+	display: flex;
+	align-items: center;
+	min-height: 80upx;
+	justify-content: space-between;
+	font-size: 24upx;
+}
+
+.cu-form-group+.cu-form-group {
+	border-top: 1upx solid #eee;
+}
+
+.cu-form-group .title {
+	text-align: justify;
+	padding-right: 30upx;
+	font-size: 24upx;
+	position: relative;
+	height: 60upx;
+	line-height: 60upx;
+	/* width: 160upx; */
+}
+
+.cu-form-group input {
+	flex: 1;
+	font-size: 24upx;
+	color: #555;
+	padding-right: 20upx;
+}
+
+.cu-form-group>text[class*="cuIcon-"] {
+	font-size: 36upx;
+	padding: 0;
+	box-sizing: border-box;
+}
+
+.cu-form-group textarea {
+	margin: 32upx 0 30upx;
+	height: 4.6em;
+	width: 100%;
+	line-height: 1.2em;
+	flex: 1;
+	font-size: 28upx;
+	padding: 0;
+}
+
+.cu-form-group.align-start .title {
+	height: 1em;
+	margin-top: 32upx;
+	line-height: 1em;
+}
+
+.cu-form-group picker {
+	flex: 1;
+	padding-right: 40upx;
+	overflow: hidden;
+	position: relative;
+}
+
+.cu-form-group picker .picker {
+	line-height: 80upx;
+	font-size: 24upx;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+	width: 100%;
+	text-align: right;
+}
+
+.cu-form-group picker::after {
+	font-family: cuIcon;
+	display: block;
+	content: "\e6a3";
+	position: absolute;
+	font-size: 34upx;
+	color: #8799a3;
+	line-height: 80upx;
+	width: 60upx;
+	text-align: center;
+	top: 0;
+	bottom: 0;
+	right: -20upx;
+	margin: auto;
+}
+
+.cu-form-group textarea[disabled],
+.cu-form-group textarea[disabled] .placeholder {
+	color: transparent;
+}
+
+/* ==================
+         模态窗口
+ ==================== */
+
+.cu-modal {
+	position: fixed;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	z-index: 1110;
+	opacity: 0;
+	outline: 0;
+	text-align: center;
+	-ms-transform: scale(1.185);
+	transform: scale(1.185);
+	backface-visibility: hidden;
+	perspective: 2000upx;
+	background: rgba(0, 0, 0, 0.6);
+	transition: all 0.3s ease-in-out 0s;
+	pointer-events: none;
+}
+
+.cu-modal::before {
+	content: "\200B";
+	display: inline-block;
+	height: 100%;
+	vertical-align: middle;
+}
+
+.cu-modal.show {
+	opacity: 1;
+	transition-duration: 0.3s;
+	-ms-transform: scale(1);
+	transform: scale(1);
+	overflow-x: hidden;
+	overflow-y: auto;
+	pointer-events: auto;
+}
+
+.cu-dialog {
+	position: relative;
+	display: inline-block;
+	vertical-align: middle;
+	margin-left: auto;
+	margin-right: auto;
+	width: 680upx;
+	max-width: 100%;
+	background-color: #f8f8f8;
+	border-radius: 10upx;
+	overflow: hidden;
+}
+
+.cu-modal.bottom-modal::before {
+	vertical-align: bottom;
+}
+
+.cu-modal.bottom-modal .cu-dialog {
+	width: 100%;
+	border-radius: 0;
+}
+
+.cu-modal.bottom-modal {
+	margin-bottom: -1000upx;
+}
+
+.cu-modal.bottom-modal.show {
+	margin-bottom: 0;
+}
+
+.cu-modal.drawer-modal {
+	transform: scale(1);
+	display: flex;
+}
+
+.cu-modal.drawer-modal .cu-dialog {
+	height: 100%;
+	min-width: 200upx;
+	border-radius: 0;
+	margin: initial;
+	transition-duration: 0.3s;
+}
+
+.cu-modal.drawer-modal.justify-start .cu-dialog {
+	transform: translateX(-100%);
+}
+
+.cu-modal.drawer-modal.justify-end .cu-dialog {
+	transform: translateX(100%);
+}
+
+.cu-modal.drawer-modal.show .cu-dialog {
+	transform: translateX(0%);
+}
+.cu-modal .cu-dialog>.cu-bar:first-child .action{
+  min-width: 100rpx;
+  margin-right: 0;
+  min-height: 100rpx;
+}
+/* ==================
+         轮播
+ ==================== */
+swiper .a-swiper-dot {
+	display: inline-block;
+	width: 16upx;
+	height: 16upx;
+	background: rgba(0, 0, 0, .3);
+	border-radius: 50%;
+	vertical-align: middle;
+}
+
+swiper[class*="-dot"] .wx-swiper-dots,
+swiper[class*="-dot"] .a-swiper-dots,
+swiper[class*="-dot"] .uni-swiper-dots {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	justify-content: center;
+}
+
+swiper.square-dot .wx-swiper-dot,
+swiper.square-dot .a-swiper-dot,
+swiper.square-dot .uni-swiper-dot {
+	background-color: #ffffff;
+	opacity: 0.4;
+	width: 10upx;
+	height: 10upx;
+	border-radius: 20upx;
+	margin: 0 8upx !important;
+}
+
+swiper.square-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.square-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.square-dot .uni-swiper-dot.uni-swiper-dot-active {
+	opacity: 1;
+	width: 30upx;
+}
+
+swiper.round-dot .wx-swiper-dot,
+swiper.round-dot .a-swiper-dot,
+swiper.round-dot .uni-swiper-dot {
+	width: 10upx;
+	height: 10upx;
+	position: relative;
+	margin: 4upx 8upx !important;
+}
+
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active::after,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active::after,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active::after {
+	content: "";
+	position: absolute;
+	width: 10upx;
+	height: 10upx;
+	top: 0upx;
+	left: 0upx;
+	right: 0;
+	bottom: 0;
+	margin: auto;
+	background-color: #ffffff;
+	border-radius: 20upx;
+}
+
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active {
+	width: 18upx;
+	height: 18upx;
+}
+
+.screen-swiper {
+	min-height: 375upx;
+}
+
+.screen-swiper image,
+.screen-swiper video,
+.swiper-item image,
+.swiper-item video {
+	width: 100%;
+	display: block;
+	height: 100%;
+	margin: 0;
+	pointer-events: none;
+}
+
+.card-swiper {
+	height: 420upx !important;
+}
+
+.card-swiper swiper-item {
+	width: 610upx !important;
+	left: 70upx;
+	box-sizing: border-box;
+	padding: 40upx 0upx 70upx;
+	overflow: initial;
+}
+
+.card-swiper swiper-item .swiper-item {
+	width: 100%;
+	display: block;
+	height: 100%;
+	border-radius: 10upx;
+	transform: scale(0.9);
+	transition: all 0.2s ease-in 0s;
+	overflow: hidden;
+}
+
+.card-swiper swiper-item.cur .swiper-item {
+	transform: none;
+	transition: all 0.2s ease-in 0s;
+}
+
+
+.tower-swiper {
+	height: 420upx;
+	position: relative;
+	max-width: 750upx;
+	overflow: hidden;
+}
+
+.tower-swiper .tower-item {
+	position: absolute;
+	width: 300upx;
+	height: 380upx;
+	top: 0;
+	bottom: 0;
+	left: 50%;
+	margin: auto;
+	transition: all 0.2s ease-in 0s;
+	opacity: 1;
+}
+
+.tower-swiper .tower-item.none {
+	opacity: 0;
+}
+
+.tower-swiper .tower-item .swiper-item {
+	width: 100%;
+	height: 100%;
+	border-radius: 6upx;
+	overflow: hidden;
+}
+
+/* ==================
+          步骤条
+ ==================== */
+
+.cu-steps {
+	display: flex;
+}
+
+scroll-view.cu-steps {
+	display: block;
+	white-space: nowrap;
+}
+
+scroll-view.cu-steps .cu-item {
+	display: inline-block;
+}
+
+.cu-steps .cu-item {
+	flex: 1;
+	text-align: center;
+	position: relative;
+	min-width: 100upx;
+}
+
+.cu-steps .cu-item:not([class*="text-"]) {
+	color: #8799a3;
+}
+
+.cu-steps .cu-item [class*="cuIcon-"],
+.cu-steps .cu-item .num {
+	display: block;
+	font-size: 40upx;
+	line-height: 80upx;
+}
+
+.cu-steps .cu-item::before,
+.cu-steps .cu-item::after,
+.cu-steps.steps-arrow .cu-item::before,
+.cu-steps.steps-arrow .cu-item::after {
+	content: "";
+	display: block;
+	position: absolute;
+	height: 0px;
+	width: calc(100% - 80upx);
+	border-bottom: 1px solid #ccc;
+	left: calc(0px - (100% - 80upx) / 2);
+	top: 40upx;
+	z-index: 0;
+}
+
+.cu-steps.steps-arrow .cu-item::before,
+.cu-steps.steps-arrow .cu-item::after {
+	content: "\e6a3";
+	font-family: 'cuIcon';
+	height: 30upx;
+	border-bottom-width: 0px;
+	line-height: 30upx;
+	top: 0;
+	bottom: 0;
+	margin: auto;
+	color: #ccc;
+}
+
+.cu-steps.steps-bottom .cu-item::before,
+.cu-steps.steps-bottom .cu-item::after {
+	bottom: 40upx;
+	top: initial;
+}
+
+.cu-steps .cu-item::after {
+	border-bottom: 1px solid currentColor;
+	width: 0px;
+	transition: all 0.3s ease-in-out 0s;
+}
+
+.cu-steps .cu-item[class*="text-"]::after {
+	width: calc(100% - 80upx);
+	color: currentColor;
+}
+
+.cu-steps .cu-item:first-child::before,
+.cu-steps .cu-item:first-child::after {
+	display: none;
+}
+
+.cu-steps .cu-item .num {
+	width: 40upx;
+	height: 40upx;
+	border-radius: 50%;
+	line-height: 40upx;
+	margin: 20upx auto;
+	font-size: 24upx;
+	border: 1px solid currentColor;
+	position: relative;
+	overflow: hidden;
+}
+
+.cu-steps .cu-item[class*="text-"] .num {
+	background-color: currentColor;
+}
+
+.cu-steps .cu-item .num::before,
+.cu-steps .cu-item .num::after {
+	content: attr(data-index);
+	position: absolute;
+	left: 0;
+	right: 0;
+	top: 0;
+	bottom: 0;
+	margin: auto;
+	transition: all 0.3s ease-in-out 0s;
+	transform: translateY(0upx);
+}
+
+.cu-steps .cu-item[class*="text-"] .num::before {
+	transform: translateY(-40upx);
+	color: #ffffff;
+}
+
+.cu-steps .cu-item .num::after {
+	transform: translateY(40upx);
+	color: #ffffff;
+	transition: all 0.3s ease-in-out 0s;
+}
+
+.cu-steps .cu-item[class*="text-"] .num::after {
+	content: "\e645";
+	font-family: 'cuIcon';
+	color: #ffffff;
+	transform: translateY(0upx);
+}
+
+.cu-steps .cu-item[class*="text-"] .num.err::after {
+	content: "\e646";
+}
+
+/* ==================
+          布局
+ ==================== */
+
+/*  -- flex弹性布局 -- */
+
+.flex {
+	display: flex;
+}
+
+.basis-xs {
+	flex-basis: 20%;
+}
+
+.basis-sm {
+	flex-basis: 40%;
+}
+
+.basis-df {
+	flex-basis: 50%;
+}
+
+.basis-lg {
+	flex-basis: 60%;
+}
+
+.basis-xl {
+	flex-basis: 80%;
+}
+
+.flex-sub {
+	flex: 1;
+}
+
+.flex-twice {
+	flex: 2;
+}
+
+.flex-treble {
+	flex: 3;
+}
+
+.flex-direction {
+	flex-direction: column;
+}
+
+.flex-wrap {
+	flex-wrap: wrap;
+}
+
+.align-start {
+	align-items: flex-start;
+}
+
+.align-end {
+	align-items: flex-end;
+}
+
+.align-center {
+	align-items: center;
+}
+
+.align-stretch {
+	align-items: stretch;
+}
+
+.self-start {
+	align-self: flex-start;
+}
+
+.self-center {
+	align-self: flex-center;
+}
+
+.self-end {
+	align-self: flex-end;
+}
+
+.self-stretch {
+	align-self: stretch;
+}
+
+.align-stretch {
+	align-items: stretch;
+}
+
+.justify-start {
+	justify-content: flex-start;
+}
+
+.justify-end {
+	justify-content: flex-end;
+}
+
+.justify-center {
+	justify-content: center;
+}
+
+.justify-between {
+	justify-content: space-between;
+}
+
+.justify-around {
+	justify-content: space-around;
+}
+
+/* grid布局 */
+
+.grid {
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.grid.grid-square {
+	overflow: hidden;
+}
+
+.grid.grid-square .cu-tag {
+	position: absolute;
+	right: 0;
+	top: 0;
+	border-bottom-left-radius: 6upx;
+	padding: 6upx 12upx;
+	height: auto;
+	background-color: rgba(0, 0, 0, 0.5);
+}
+
+.grid.grid-square>view>text[class*="cuIcon-"] {
+	font-size: 52upx;
+	position: absolute;
+	color: #8799a3;
+	margin: auto;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	flex-direction: column;
+}
+
+.grid.grid-square>view {
+	margin-right: 20upx;
+	margin-bottom: 20upx;
+	border-radius: 6upx;
+	position: relative;
+	overflow: hidden;
+}
+.grid.grid-square>view.bg-img image {
+	width: 100%;
+	height: 100%;
+	position: absolute;
+}
+.grid.col-1.grid-square>view {
+	padding-bottom: 100%;
+	height: 0;
+	margin-right: 0;
+}
+
+.grid.col-2.grid-square>view {
+	padding-bottom: calc((100% - 20upx)/2);
+	height: 0;
+	width: calc((100% - 20upx)/2);
+}
+
+.grid.col-3.grid-square>view {
+	padding-bottom: calc((100% - 40upx)/3);
+	height: 0;
+	width: calc((100% - 40upx)/3);
+}
+
+.grid.col-4.grid-square>view {
+	padding-bottom: calc((100% - 60upx)/4);
+	height: 0;
+	width: calc((100% - 60upx)/4);
+}
+
+.grid.col-5.grid-square>view {
+	padding-bottom: calc((100% - 80upx)/5);
+	height: 0;
+	width: calc((100% - 80upx)/5);
+}
+
+.grid.col-2.grid-square>view:nth-child(2n),
+.grid.col-3.grid-square>view:nth-child(3n),
+.grid.col-4.grid-square>view:nth-child(4n),
+.grid.col-5.grid-square>view:nth-child(5n) {
+	margin-right: 0;
+}
+
+.grid.col-1>view {
+	width: 100%;
+}
+
+.grid.col-2>view {
+	width: 50%;
+}
+
+.grid.col-3>view {
+	width: 33.33%;
+}
+
+.grid.col-4>view {
+	width: 25%;
+}
+
+.grid.col-5>view {
+	width: 20%;
+}
+
+/*  -- 内外边距 -- */
+
+.margin-0 {
+	margin: 0;
+}
+
+.margin-xs {
+	margin: 10upx;
+}
+
+.margin-sm {
+	margin: 20upx;
+}
+
+.margin {
+	margin: 30upx;
+}
+
+.margin-lg {
+	margin: 40upx;
+}
+
+.margin-xl {
+	margin: 50upx;
+}
+
+.margin-top-xs {
+	margin-top: 10upx;
+}
+
+.margin-top-sm {
+	margin-top: 20upx;
+}
+
+.margin-top {
+	margin-top: 30upx;
+}
+
+.margin-top-lg {
+	margin-top: 40upx;
+}
+
+.margin-top-xl {
+	margin-top: 50upx;
+}
+
+.margin-right-xs {
+	margin-right: 10upx;
+}
+
+.margin-right-sm {
+	margin-right: 20upx;
+}
+
+.margin-right {
+	margin-right: 30upx;
+}
+
+.margin-right-lg {
+	margin-right: 40upx;
+}
+
+.margin-right-xl {
+	margin-right: 50upx;
+}
+
+.margin-bottom-xs {
+	margin-bottom: 10upx;
+}
+
+.margin-bottom-sm {
+	margin-bottom: 20upx;
+}
+
+.margin-bottom {
+	margin-bottom: 30upx;
+}
+
+.margin-bottom-lg {
+	margin-bottom: 40upx;
+}
+
+.margin-bottom-xl {
+	margin-bottom: 50upx;
+}
+
+.margin-left-xs {
+	margin-left: 10upx;
+}
+
+.margin-left-sm {
+	margin-left: 20upx;
+}
+
+.margin-left {
+	margin-left: 30upx;
+}
+
+.margin-left-lg {
+	margin-left: 40upx;
+}
+
+.margin-left-xl {
+	margin-left: 50upx;
+}
+
+.margin-lr-xs {
+	margin-left: 10upx;
+	margin-right: 10upx;
+}
+
+.margin-lr-sm {
+	margin-left: 20upx;
+	margin-right: 20upx;
+}
+
+.margin-lr {
+	margin-left: 30upx;
+	margin-right: 30upx;
+}
+
+.margin-lr-lg {
+	margin-left: 40upx;
+	margin-right: 40upx;
+}
+
+.margin-lr-xl {
+	margin-left: 50upx;
+	margin-right: 50upx;
+}
+
+.margin-tb-xs {
+	margin-top: 10upx;
+	margin-bottom: 10upx;
+}
+
+.margin-tb-sm {
+	/* margin-top: 20upx; */
+	margin-bottom: 20upx;
+}
+
+.margin-tb {
+	margin-top: 30upx;
+	margin-bottom: 30upx;
+}
+
+.margin-tb-lg {
+	margin-top: 40upx;
+	margin-bottom: 40upx;
+}
+
+.margin-tb-xl {
+	margin-top: 50upx;
+	margin-bottom: 50upx;
+}
+
+.padding-0 {
+	padding: 0;
+}
+
+.padding-xs {
+	padding: 10upx;
+}
+
+.padding-sm {
+	padding: 20upx;
+}
+
+.padding {
+	padding: 30upx;
+}
+
+.padding-lg {
+	padding: 40upx;
+}
+
+.padding-xl {
+	padding: 50upx;
+}
+
+.padding-top-xs {
+	padding-top: 10upx;
+}
+
+.padding-top-sm {
+	padding-top: 20upx;
+}
+
+.padding-top {
+	padding-top: 30upx;
+}
+
+.padding-top-lg {
+	padding-top: 40upx;
+}
+
+.padding-top-xl {
+	padding-top: 50upx;
+}
+
+.padding-right-xs {
+	padding-right: 10upx;
+}
+
+.padding-right-sm {
+	padding-right: 20upx;
+}
+
+.padding-right {
+	padding-right: 30upx;
+}
+
+.padding-right-lg {
+	padding-right: 40upx;
+}
+
+.padding-right-xl {
+	padding-right: 50upx;
+}
+
+.padding-bottom-xs {
+	padding-bottom: 10upx;
+}
+
+.padding-bottom-sm {
+	padding-bottom: 20upx;
+}
+
+.padding-bottom {
+	padding-bottom: 30upx;
+}
+
+.padding-bottom-lg {
+	padding-bottom: 40upx;
+}
+
+.padding-bottom-xl {
+	padding-bottom: 50upx;
+}
+
+.padding-left-xs {
+	padding-left: 10upx;
+}
+
+.padding-left-sm {
+	padding-left: 20upx;
+}
+
+.padding-left {
+	padding-left: 30upx;
+}
+
+.padding-left-lg {
+	padding-left: 40upx;
+}
+
+.padding-left-xl {
+	padding-left: 50upx;
+}
+
+.padding-lr-xs {
+	padding-left: 10upx;
+	padding-right: 10upx;
+}
+
+.padding-lr-sm {
+	padding-left: 20upx;
+	padding-right: 20upx;
+}
+
+.padding-lr {
+	padding-left: 30upx;
+	padding-right: 30upx;
+}
+
+.padding-lr-lg {
+	padding-left: 40upx;
+	padding-right: 40upx;
+}
+
+.padding-lr-xl {
+	padding-left: 50upx;
+	padding-right: 50upx;
+}
+
+.padding-tb-xs {
+	padding-top: 10upx;
+	padding-bottom: 10upx;
+}
+
+.padding-tb-sm {
+	padding-top: 20upx;
+	padding-bottom: 20upx;
+}
+
+.padding-tb {
+	padding-top: 30upx;
+	padding-bottom: 30upx;
+}
+
+.padding-tb-lg {
+	padding-top: 40upx;
+	padding-bottom: 40upx;
+}
+
+.padding-tb-xl {
+	padding-top: 50upx;
+	padding-bottom: 50upx;
+}
+
+/* -- 浮动 --  */
+
+.cf::after,
+.cf::before {
+	content: " ";
+	display: table;
+}
+
+.cf::after {
+	clear: both;
+}
+
+.fl {
+	float: left;
+}
+
+.fr {
+	float: right;
+}
+
+/* ==================
+          背景
+ ==================== */
+
+.line-red::after,
+.lines-red::after {
+	border-color: #e54d42;
+}
+
+.line-orange::after,
+.lines-orange::after {
+	border-color: #f37b1d;
+}
+
+.line-yellow::after,
+.lines-yellow::after {
+	border-color: #fbbd08;
+}
+
+.line-olive::after,
+.lines-olive::after {
+	border-color: #8dc63f;
+}
+
+.line-green::after,
+.lines-green::after {
+	border-color: #39b54a;
+}
+
+.line-cyan::after,
+.lines-cyan::after {
+	border-color: #1cbbb4;
+}
+
+.line-blue::after,
+.lines-blue::after {
+	border-color: #0081ff;
+}
+
+.line-purple::after,
+.lines-purple::after {
+	border-color: #6739b6;
+}
+
+.line-mauve::after,
+.lines-mauve::after {
+	border-color: #9c26b0;
+}
+
+.line-pink::after,
+.lines-pink::after {
+	border-color: #e03997;
+}
+
+.line-brown::after,
+.lines-brown::after {
+	border-color: #a5673f;
+}
+
+.line-grey::after,
+.lines-grey::after {
+	border-color: #8799a3;
+}
+
+.line-gray::after,
+.lines-gray::after {
+	border-color: #aaaaaa;
+}
+
+.line-black::after,
+.lines-black::after {
+	border-color: #333333;
+}
+
+.line-white::after,
+.lines-white::after {
+	border-color: #ffffff;
+}
+
+.bg-red {
+	background-color: #de1c31;
+	color: #ffffff;
+}
+
+.bg-orange {
+	background-color: #f37b1d;
+	color: #ffffff;
+}
+
+.bg-yellow {
+	background-color: #fbbd08;
+	color: #333333;
+}
+
+.bg-olive {
+	background-color: #8dc63f;
+	color: #ffffff;
+}
+
+.bg-green {
+	background-color: #39b54a;
+	color: #ffffff;
+}
+
+.bg-cyan {
+	background-color: #1cbbb4;
+	color: #ffffff;
+}
+
+.bg-blue {
+	background-color: #56ccf2;
+	color: #ffffff;
+}
+
+.bg-purple {
+	background-color: #6739b6;
+	color: #ffffff;
+}
+
+.bg-mauve {
+	background-color: #9c26b0;
+	color: #ffffff;
+}
+
+.bg-pink {
+	background-color: #e03997;
+	color: #ffffff;
+}
+
+.bg-brown {
+	background-color: #a5673f;
+	color: #ffffff;
+}
+
+.bg-grey {
+	background-color: #8799a3;
+	color: #ffffff;
+}
+
+.bg-gray {
+	background-color: #f0f0f0;
+	color: #333333;
+}
+
+.bg-black {
+	background-color: #333333;
+	color: #ffffff;
+}
+
+.bg-white {
+	background-color: #ffffff;
+	color: #666666;
+}
+
+.bg-shadeTop {
+	background-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.01));
+	color: #ffffff;
+}
+
+.bg-shadeBottom {
+	background-image: linear-gradient(rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 1));
+	color: #ffffff;
+}
+
+.bg-red.light {
+	color: #e54d42;
+	background-color: #fadbd9;
+}
+
+.bg-orange.light {
+	color: #f37b1d;
+	background-color: #fde6d2;
+}
+
+.bg-yellow.light {
+	color: #fbbd08;
+	background-color: #fef2ced2;
+}
+
+.bg-olive.light {
+	color: #8dc63f;
+	background-color: #e8f4d9;
+}
+
+.bg-green.light {
+	color: #39b54a;
+	background-color: #d7f0dbff;
+}
+
+.bg-cyan.light {
+	color: #1cbbb4;
+	background-color: #d2f1f0;
+}
+
+.bg-blue.light {
+	color: #0081ff;
+	background-color: #cce6ff;
+}
+
+.bg-purple.light {
+	color: #6739b6;
+	background-color: #e1d7f0;
+}
+
+.bg-mauve.light {
+	color: #9c26b0;
+	background-color: #ebd4ef;
+}
+
+.bg-pink.light {
+	color: #e03997;
+	background-color: #f9d7ea;
+}
+
+.bg-brown.light {
+	color: #a5673f;
+	background-color: #ede1d9;
+}
+
+.bg-grey.light {
+	color: #8799a3;
+	background-color: #e7ebed;
+}
+
+.bg-gradual-red {
+	background-image: linear-gradient(45deg, #f43f3b, #ec008c);
+	color: #ffffff;
+}
+
+.bg-gradual-orange {
+	background-image: linear-gradient(45deg, #ff9700, #ed1c24);
+	color: #ffffff;
+}
+
+.bg-gradual-green {
+	background-image: linear-gradient(45deg, #39b54a, #8dc63f);
+	color: #ffffff;
+}
+
+.bg-gradual-purple {
+	background-image: linear-gradient(45deg, #9000ff, #5e00ff);
+	color: #ffffff;
+}
+
+.bg-gradual-pink {
+	background-image: linear-gradient(45deg, #ec008c, #6739b6);
+	color: #ffffff;
+}
+
+.bg-gradual-blue {
+	background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
+	color: #ffffff;
+}
+
+.shadow[class*="-red"] {
+	box-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+}
+
+.shadow[class*="-orange"] {
+	box-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+}
+
+.shadow[class*="-yellow"] {
+	box-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+}
+
+.shadow[class*="-olive"] {
+	box-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+}
+
+.shadow[class*="-green"] {
+	box-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+}
+
+.shadow[class*="-cyan"] {
+	box-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+}
+
+.shadow[class*="-blue"] {
+	box-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+}
+
+.shadow[class*="-purple"] {
+	box-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+}
+
+.shadow[class*="-mauve"] {
+	box-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+}
+
+.shadow[class*="-pink"] {
+	box-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+}
+
+.shadow[class*="-brown"] {
+	box-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+}
+
+.shadow[class*="-grey"] {
+	box-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.shadow[class*="-gray"] {
+	box-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.shadow[class*="-black"] {
+	box-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.shadow[class*="-white"] {
+	box-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.text-shadow[class*="-red"] {
+	text-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+}
+
+.text-shadow[class*="-orange"] {
+	text-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+}
+
+.text-shadow[class*="-yellow"] {
+	text-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+}
+
+.text-shadow[class*="-olive"] {
+	text-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+}
+
+.text-shadow[class*="-green"] {
+	text-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+}
+
+.text-shadow[class*="-cyan"] {
+	text-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+}
+
+.text-shadow[class*="-blue"] {
+	text-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+}
+
+.text-shadow[class*="-purple"] {
+	text-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+}
+
+.text-shadow[class*="-mauve"] {
+	text-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+}
+
+.text-shadow[class*="-pink"] {
+	text-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+}
+
+.text-shadow[class*="-brown"] {
+	text-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+}
+
+.text-shadow[class*="-grey"] {
+	text-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.text-shadow[class*="-gray"] {
+	text-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.text-shadow[class*="-black"] {
+	text-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.bg-img {
+	background-size: cover;
+	background-position: center;
+	background-repeat: no-repeat;
+}
+
+.bg-mask {
+	background-color: #333333;
+	position: relative;
+}
+
+.bg-mask::after {
+	content: "";
+	border-radius: inherit;
+	width: 100%;
+	height: 100%;
+	display: block;
+	background-color: rgba(0, 0, 0, 0.4);
+	position: absolute;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	top: 0;
+}
+
+.bg-mask view,
+.bg-mask cover-view {
+	z-index: 5;
+	position: relative;
+}
+
+.bg-video {
+	position: relative;
+}
+
+.bg-video video {
+	display: block;
+	height: 100%;
+	width: 100%;
+	-o-object-fit: cover;
+	object-fit: cover;
+	position: absolute;
+	top: 0;
+	z-index: 0;
+	pointer-events: none;
+}
+
+/* ==================
+          文本
+ ==================== */
+
+.text-xs {
+	font-size: 20upx;
+}
+
+.text-sm {
+	font-size: 24upx;
+}
+
+.text-df {
+	font-size: 28upx;
+}
+
+.text-lg {
+	font-size: 32upx;
+}
+
+.text-xl {
+	font-size: 36upx;
+}
+
+.text-xxl {
+	font-size: 44upx;
+}
+
+.text-sl {
+	font-size: 80upx;
+}
+
+.text-xsl {
+	font-size: 120upx;
+}
+
+.text-Abc {
+	text-transform: Capitalize;
+}
+
+.text-ABC {
+	text-transform: Uppercase;
+}
+
+.text-abc {
+	text-transform: Lowercase;
+}
+
+.text-price::before {
+	content: "¥";
+	font-size: 80%;
+	margin-right: 4upx;
+}
+.text-water::before {
+	content: "💧<=";
+	font-size: 80%;
+	margin-right: 4upx;
+}
+.text-bluk::before {
+	content: "▨>=";
+	font-size: 80%;
+	margin-right: 4upx;
+}
+.text-cut {
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+}
+
+.text-bold {
+	font-weight: bold;
+}
+
+.text-center {
+	text-align: center;
+}
+
+.text-content {
+	line-height: 1.6;
+}
+
+.text-left {
+	text-align: left;
+}
+
+.text-right {
+	text-align: right;
+}
+
+.text-red,
+.line-red,
+.lines-red {
+	color: #e54d42;
+}
+
+.text-orange,
+.line-orange,
+.lines-orange {
+	color: #f37b1d;
+}
+
+.text-yellow,
+.line-yellow,
+.lines-yellow {
+	color: #fbbd08;
+}
+
+.text-olive,
+.line-olive,
+.lines-olive {
+	color: #8dc63f;
+}
+
+.text-green,
+.line-green,
+.lines-green {
+	color: #39b54a;
+}
+
+.text-cyan,
+.line-cyan,
+.lines-cyan {
+	color: #1cbbb4;
+}
+
+.text-blue,
+.line-blue,
+.lines-blue {
+	color: #56ccf2;;
+}
+
+.text-purple,
+.line-purple,
+.lines-purple {
+	color: #6739b6;
+}
+
+.text-mauve,
+.line-mauve,
+.lines-mauve {
+	color: #9c26b0;
+}
+
+.text-pink,
+.line-pink,
+.lines-pink {
+	/* color: #0081ff; */
+	color:#26781B;
+	/* color:#4eadfe; */
+}
+
+.text-brown,
+.line-brown,
+.lines-brown {
+	color: #a5673f;
+}
+
+.text-grey,
+.line-grey,
+.lines-grey {
+	color: #8799a3;
+}
+
+.text-gray,
+.line-gray,
+.lines-gray {
+	color: #aaaaaa;
+}
+
+.text-black,
+.line-black,
+.lines-black {
+	color: #333333;
+}
+
+.text-white,
+.line-white,
+.lines-white {
+	color: #ffffff;
+}

+ 158 - 0
components/drag-button/drag-button.vue

@@ -0,0 +1,158 @@
+<template>
+	<view>
+		<view
+			id="_drag_button"
+			class="drag"
+			:style="'right: ' + right + 'px; top:' + top + 'px;background:'+background+';box-shadow:'+boxshadow"
+			@touchstart="touchstart"
+			@touchmove.stop.prevent="touchmove"
+			@touchend="touchend"
+			@click.stop.prevent="click"
+			:class="{transition: isDock && !isMove }"
+		>
+		<image v-if='isIcon' class='side-bg' src='https://taohaoliang.oss-cn-beijing.aliyuncs.com/tmp/side-bg.png'></image>
+		<view style='position: relative;z-index:2;text-align:center;' :class="icon"><view><image style='width:11px;height:11px;' src="https://taohaoliang.oss-cn-beijing.aliyuncs.com/tmp/add.png" mode=""></image></view>{{text}}</view> 
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'drag-button',
+		props: {
+			isDock:{
+				type: Boolean,
+				default: false
+			},
+			isIcon:{
+				type: Boolean,
+				default: false
+			},
+			existTabBar:{
+				type: Boolean,
+				default: false
+			},
+			text:'',
+			icon:'',
+			location:'',
+			background:{
+				type: String,
+				default: 'transparent'
+			},
+			boxshadow:{
+				type: String,
+				default: '0 0 6rpx transparent'
+			},
+		},
+		data() {
+			return {
+				top:0,
+				right:0,
+				width: 0,
+				height: 0,
+				offsetWidth: 0,
+				offsetHeight: 0,
+				windowWidth: 0,
+				windowHeight: 0,
+				isMove: true,
+				edge: 10
+			}
+		},
+		mounted() {
+			const sys = uni.getSystemInfoSync();
+			this.windowWidth = sys.windowWidth;
+			this.windowHeight = sys.windowHeight;
+			
+			// #ifdef APP-PLUS
+				this.existTabBar && (this.windowHeight -= 50);
+			// #endif
+			if (sys.windowTop) {
+				this.windowHeight += sys.windowTop;
+			}
+			const query = uni.createSelectorQuery().in(this);
+			query.select('#_drag_button').boundingClientRect(data => {
+				this.width = data.width;
+				this.height = data.height;
+				this.offsetWidth = data.width / 2;
+				this.offsetHeight = data.height / 2;
+				this.top = this.windowHeight - this.height - this.edge - Number(this.location) - 80;
+			}).exec();
+		},
+		methods: {
+			click() {
+				this.$emit('btnClick');
+			},
+			touchstart(e) {
+				this.$emit('btnTouchstart');
+			},
+			touchmove(e) {
+				// 单指触摸
+				if (e.touches.length !== 1) {
+					return false;
+				}
+				
+				this.isMove = true;
+				
+				this.left = e.touches[0].clientX - this.offsetWidth;
+				
+				let clientY = e.touches[0].clientY - this.offsetHeight;
+				// #ifdef H5
+					clientY += this.height;
+				// #endif
+				let edgeBottom = this.windowHeight - this.height - this.edge;
+
+				// 上下触及边界
+				if (clientY < this.edge) {
+					this.top = this.edge;
+				} else if (clientY > edgeBottom) {
+					this.top = edgeBottom;
+				} else {
+					this.top = clientY
+				}
+				
+			},
+			touchend(e) {
+				if (this.isDock) {
+					let edgeRigth = this.windowWidth - this.width - this.edge;
+					
+					if (this.left < this.windowWidth / 2 - this.offsetWidth) {
+						this.left = this.edge;
+					} else {
+						this.left = edgeRigth;
+					}
+					
+				}
+				
+				this.isMove = false;
+				
+				this.$emit('btnTouchend');
+			},
+		}}
+</script>
+
+<style lang="scss">
+	.side-bg{
+		position:absolute;
+		width: 60px;
+		height: 38px;
+		z-index: 1;
+	}
+	.drag {
+		position:relative;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		color: $uni-text-color-inverse;
+		width: 60px;
+		height: 38px;
+		background:transparent;
+		font-size: $uni-font-size-sm;
+		position: fixed;
+		z-index: 999999;
+		
+		&.transition {
+			transition: left .3s ease,top .3s ease;
+		}
+	}
+	
+</style>

+ 81 - 0
components/emotion/index.vue

@@ -0,0 +1,81 @@
+<template>
+	<view class="emotion-wrap">
+		<view class="emotion-btn">
+			<image src="../../static/img/clear.png" 
+			style="width: 64rpx;height: 64rpx;" @tap="handleRemoveEmoji"></image>
+		</view>
+		<swiper class="swiper-wrap" indicator-dots="true" duration="150">
+			<swiper-item v-for="(page,pid) in list" :key="pid">
+				<view class="emotion-box">
+					<view v-for="(em,eid) in page" :key="eid" @tap="handleClikEmoji(em)">
+						<u-image class="emotion" width="70rpx" height="70rpx" :src="`https://liangxin.zthymaoyi.com/upload/${em.url}`"></u-image>
+					</view>
+				</view>
+			</swiper-item>
+		</swiper>
+	</view>
+</template>
+
+<script>
+	import { emojiList } from "@/pageC/static/emoji/emoji.js"
+	export default {
+		name:'u-emotion',
+		data() {
+			return {
+				list: emojiList,
+				cur: {}
+			}
+		},
+		methods: {
+			handleSend() {
+				this.$emit('send')
+			},
+			handleRemoveEmoji() {
+				this.$emit('addEmoji', this.cur, true)
+			},
+			handleClikEmoji(item) {
+				this.cur = {
+					emojiPath: 'https://liangxin.zthymaoyi.com/upload/',
+					minEmoji: true,
+					emojiItem: item,
+					groupIndex: 1,
+					emoticonFlag: false
+				}
+				this.$emit('addEmoji', this.cur)
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.emotion-wrap {
+		height: 100%;
+		padding-left: 2%;
+		padding-bottom: 20px;
+		overflow: auto;
+		
+		.emotion-box {
+			padding-top: 30px;
+		}
+		
+		.swiper-wrap {
+			height: 100%;
+		}
+
+		.emotion-btn {
+			z-index: 1;
+			position: fixed;
+			right: 20rpx;
+			bottom: 30rpx;
+		}
+
+		.emotion-del {
+			margin-right: 10rpx;
+		}
+
+		.emotion {
+			float: left;
+			margin: 5px;
+		}
+	}
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 18 - 0
components/empty.vue


Diferenças do arquivo suprimidas por serem muito extensas
+ 3 - 0
components/icon/icon.css


+ 86 - 0
components/img-cache/README.md

@@ -0,0 +1,86 @@
+## ✨ Feature
+
+- UNIAPP APP 平台上图片缓存组件
+- 可兼容其他平台写法
+
+## 🥗 Install
+
+- 在 uni-app 插件市场右上角选择`使用HBuilderX 导入插件`或者`下载插件ZIP`。
+- 将下载解压后的`img-cache`文件夹,复制到`components`目录内。
+
+## 🥐 Usage
+
+### Vue CLI
+
+```Javascript
+// 引入
+import ImgCache from '@/components/img-cache/img-cache.vue';
+
+// 注册
+components: {
+  ImgCache
+}
+
+// 使用
+<img-cache src="https://example.com/image.png"></img-cache>
+```
+
+### HBuilderX
+
+由于 HBuilder X 的[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom),可以不用引用、注册,直接在页面中使用
+
+```HTML
+<img-cache src="https://example.com/image.png"></img-cache>
+```
+
+## 🥪 API
+
+### Props
+
+`image`的[属性](https://uniapp.dcloud.io/component/image)在`img-cache`组件上是一样可以用的,这里在原有的基础上加了几个属性
+
+| 属性名       | 类型             | 默认值   | 说明                                             |
+| :----------- | :--------------- | :------- | :----------------------------------------------- |
+| dir          | string           | imgcache | 缓存的文件目录,文件夹开头不能有\_               |
+| width        | string \| number | -        | 宽度,单位任意,如果为数值,则为 rpx 单位        |
+| height       | string \| number | -        | 高度,单位任意,如果为数值,则为 rpx 单位        |
+| custom-style | object           | -        | 自定义样式,自定义样式宽高权重大于属性设定的宽高 |
+
+### Events
+
+| 属性名 | 类型        | 说明                                                                    |
+| :----- | :---------- | :---------------------------------------------------------------------- |
+| click  | HandleEvent | 点击事件,请勿使用@tap 点击事件,微信小程序无效,返回值为点击事件及参数 |
+
+### Method
+
+这里我写了几个方法用于处理缓存文件,可无需引入组件,而是引入组件`img-cache`目录下的`index.js`,文件路径格式查看[RelativeURL](http://www.html5plus.org/doc/zh_cn/io.html#plus.io.RelativeURL)
+
+```Javascript
+// 引入
+import { resolveFile, getDirSize, removeDir, formatSize } from '@/components/img-cache';
+```
+
+**resolveFile(url)**
+
+- 获取[目录对象](http://www.html5plus.org/doc/zh_cn/io.html#plus.io.DirectoryEntry)或[文件对象](http://www.html5plus.org/doc/zh_cn/io.html#plus.io.FileEntry)
+- 参数:`url` `<string>` 要操作文件或目录的 URL 地址
+- 返回:`<Promise>` 目录或文件对象,若不存在返回 null
+
+**getDirSize(dir)**
+
+- 获取目录大小
+- 参数:`dir` `<string>` 目录地址
+- 返回:`<Promise>` 目录大小,单位字节
+
+**removeDir(dir)**
+
+- 删除目录
+- 参数:`dir` `<string>` 目录地址
+- 返回:`<Promise>` 删除状态
+
+**formatSize(size)**
+
+- 格式化字节大小
+- 参数:`size` `<number>` 字节大小
+- 返回:`<String>` 转换后的文字

+ 47 - 0
components/img-cache/download.js

@@ -0,0 +1,47 @@
+import path from 'path'
+
+var queue = [] // 下载队列 避免重复下载
+
+/**
+ * 下载文件
+ * @param {string} url 要下载文件资源地址
+ * @param {string} [dir] 下载文件保存的路径
+ * @returns {Promise<string|null>} 下载后的本地路径
+ */
+function download(url, dir) {
+  return new Promise(resolve => {
+    if (queue.includes(url)) return
+    queue.push(url)
+    const task = plus.downloader.createDownload(
+      url,
+      dir ? { filename: filename(url, dir) } : {},
+      (download, status) => {
+        queue.splice(queue.indexOf(url), 1)
+        resolve(status === 200 ? download.filename : null)
+      }
+    )
+    task.start()
+  })
+}
+
+/**
+ * 生成随机文件名后的路径
+ * @param {string} url 网络地址
+ * @param {string} dir 保存的路径
+ * @returns {string} 随机文件名后的路径
+ */
+function filename(url, dir) {
+  const CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+  let random = ''
+  for (let i = 0; i < 4; i++) {
+    const index = parseInt(Math.random() * CHARS.length)
+    random += CHARS[index]
+  }
+
+  dir = dir.replace(/(^\/)|(\/$)/g, '')
+  const name = Date.now() + random + path.extname(url)
+
+  return `_doc/${dir}/${name}`
+}
+
+export default download

+ 154 - 0
components/img-cache/img-cache.vue

@@ -0,0 +1,154 @@
+<template>
+  <image
+    class="img-cache"
+    :src="resource"
+    :mode="mode"
+    :lazy-load="lazyLoad"
+    :fade-show="fadeShow"
+    :webp="webp"
+    :show-menu-by-longpress="showMenuByLongpress"
+    :style="[style]"
+    @tap="fnEvent('click', $event)"
+    @error="fnEvent('error', $event)"
+    @load="fnEvent('load', $event)"
+  >
+  </image>
+</template>
+
+<script>
+// #ifdef APP-PLUS
+import storage from './storage'
+import download from './download'
+import { resolveFile } from './index'
+// #endif
+
+/**
+ * ImgCache 图片缓存
+ * @description APP端图片缓存
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=2515
+ * @property {string} src 图片资源地址
+ * @property {string} mode 图片裁剪、缩放的模式
+ * @property {boolean} lazyLoad 是否图片懒加载
+ * @property {boolean} fade-show 图片显示动画效果
+ * @property {boolean} webp 默认不解析 webP 格式,只支持网络资源
+ * @property {boolean} show-menu-by-longpress 开启长按图片显示识别小程序码菜单
+ * @property {string} dir 缓存的文件目录,文件夹开头不能有_
+ * @property {string} width 宽度,单位任意,如果为数值,则为 rpx 单位
+ * @property {string} height 高度,单位任意,如果为数值,则为 rpx 单位
+ * @property {object} custom-style 自定义样式
+ * @event {Function} click 点击图片
+ * @event {Function} error 错误发生
+ * @event {Function} load 图片载入完毕
+ * @example <img-cache src="https://example.com/image.png"></img-cache>
+ */
+export default {
+  name: 'ImgCache',
+  props: {
+    src: {
+      type: String
+    },
+    mode: {
+      type: String,
+      default: 'scaleToFill'
+    },
+    lazyLoad: {
+      type: Boolean,
+      default: false
+    },
+    fadeShow: {
+      type: Boolean,
+      default: true
+    },
+    webp: {
+      type: Boolean,
+      default: false
+    },
+    showMenuByLongpress: {
+      type: Boolean,
+      default: false
+    },
+    dir: {
+      type: String,
+      default: 'imgcache'
+    },
+    width: {
+      type: [String, Number]
+    },
+    height: {
+      type: [String, Number]
+    },
+    customStyle: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      resource: ''
+    }
+  },
+  computed: {
+    style() {
+      let style = { willChange: 'transform' }
+
+      // 判断传过来的值不为 undefined null ''
+      if ((this.width ?? '') !== '') style.width = this.addUnit(this.width)
+      if ((this.height ?? '') !== '') style.height = this.addUnit(this.height)
+
+      return {
+        ...style,
+        ...this.customStyle
+      }
+    }
+  },
+  watch: {
+    src: {
+      handler: 'init',
+      immediate: true
+    }
+  },
+  methods: {
+    // 初始化
+    init() {
+      // #ifdef APP-PLUS
+      this.fnCache()
+      // #endif
+
+      // #ifndef APP-PLUS
+      this.setSrc()
+      // #endif
+    },
+    // 获取缓存
+    async fnCache() {
+      const url = this.src // 赋值到新变量,避免下载时 src 更改,从而网络地址和本地地址图片不一致
+
+      if (!/^https?:\/\//.test(url)) return this.setSrc() // 判断是否网络地址
+
+      const [select] = storage.select({ url }) // 查询缓存是否存在
+
+      if (select) {
+        const path = select.local
+        if (await resolveFile(path)) return this.setSrc(path) // 判断本地文件是否存在 如果存在则显示本地文件
+        storage.delete(select) // 如果本地文件不存在则删除缓存数据
+      }
+      this.setSrc()
+
+      const local = await download(url, this.dir) // 下载文件
+      if (local) storage.insert({ url, local }) // 缓存数据
+    },
+    // 发送事件
+    fnEvent(emit, event) {
+      this.$emit(emit, event)
+    },
+    // 设置图片资源地址
+    setSrc(src) {
+      this.resource = src || this.src
+    },
+    // 添加单位,如果为数值则为rpx单位,否则直接返回
+    addUnit(value) {
+      value = String(value ?? '')
+      return /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value) ? `${value}rpx` : value
+    }
+  }
+}
+</script>

+ 75 - 0
components/img-cache/index.js

@@ -0,0 +1,75 @@
+/**
+ * 获取目录对象或文件对象
+ * @param {string} url 要操作文件或目录的URL地址
+ * @returns {Promise<object>} 若成功返回请求到的目录或文件对象,否则返回 null
+ */
+export function resolveFile(url) {
+  return new Promise(resolve => {
+    // #ifdef APP-PLUS
+    plus.io.resolveLocalFileSystemURL(url, resolve, () => resolve(null))
+    // #endif
+
+    // #ifndef APP-PLUS
+    resolve(null)
+    // #endif
+  })
+}
+
+/**
+ * 获取目录大小
+ * @param {string} dir 目录地址
+ * @returns {Promise<number>} 返回目录大小,单位字节
+ */
+export function getDirSize(dir) {
+  return new Promise(async resolve => {
+    // #ifdef APP-PLUS
+    const entry = await resolveFile(dir)
+    if (!entry) return resolve(0)
+    entry.getMetadata(
+      res => resolve(res.size || 0),
+      () => resolve(0),
+      true
+    )
+    // #endif
+
+    // #ifndef APP-PLUS
+    resolve(0)
+    // #endif
+  })
+}
+
+/**
+ * 删除目录
+ * @param {string} dir 目录地址
+ * @returns {Promise<boolean>} 删除状态
+ */
+export function removeDir(dir) {
+  return new Promise(async resolve => {
+    // #ifdef APP-PLUS
+    const entry = await resolveFile(dir)
+    if (!entry) return resolve(false)
+    entry.removeRecursively(
+      () => resolve(true),
+      () => resolve(false)
+    )
+    // #endif
+
+    // #ifndef APP-PLUS
+    resolve(false)
+    // #endif
+  })
+}
+
+/**
+ * 格式化字节大小
+ * @param {number} size 字节大小
+ * @returns {string} 格式化后的文字
+ */
+export function formatSize(size) {
+  const s = parseInt(size)
+  if (s === 0) return '0B'
+  else if (s < 1024) return s + 'B'
+  else if (s < 1048576) return (s / 1024).toFixed(2) + 'KB'
+  else if (s < 1073741824) return (s / 1048576).toFixed(2) + 'MB'
+  else return (s / 1073741824).toFixed(2) + 'GB'
+}

+ 36 - 0
components/img-cache/storage.js

@@ -0,0 +1,36 @@
+class Storage {
+  constructor(name) {
+    this.name = name
+  }
+  // 获取缓存
+  getStorage() {
+    return uni.getStorageSync(this.name) || []
+  }
+  // 设置缓存
+  setStorage(data = []) {
+    if (data.length) uni.setStorageSync(this.name, data)
+    return data
+  }
+  // 插入数据
+  insert(...arg) {
+    let storage = this.getStorage()
+    storage.push(...arg)
+    this.setStorage(storage)
+    return storage
+  }
+  // 删除数据
+  delete(obj = {}) {
+    const origin = this.getStorage()
+    const storage = origin.filter(ele => !Object.keys(obj).some(k => obj[k] === ele[k]))
+    this.setStorage(storage)
+    return storage
+  }
+  // 查询数据
+  select(obj = {}) {
+    const origin = this.getStorage()
+    const storage = origin.filter(ele => Object.keys(obj).every(k => obj[k] === ele[k]))
+    return storage
+  }
+}
+
+export default new Storage('imgcache')

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
components/jweixin-module/index.js


+ 51 - 0
components/jweixin-module/jwx.js

@@ -0,0 +1,51 @@
+import * as config from '../../config'
+
+const jweixin = require('./index')
+
+const configWeiXin = async function(callback) {
+	uni.request({
+		url: config.def().baseUrl + '/m.api',
+		data: {
+			_gp: 'user',
+			_mt: 'getH5Sign',
+			url: window.location.href
+		},
+		method: 'POST',
+		header: {
+			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
+		},
+		success: (res) => {
+			if (res.statusCode === 200) {
+				if (res.data.errno === 200) {
+					let resConfig = res.data.data
+					if (resConfig) {
+					    let apiList = [ // 可能需要用到的能力
+					        'chooseWXPay'
+					    ];
+					    let info = {
+					        debug: config.def().debug, // 调试,发布的时候改为false
+					        appId: config.def().h5Appid,
+					        nonceStr: resConfig.noncestr,
+					        timestamp: resConfig.timestamp,
+					        signature: resConfig.sign,
+					        jsApiList: apiList
+					    };
+					    jweixin.config(info);
+					    jweixin.error(err => {
+					        console.log('config fail:', err);
+					    });
+					
+					    jweixin.ready(res => {
+					        if (callback) callback(jweixin); // 配置成功
+					    });
+					}
+				}
+			}
+		}
+	})
+ 
+}
+
+export {
+	configWeiXin
+}

+ 179 - 0
components/lyg-popup/lyg-popup.vue

@@ -0,0 +1,179 @@
+<template name="protocol-popup">
+	<view @touchmove.stop.prevent="clear" v-show="showPopup">
+		<view class="popup_mask" @touchmove.stop.prevent="clear"></view>
+		<view class="popup_content">
+			<view class="title">{{title}}</view>
+			<view class="explain_text">
+				请你务必认真阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了向你提供数据、分享等服务所要获取的权限信息。
+				<view class="line">
+					你可以阅读
+					<navigator :url="protocolPath" class="path" hover-class="navigator-hover">《服务协议》</navigator>和<navigator :url="policyPath"
+					 class="path" hover-class="navigator-hover">《隐私政策》</navigator>了解详细信息。如您同意,请点击"同意"开始接受我们的服务。
+				</view>
+			</view>
+
+			<!-- <view class="button">
+				<view @tap="back">暂不使用</view>
+				<view @tap="confirm">同意</view>
+			</view> -->
+			<view class="button-group">
+				<button @tap="back" style='border-right:1px solid #f2f2f2;'>暂不使用</button>
+				<button @tap="confirm">同意</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "lyg-popup",
+		props: {
+			title: {
+				type: String,
+				default: "服务协议和隐私政策"
+			},
+			// 协议路径
+			protocolPath: {
+				type: String
+			},
+			// 政策路径
+			policyPath: {
+				type: String
+			},
+			policyStorageKey: {
+				type: String,
+				default:"has_read_privacy"
+			}
+		},
+		data() {
+			return {
+				showPopup: false
+			};
+
+		},
+		created: function() {
+			var that = this;
+			console.log("lyg-popup created");
+			uni.getStorage({
+				key: 'policyStorageKey',
+				success: (res) => {
+					if (res.data) {
+						that.showPopup = false;
+						uni.showTabBar({});
+					}
+				},
+				fail: function(e) {
+					that.showPopup = true;
+					uni.hideTabBar({});
+					console.log(e)
+				}
+			});
+		},
+		methods: {
+			// 禁止滚动
+			clear() {
+				return;
+			},
+			back() {
+				this.$emit('popupState', false)
+				// #ifdef APP-PLUS  
+				plus.runtime.quit();
+				// #endif
+			},
+			// 关闭弹框
+			confirm() {
+				this.showPopup = false;
+				this.$emit('popupState', true);
+
+				uni.setStorage({ 
+					key: 'policyStorageKey',
+					data: true
+				});
+				
+				uni.showTabBar({});
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	.popup_mask {
+		position: fixed;
+		bottom: 0;
+		top: 0;
+		left: 0;
+		right: 0;
+		background-color: rgba(0, 0, 0, 0.4);
+		transition-property: opacity;
+		transition-duration: 0.3s;
+		opacity: 0;
+		z-index: 98;
+
+	}
+	.button-group{
+		    border-top: 1px solid #f2f2f2;
+	}
+.button-group button{
+	display: inline-block;
+	    width: 50%;
+	    background: #fff;
+		border-radius: 0;
+}
+.button-group button::after {
+   border: none;
+}
+	.popup_mask {
+		opacity: 1;
+	}
+
+	.popup_content {
+		overflow: hidden;
+		box-sizing: border-box;
+		padding: 40upx 20upx 0 20upx;
+		position: fixed;
+		bottom: 30%;
+		border-radius: 8px;
+		left: 50%;
+		margin-left: -40%;
+		right: 0;
+		min-height: 400upx;
+		background: #ffffff;
+		width: 80%;
+		z-index: 99;
+
+		.title {
+			text-align: center;
+			font-size: 34upx;
+			padding: 10upx 0 0 0;
+		}
+
+		.explain_text {
+			font-size: 30upx;
+			padding: 30upx 30upx 40upx 30upx;
+			line-height: 38upx;
+
+			.line {
+				display: block;
+
+				.path {
+					color: #007aff;
+					display: inline-block;
+					text-align: center;
+				}
+			}
+		}
+
+		.button {
+			display: flex;
+			padding: 20upx;
+			align-items: center;
+			font-size: 34upx;
+			justify-content: space-around;
+			border-top: 1upx solid #f2f2f2;
+
+			view {
+				text-align: center;
+			}
+		}
+	}
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 212 - 0
components/mehaotian-search-revision.vue


+ 135 - 0
components/mix-list-cell.vue

@@ -0,0 +1,135 @@
+<template>
+	<view class="content">
+		<button v-if="openType" :open-type="openType" class="btn"></button>
+		<view class="mix-list-cell" :class="border" @click="eventClick" hover-class="cell-hover" :hover-stay-time="50">
+			<text v-if="icon" class="cell-icon yticon" :style="[{
+						color: iconColor,
+					}]" :class="icon"></text>
+			<text class="cell-tit clamp">{{title}}</text>
+			<text v-if="tips" class="cell-tip">{{tips}}</text>
+			<text class="cell-more yticon" :class="typeList[navigateType]"></text>
+
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 *  简单封装了下, 应用范围比较狭窄,可以在此基础上进行扩展使用
+	 *  比如加入image, iconSize可控等
+	 */
+	export default {
+		data() {
+			return {
+				typeList: {
+					left: 'icon-zuo',
+					right: 'icon-you',
+					up: 'icon-shang',
+					down: 'icon-xia'
+				},
+			}
+		},
+		props: {
+			openType: {
+				type: String,
+				default: ''
+			},
+			icon: {
+				type: String,
+				default: ''
+			},
+			title: {
+				type: String,
+				default: '标题'
+			},
+			tips: {
+				type: String,
+				default: ''
+			},
+			navigateType: {
+				type: String,
+				default: 'right'
+			},
+			border: {
+				type: String,
+				default: 'b-b'
+			},
+			hoverClass: {
+				type: String,
+				default: 'cell-hover'
+			},
+			iconColor: {
+				type: String,
+				default: '#333'
+			}
+		},
+		methods: {
+			eventClick() {
+				this.$emit('eventClick');
+			}
+		},
+	}
+</script>
+
+<style lang='scss'>
+	.icon .mix-list-cell.b-b:after {
+		left: 90upx;
+	}
+
+	.mix-list-cell {
+		display: flex;
+		align-items: baseline;
+		background: #FFFFFF;
+		padding-left: 20upx;
+		padding: 20upx $page-row-spacing;
+		line-height: 60upx;
+		position: relative;
+
+		&.cell-hover {
+			background: #fafafa;
+		}
+
+		&.b-b:after {
+			left: 30upx;
+		}
+
+		.cell-icon {
+			align-self: center;
+			width: 56upx;
+			max-height: 60upx;
+			font-size: 38upx;
+		}
+
+		.cell-more {
+			align-self: center;
+			font-size: 30upx;
+			color: $font-color-base;
+			margin-left: $uni-spacing-row-sm;
+		}
+
+		.cell-tit {
+			flex: 1;
+			font-size: $font-base;
+			color: $font-color-dark;
+			margin-right: 10upx;
+		}
+
+		.cell-tip {
+			border-top: #909399;
+			font-size: $font-sm+2upx;
+			color: $font-color-light;
+		}
+
+	}
+
+	.btn::after {
+		display: none;
+	}
+	
+	.btn {
+		width: 95%;
+		position: absolute;
+		opacity: 0;
+		z-index: 99;
+	}
+</style>

+ 264 - 0
components/neil-modal/neil-modal.vue

@@ -0,0 +1,264 @@
+<template>
+    <view class="neil-modal" @touchmove.stop.prevent="bindTouchmove" :class="{'neil-modal--show':isOpen}">
+        <view class="neil-modal__mask" @click="clickMask"></view>
+        <view class="neil-modal__container">
+            <view class="neil-modal__header" v-if="title.length > 0">{{title}}</view>
+            <view class="neil-modal__content" :class="content ? 'neil-modal--padding' : ''" :style="{textAlign:align}">
+                <template v-if="content">
+                    <text class="modal-content">{{content}}</text>
+                </template>
+                <template v-else>
+                    <slot />
+                </template>
+            </view>
+            <view class="neil-modal__footer">
+                <view v-if="showCancel" class="neil-modal__footer-left" @click="clickLeft" :style="{color:cancelColor}"
+                    hover-class="neil-modal__footer-hover" :hover-start-time="20" :hover-stay-time="70">
+                    {{cancelText}}
+                </view>
+                <view v-if="showConfirm" class="neil-modal__footer-right" @click="clickRight" :style="{color:confirmColor}" hover-class="neil-modal__footer-hover"
+                    :hover-start-time="20" :hover-stay-time="70">
+                    {{confirmText}}
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'neil-modal',
+        props: {
+            title: { //标题
+                type: String,
+                default: ''
+            },
+            content: String, //提示的内容
+            align: { //content 的对齐方式left/center/right
+                type: String,
+                default: 'left'
+            },
+            cancelText: { //取消按钮的文字,默认为"取消"
+                type: String,
+                default: '取消'
+            },
+            cancelColor: { //取消按钮颜色
+                type: String,
+                default: '#333333'
+            },
+            confirmText: { //确定按钮的文字,默认为"确定"
+                type: String,
+                default: '确定'
+            },
+            confirmColor: { //确认按钮颜色
+                type: String,
+                default: '#007aff'
+            },
+            showCancel: { //是否显示取消按钮,默认为 true
+                type: [Boolean, String],
+                default: false
+            },
+			showConfirm: { //是否显示取消按钮,默认为 true
+                type: [Boolean, String],
+                default: false
+            },
+            show: { //是否显示模态框
+                type: [Boolean, String],
+                default: false
+            },
+            autoClose: { //点击遮罩是否自动关闭弹窗
+                type: [Boolean, String],
+                default: false
+            }
+        },
+        data() {
+            return {
+                isOpen: false
+            }
+        },
+        watch: {
+            show(val) {
+				this.isOpen = val
+            }
+        },
+        created() {
+        	this.isOpen = this.show
+        },
+        methods: {
+            bindTouchmove() {},
+            clickLeft() {
+                setTimeout(() => {
+                	this.$emit('cancel')
+                }, 200)
+                this.closeModal()
+            },
+            clickRight() {
+                setTimeout(() => {
+                	this.$emit('confirm')
+                }, 200)
+                // this.closeModal()
+            },
+			clickMask(){
+				if(this.autoClose){
+					this.closeModal()
+				}
+			},
+            closeModal() {
+                this.showAnimation = false
+				this.isOpen = false
+				this.$emit('close')
+            }
+        }
+    }
+</script>
+
+<style lang="scss">
+    $bg-color-mask:rgba(0, 0, 0, 0.5); //遮罩颜色
+    $bg-color-hover:#f1f1f1; //点击状态颜色
+
+    .neil-modal {
+        position: fixed;
+		visibility: hidden;
+        width: 100%;
+        height: 100%;
+        top: 0;
+        left: 0;
+        z-index: 1000;
+		transition:visibility 200ms ease-in;
+		
+		&.neil-modal--show{
+			visibility: visible;
+		}
+
+        &__header {
+            position: relative;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            padding: 18upx 24upx;
+            line-height: 1.5;
+            color: #333;
+            font-size: 32upx;
+            text-align: center;
+
+            &::after {
+                content: " ";
+                position: absolute;
+                left: 0;
+                bottom: 0;
+                right: 0;
+                height: 1px;
+                border-top: 1px solid #e5e5e5;
+                transform-origin: 0 0;
+                transform: scaleY(.5);
+            }
+        }
+
+        &__container {
+            position: absolute;
+			z-index: 999;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%) ;
+            transition: transform 0.3s;
+            width: 540upx;
+            border-radius: 20upx;
+            background-color: #fff;
+            overflow: hidden;
+            opacity: 0;
+            transition: opacity 200ms ease-in;
+        }
+
+        &__content {
+            position: relative;
+            color: #333;
+            font-size: 28upx;
+            box-sizing: border-box;
+            line-height: 1.5;
+
+            &::after {
+                content: " ";
+                position: absolute;
+                left: 0;
+                bottom: -1px;
+                right: 0;
+                height: 1px;
+                border-bottom: 1px solid #e5e5e5;
+                transform-origin: 0 0;
+                transform: scaleY(.5);
+            }
+        }
+
+        &__footer {
+            position: relative;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            color: #333;
+            font-size: 32upx;
+            display: flex;
+            flex-direction: row;
+
+            &-left,
+            &-right {
+                position: relative;
+                flex: 1;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                height: 88upx;
+                font-size: 28upx;
+                line-height: 88upx;
+                text-align: center;
+                background-color: #fff;
+                color: #333;
+            }
+
+            &-right {
+                color: #007aff;
+            }
+
+            &-left::after {
+                content: " ";
+                position: absolute;
+                right: -1px;
+                top: 0;
+                width: 1px;
+                bottom: 0;
+                border-right: 1px solid #e5e5e5;
+                transform-origin: 0 0;
+                transform: scaleX(.5);
+            }
+
+            &-hover {
+                background-color: $bg-color-hover;
+            }
+        }
+
+        &__mask {
+            display: block;
+            position: absolute;
+			z-index: 998;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background: $bg-color-mask;
+            opacity: 0;
+            transition: opacity 200ms ease-in;
+			&.neil-modal--show{
+				opacity: 1;
+			}
+        }
+
+        &--padding {
+            padding: 32upx 24upx;
+            min-height: 90upx;
+        }
+		&--show {
+		    .neil-modal__container,.neil-modal__mask{
+				opacity: 1;
+			}
+		}
+    }
+</style>

+ 84 - 0
components/neil-modal/readme.md

@@ -0,0 +1,84 @@
+### Modal 模态框
+
+自定义 Modal 模态框组件
+
+**使用方式:**
+
+在 script 中引用组件
+
+```javascript
+import neilModal from '@/components/neil-modal/neil-modal.vue';
+export default {
+    components: {neilModal}
+}
+```
+
+基础使用方式
+
+```html
+<neil-modal 
+    :show="show" 
+    @close="closeModal" 
+    title="标题" 
+    content="这里是正文内容,这里是正文内容,这里是正文内容,这里是正文内容,这里是正文内容,这里是正文内容"
+    @cancel="bindBtn('cancel')" 
+    @confirm="bindBtn('confirm')">
+</neil-modal>
+```
+
+单个确认按钮
+
+```html
+<neil-modal 
+    :show="show" 
+    title="标题" 
+    content="这里是正文内容,这里是正文内容,这里是正文内容,这里是正文内容,这里是正文内容,这里是正文内容"
+    :show-cancel="false">
+</neil-modal>
+```
+
+**属性说明:**
+
+|属性名	|类型		|默认值	|说明	|
+|---	|----		|---	|---	|
+|title|String||标题	|
+|content|String||内容|
+|align|String|left|内容对齐方式,值为:left(左对齐)、center(居中对齐)、right(右对齐)|
+|show   |Boolean	|false	|Modal的显示状态	|
+|show-cancel|Boolean|true	|是否显示取消按钮|
+|auto-close|Boolean|true	|点击遮罩是否自动关闭模态框|
+|confirm-color|String|#007aff|确认按钮的颜色	|
+|confirm-text|String|确定|确定按钮的文字	|
+|cancel-color|String|#333333|取消按钮的颜色	|
+|cancel-text|String|取消|取消按钮的文字	|
+
+**事件说明:**
+
+|事件名|说明		|
+|close|组件关闭时触发事件|
+|confirm|点击确认按钮时触发事件|
+|cancel|点击取消按钮时触发事件|
+
+**slot**
+
+在 ``neil-modal`` 节点下,可以通过插入节点实现自定义 content 的需求(只有 content 属性为空的时候才会加载 slot)
+
+使用示例:
+
+```html
+<neil-modal :show="show" @close="closeModal" title="更新提示" confirm-text="立即更新" cancel-text="暂不更新">
+    <view style="min-height: 90upx;padding: 32upx 24upx;">
+        <view>1. 修复标题颜色不对的问题</view>
+        <view>2. 增加支付宝支付功能</view>
+        <view>3. 增加更多示例</view>
+    </view>
+</neil-modal>
+```
+
+**其他**
+
+* Modal 组件 z-index 为 1000;
+* Modal 组件非原生组件,使用时会被原生组件所覆盖;
+* 通过本页面下载按钮下载的zip为一个完整 ``uni-app`` 工程,拖入 HBuilderX即可运行体验效果;
+* 若想集成本组件到现有工程,可以将 components 目录下的 neil-modal 目录拷贝到自己工程的 components 目录;
+* 使用过程出现问题或有新的需求可在评论区留言。

+ 143 - 0
components/ossutil/base64.js

@@ -0,0 +1,143 @@
+
+var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+var base64DecodeChars = new Array(
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+    -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);
+function encode(str) {
+    var out, i, len;
+    var c1, c2, c3;
+    len = str.length;
+    i = 0;
+    out = "";
+    while (i < len) {
+        c1 = str.charCodeAt(i++) & 0xff;
+        if (i == len) {
+            out += base64EncodeChars.charAt(c1 >> 2);
+            out += base64EncodeChars.charAt((c1 & 0x3) << 4);
+            out += "==";
+            break;
+        }
+        c2 = str.charCodeAt(i++);
+        if (i == len) {
+            out += base64EncodeChars.charAt(c1 >> 2);
+            out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
+            out += base64EncodeChars.charAt((c2 & 0xF) << 2);
+            out += "=";
+            break;
+        }
+        c3 = str.charCodeAt(i++);
+        out += base64EncodeChars.charAt(c1 >> 2);
+        out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
+        out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
+        out += base64EncodeChars.charAt(c3 & 0x3F);
+    }
+    return out;
+}
+function decode(str) {
+    var c1, c2, c3, c4;
+    var i, len, out;
+    len = str.length;
+    i = 0;
+    out = "";
+    while (i < len) {
+        /* c1 */
+        do {
+            c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
+        } while (i < len && c1 == -1);
+        if (c1 == -1)
+            break;
+        /* c2 */
+        do {
+            c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
+        } while (i < len && c2 == -1);
+        if (c2 == -1)
+            break;
+        out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
+        /* c3 */
+        do {
+            c3 = str.charCodeAt(i++) & 0xff;
+            if (c3 == 61)
+                return out;
+            c3 = base64DecodeChars[c3];
+        } while (i < len && c3 == -1);
+        if (c3 == -1)
+            break;
+        out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
+        /* c4 */
+        do {
+            c4 = str.charCodeAt(i++) & 0xff;
+            if (c4 == 61)
+                return out;
+            c4 = base64DecodeChars[c4];
+        } while (i < len && c4 == -1);
+        if (c4 == -1)
+            break;
+        out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
+    }
+    return out;
+}
+
+
+function utf16to8(str) {
+    var out, i, len, c;
+    out = "";
+    len = str.length;
+    for (i = 0; i < len; i++) {
+        c = str.charCodeAt(i);
+        if ((c >= 0x0001) && (c <= 0x007F)) {
+            out += str.charAt(i);
+        } else if (c > 0x07FF) {
+            out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
+            out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
+            out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+        } else {
+            out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
+            out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+        }
+    }
+    return out;
+}
+function utf8to16(str) {
+    var out, i, len, c;
+    var char2, char3;
+    out = "";
+    len = str.length;
+    i = 0;
+    while (i < len) {
+        c = str.charCodeAt(i++);
+        switch (c >> 4) {
+            case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
+                // 0xxxxxxx
+                out += str.charAt(i - 1);
+                break;
+            case 12: case 13:
+                // 110x xxxx 10xx xxxx
+                char2 = str.charCodeAt(i++);
+                out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
+                break;
+            case 14:
+                // 1110 xxxx 10xx xxxx 10xx xxxx
+                char2 = str.charCodeAt(i++);
+                char3 = str.charCodeAt(i++);
+                out += String.fromCharCode(((c & 0x0F) << 12) |
+                    ((char2 & 0x3F) << 6) |
+                    ((char3 & 0x3F) << 0));
+                break;
+        }
+    }
+    return out;
+}
+
+
+module.exports = {
+    encode: encode,
+    decode: decode,
+    utf16to8: utf16to8,
+    utf8to16: utf8to16
+}

+ 9 - 0
components/ossutil/config.js

@@ -0,0 +1,9 @@
+var fileHost = 'https://taohaoliang.oss-cn-beijing.aliyuncs.com';//你的阿里云地址最后面跟上一个/   在你当前小程序的后台的uploadFile 合法域名也要配上这个域名
+var config = {
+   //aliyun OSS config
+  uploadImageUrl: `${fileHost}`, // 默认存在根目录,可根据需求改
+  AccessKeySecret: 'FpClTp4OVrRRtHEfi3lBOWUoLxKieW',        // AccessKeySecret 去你的阿里云上控制台上找
+  OSSAccessKeyId: 'LTAI4G9c14PgKvM23WZ9zrpc',         // AccessKeyId 去你的阿里云上控制台上找
+   timeout: 87600 //这个是上传文件时Policy的失效时间
+};
+module.exports = config

+ 178 - 0
components/ossutil/crypto.js

@@ -0,0 +1,178 @@
+const Crypto = {};
+
+(function(){
+
+var base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+
+// Crypto utilities
+var util = Crypto.util = {
+
+	// Bit-wise rotate left
+	rotl: function (n, b) {
+		return (n << b) | (n >>> (32 - b));
+	},
+
+	// Bit-wise rotate right
+	rotr: function (n, b) {
+		return (n << (32 - b)) | (n >>> b);
+	},
+
+	// Swap big-endian to little-endian and vice versa
+	endian: function (n) {
+
+		// If number given, swap endian
+		if (n.constructor == Number) {
+			return util.rotl(n,  8) & 0x00FF00FF |
+			       util.rotl(n, 24) & 0xFF00FF00;
+		}
+
+		// Else, assume array and swap all items
+		for (var i = 0; i < n.length; i++)
+			n[i] = util.endian(n[i]);
+		return n;
+
+	},
+
+	// Generate an array of any length of random bytes
+	randomBytes: function (n) {
+		for (var bytes = []; n > 0; n--)
+			bytes.push(Math.floor(Math.random() * 256));
+		return bytes;
+	},
+
+	// Convert a string to a byte array
+	stringToBytes: function (str) {
+		var bytes = [];
+		for (var i = 0; i < str.length; i++)
+			bytes.push(str.charCodeAt(i));
+		return bytes;
+	},
+
+	// Convert a byte array to a string
+	bytesToString: function (bytes) {
+		var str = [];
+		for (var i = 0; i < bytes.length; i++)
+			str.push(String.fromCharCode(bytes[i]));
+		return str.join("");
+	},
+
+	// Convert a string to big-endian 32-bit words
+	stringToWords: function (str) {
+		var words = [];
+		for (var c = 0, b = 0; c < str.length; c++, b += 8)
+			words[b >>> 5] |= str.charCodeAt(c) << (24 - b % 32);
+		return words;
+	},
+
+	// Convert a byte array to big-endian 32-bits words
+	bytesToWords: function (bytes) {
+		var words = [];
+		for (var i = 0, b = 0; i < bytes.length; i++, b += 8)
+			words[b >>> 5] |= bytes[i] << (24 - b % 32);
+		return words;
+	},
+
+	// Convert big-endian 32-bit words to a byte array
+	wordsToBytes: function (words) {
+		var bytes = [];
+		for (var b = 0; b < words.length * 32; b += 8)
+			bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
+		return bytes;
+	},
+
+	// Convert a byte array to a hex string
+	bytesToHex: function (bytes) {
+		var hex = [];
+		for (var i = 0; i < bytes.length; i++) {
+			hex.push((bytes[i] >>> 4).toString(16));
+			hex.push((bytes[i] & 0xF).toString(16));
+		}
+		return hex.join("");
+	},
+
+	// Convert a hex string to a byte array
+	hexToBytes: function (hex) {
+		var bytes = [];
+		for (var c = 0; c < hex.length; c += 2)
+			bytes.push(parseInt(hex.substr(c, 2), 16));
+		return bytes;
+	},
+
+	// Convert a byte array to a base-64 string
+	bytesToBase64: function (bytes) {
+
+		// Use browser-native function if it exists
+		if (typeof btoa == "function") return btoa(util.bytesToString(bytes));
+
+		var base64 = [],
+		    overflow;
+
+		for (var i = 0; i < bytes.length; i++) {
+			switch (i % 3) {
+				case 0:
+					base64.push(base64map.charAt(bytes[i] >>> 2));
+					overflow = (bytes[i] & 0x3) << 4;
+					break;
+				case 1:
+					base64.push(base64map.charAt(overflow | (bytes[i] >>> 4)));
+					overflow = (bytes[i] & 0xF) << 2;
+					break;
+				case 2:
+					base64.push(base64map.charAt(overflow | (bytes[i] >>> 6)));
+					base64.push(base64map.charAt(bytes[i] & 0x3F));
+					overflow = -1;
+			}
+		}
+
+		// Encode overflow bits, if there are any
+		if (overflow != undefined && overflow != -1)
+			base64.push(base64map.charAt(overflow));
+
+		// Add padding
+		while (base64.length % 4 != 0) base64.push("=");
+
+		return base64.join("");
+
+	},
+
+	// Convert a base-64 string to a byte array
+	base64ToBytes: function (base64) {
+
+		// Use browser-native function if it exists
+		if (typeof atob == "function") return util.stringToBytes(atob(base64));
+
+		// Remove non-base-64 characters
+		base64 = base64.replace(/[^A-Z0-9+\/]/ig, "");
+
+		var bytes = [];
+
+		for (var i = 0; i < base64.length; i++) {
+			switch (i % 4) {
+				case 1:
+					bytes.push((base64map.indexOf(base64.charAt(i - 1)) << 2) |
+					           (base64map.indexOf(base64.charAt(i)) >>> 4));
+					break;
+				case 2:
+					bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0xF) << 4) |
+					           (base64map.indexOf(base64.charAt(i)) >>> 2));
+					break;
+				case 3:
+					bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0x3) << 6) |
+					           (base64map.indexOf(base64.charAt(i))));
+					break;
+			}
+		}
+
+		return bytes;
+
+	}
+
+};
+
+// Crypto mode namespace
+Crypto.mode = {};
+
+})();
+
+module.exports = Crypto;

+ 34 - 0
components/ossutil/hmac.js

@@ -0,0 +1,34 @@
+const Crypto = require('./crypto.js');
+
+(function(){
+
+// Shortcut
+var util = Crypto.util;
+
+Crypto.HMAC = function (hasher, message, key, options) {
+
+	// Allow arbitrary length keys
+	key = key.length > hasher._blocksize * 4 ?
+	      hasher(key, { asBytes: true }) :
+	      util.stringToBytes(key);
+
+	// XOR keys with pad constants
+	var okey = key,
+	    ikey = key.slice(0);
+	for (var i = 0; i < hasher._blocksize * 4; i++) {
+		okey[i] ^= 0x5C;
+		ikey[i] ^= 0x36;
+	}
+
+	var hmacbytes = hasher(util.bytesToString(okey) +
+	                       hasher(util.bytesToString(ikey) + message, { asString: true }),
+	                       { asBytes: true });
+	return options && options.asBytes ? hmacbytes :
+	       options && options.asString ? util.bytesToString(hmacbytes) :
+	       util.bytesToHex(hmacbytes);
+
+};
+
+})();
+
+module.exports = Crypto;

+ 79 - 0
components/ossutil/sha1.js

@@ -0,0 +1,79 @@
+const Crypto = require('./crypto.js');
+
+(function(){
+
+// Shortcut
+var util = Crypto.util;
+
+// Public API
+var SHA1 = Crypto.SHA1 = function (message, options) {
+	var digestbytes = util.wordsToBytes(SHA1._sha1(message));
+	return options && options.asBytes ? digestbytes :
+	       options && options.asString ? util.bytesToString(digestbytes) :
+	       util.bytesToHex(digestbytes);
+};
+
+// The core
+SHA1._sha1 = function (message) {
+
+	var m  = util.stringToWords(message),
+	    l  = message.length * 8,
+	    w  =  [],
+	    H0 =  1732584193,
+	    H1 = -271733879,
+	    H2 = -1732584194,
+	    H3 =  271733878,
+	    H4 = -1009589776;
+
+	// Padding
+	m[l >> 5] |= 0x80 << (24 - l % 32);
+	m[((l + 64 >>> 9) << 4) + 15] = l;
+
+	for (var i = 0; i < m.length; i += 16) {
+
+		var a = H0,
+		    b = H1,
+		    c = H2,
+		    d = H3,
+		    e = H4;
+
+		for (var j = 0; j < 80; j++) {
+
+			if (j < 16) w[j] = m[i + j];
+			else {
+				var n = w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16];
+				w[j] = (n << 1) | (n >>> 31);
+			}
+
+			var t = ((H0 << 5) | (H0 >>> 27)) + H4 + (w[j] >>> 0) + (
+			         j < 20 ? (H1 & H2 | ~H1 & H3) + 1518500249 :
+			         j < 40 ? (H1 ^ H2 ^ H3) + 1859775393 :
+			         j < 60 ? (H1 & H2 | H1 & H3 | H2 & H3) - 1894007588 :
+			                  (H1 ^ H2 ^ H3) - 899497514);
+
+			H4 =  H3;
+			H3 =  H2;
+			H2 = (H1 << 30) | (H1 >>> 2);
+			H1 =  H0;
+			H0 =  t;
+
+		}
+
+		H0 += a;
+		H1 += b;
+		H2 += c;
+		H3 += d;
+		H4 += e;
+
+	}
+
+	return [H0, H1, H2, H3, H4];
+
+};
+
+// Package private blocksize
+SHA1._blocksize = 16;
+
+})();
+
+module.exports = Crypto;

+ 394 - 0
components/ossutil/signature.js

@@ -0,0 +1,394 @@
+class Handwriting {
+  // 内置数据
+  ctx = '';
+  canvasWidth = 300;
+  canvasHeight = 900;
+  linePrack = []; //划线轨迹 ; 生成线条的实际点
+  currentLine = [];
+  transparent = 1; // 透明度
+  pressure = 0.5; // 默认压力
+  smoothness = 100; //顺滑度,用60的距离来计算速度
+  lineSize = 1.5; // 笔记倍数
+  lineMin = 0.5; // 最小笔画半径
+  lineMax = 2; // 最大笔画半径
+  currentPoint = {};
+  firstTouch = true; // 第一次触发
+  radius = 1; //画圆的半径
+  cutArea = {
+    top: 0,
+    right: 0,
+    bottom: 0,
+    left: 0
+  }; //裁剪区域
+  lastPoint = 0;
+  chirography = []; //笔迹
+  startY = 0;
+  deltaY = 0;
+  startValue = 0;
+  constructor(opts) {
+    this.lineColor = opts.lineColor || '#1A1A1A' // 颜色
+    this.slideValue = opts.slideValue || 50
+		this.canvasName = opts.canvasName || 'handWriting'
+    this.init()
+  }
+  init() {
+    this.ctx = uni.createCanvasContext(this.canvasName)
+    var query = uni.createSelectorQuery();
+    query.select('.handCenter').boundingClientRect(rect => {
+      console.log(rect)
+	  if(rect){
+		 this.canvasWidth = rect.width;
+		 this.canvasHeight = rect.height; 
+	  }
+      
+    }).exec();
+    this.selectSlideValue(this.slideValue);
+  }
+
+  // 笔迹开始
+  uploadScaleStart(event) {
+		let e = event.mp
+    if (e.type != 'touchstart') return false;
+    this.ctx.setFillStyle(this.lineColor); // 初始线条设置颜色
+    this.ctx.setGlobalAlpha(this.transparent); // 设置半透明
+    this.currentPoint = {
+      x: e.touches[0].x,
+      y: e.touches[0].y
+    }
+    this.currentLine.unshift({
+      time: new Date().getTime(),
+      dis: 0,
+      x: this.currentPoint.x,
+      y: this.currentPoint.y
+    })
+    if (this.firstTouch) {
+      this.cutArea = {
+        top: this.currentPoint.y,
+        right: this.currentPoint.x,
+        bottom: this.currentPoint.y,
+        left: this.currentPoint.x
+      }
+      this.firstTouch = false
+    }
+    this.pointToLine(this.currentLine);
+  }
+  // 笔迹移动
+  uploadScaleMove(event) {
+		let e = event.mp
+    if (e.type != 'touchmove') return false;
+    if (e.cancelable) {
+      // 判断默认行为是否已经被禁用
+      if (!e.defaultPrevented) {
+        e.preventDefault();
+      }
+    }
+    let point = {
+      x: e.touches[0].x,
+      y: e.touches[0].y
+    }
+    //测试裁剪
+    if (point.y < this.cutArea.top) {
+      this.cutArea.top = point.y;
+    }
+    if (point.y < 0) this.cutArea.top = 0;
+
+    if (point.x > this.cutArea.right) {
+      this.cutArea.right = point.x;
+    }
+    if (this.canvasWidth - point.x <= 0) {
+      this.cutArea.right = this.canvasWidth;
+    }
+    if (point.y > this.cutArea.bottom) {
+      this.cutArea.bottom = point.y;
+    }
+    if (this.canvasHeight - point.y <= 0) {
+      this.cutArea.bottom = this.canvasHeight;
+    }
+    if (point.x < this.cutArea.left) {
+      this.cutArea.left = point.x;
+    }
+    if (point.x < 0) this.cutArea.left = 0;
+
+    this.lastPoint = this.currentPoint;
+    this.currentPoint = point
+    this.currentLine.unshift({
+      time: new Date().getTime(),
+      dis: this.distance(this.currentPoint, this.lastPoint, 'move'),
+      x: point.x,
+      y: point.y
+    })
+    this.pointToLine(this.currentLine);
+  }
+  // 笔迹结束
+  uploadScaleEnd(event) {
+		let e = event.mp
+    if (e.type != 'touchend') return 0;
+    let point = {
+      x: e.changedTouches[0].x,
+      y: e.changedTouches[0].y
+    }
+		
+    this.lastPoint = this.currentPoint;
+    this.currentPoint = point
+    this.currentLine.unshift({
+      time: new Date().getTime(),
+      dis: this.distance(this.currentPoint, this.lastPoint, 'end'),
+      x: point.x,
+      y: point.y
+    })
+    if (this.currentLine.length > 2) {
+      var info = (this.currentLine[0].time - this.currentLine[this.currentLine.length - 1].time) / this.currentLine.length;
+      //$("#info").text(info.toFixed(2));
+    }
+    //一笔结束,保存笔迹的坐标点,清空,当前笔迹
+    //增加判断是否在手写区域;
+    this.pointToLine(this.currentLine);
+    var currentChirography = {
+      lineSize: this.lineSize,
+      lineColor: this.lineColor
+    };
+    this.chirography.unshift(currentChirography);
+    this.linePrack.unshift(this.currentLine);
+    this.currentLine = []
+  }
+  retDraw() {
+    this.ctx.clearRect(0, 0, 700, 730)
+    this.ctx.draw()
+  }
+
+  //画两点之间的线条;参数为:line,会绘制最近的开始的两个点;
+  pointToLine(line) {
+    this.calcBethelLine(line);
+    // this.calcBethelLine1(line);
+    return;
+  }
+  //计算插值的方式;
+  calcBethelLine(line) {
+    if (line.length <= 1) {
+      line[0].r = this.radius;
+      return;
+    }
+    let x0, x1, x2, y0, y1, y2, r0, r1, r2, len, lastRadius, dis = 0,
+      time = 0,
+      curveValue = 0.5;
+    if (line.length <= 2) {
+      x0 = line[1].x
+      y0 = line[1].y
+      x2 = line[1].x + (line[0].x - line[1].x) * curveValue;
+      y2 = line[1].y + (line[0].y - line[1].y) * curveValue;
+      //x2 = line[1].x;
+      //y2 = line[1].y;
+      x1 = x0 + (x2 - x0) * curveValue;
+      y1 = y0 + (y2 - y0) * curveValue;;
+
+    } else {
+      x0 = line[2].x + (line[1].x - line[2].x) * curveValue;
+      y0 = line[2].y + (line[1].y - line[2].y) * curveValue;
+      x1 = line[1].x;
+      y1 = line[1].y;
+      x2 = x1 + (line[0].x - x1) * curveValue;
+      y2 = y1 + (line[0].y - y1) * curveValue;
+    }
+    //从计算公式看,三个点分别是(x0,y0),(x1,y1),(x2,y2) ;(x1,y1)这个是控制点,控制点不会落在曲线上;实际上,这个点还会手写获取的实际点,却落在曲线上
+    len = this.distance({
+      x: x2,
+      y: y2
+    }, {
+      x: x0,
+      y: y0
+    }, 'calc');
+    lastRadius = this.radius;
+    for (let n = 0; n < line.length - 1; n++) {
+      dis += line[n].dis;
+      time += line[n].time - line[n + 1].time;
+      if (dis > this.smoothness) break;
+    }
+    this.radius = Math.min(time / len * this.pressure + this.lineMin, this.lineMax) * this.lineSize
+    line[0].r = this.radius;
+    //计算笔迹半径;
+    if (line.length <= 2) {
+      r0 = (lastRadius + this.radius) / 2;
+      r1 = r0;
+      r2 = r1;
+      //return;
+    } else {
+      r0 = (line[2].r + line[1].r) / 2;
+      r1 = line[1].r;
+      r2 = (line[1].r + line[0].r) / 2;
+    }
+    let n = 5;
+    let point = [];
+    for (let i = 0; i < n; i++) {
+      let t = i / (n - 1);
+      let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2;
+      let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2;
+      let r = lastRadius + (this.radius - lastRadius) / n * i;
+      point.push({
+        x: x,
+        y: y,
+        r: r
+      });
+      if (point.length == 3) {
+        let a = this.ctaCalc(point[0].x, point[0].y, point[0].r, point[1].x, point[1].y, point[1].r, point[2].x, point[2].y, point[2].r);
+        a[0].color = this.lineColor;
+        this.bethelDraw(a, 1);
+        point = [{
+          x: x,
+          y: y,
+          r: r
+        }];
+      }
+    }
+  }
+  //求两点之间距离
+  distance(a, b, type) {
+    let x = b.x - a.x;
+    let y = b.y - a.y;
+    return Math.sqrt(x * x + y * y) * 5;
+  }
+  ctaCalc(x0, y0, r0, x1, y1, r1, x2, y2, r2) {
+    let a = [],
+      vx01, vy01, norm, n_x0, n_y0, vx21, vy21, n_x2, n_y2;
+    vx01 = x1 - x0;
+    vy01 = y1 - y0;
+    norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2;
+    vx01 = vx01 / norm * r0;
+    vy01 = vy01 / norm * r0;
+    n_x0 = vy01;
+    n_y0 = -vx01;
+    vx21 = x1 - x2;
+    vy21 = y1 - y2;
+    norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2;
+    vx21 = vx21 / norm * r2;
+    vy21 = vy21 / norm * r2;
+    n_x2 = -vy21;
+    n_y2 = vx21;
+    a.push({
+      mx: x0 + n_x0,
+      my: y0 + n_y0,
+      color: "#1A1A1A"
+    });
+    a.push({
+      c1x: x1 + n_x0,
+      c1y: y1 + n_y0,
+      c2x: x1 + n_x2,
+      c2y: y1 + n_y2,
+      ex: x2 + n_x2,
+      ey: y2 + n_y2
+    });
+    a.push({
+      c1x: x2 + n_x2 - vx21,
+      c1y: y2 + n_y2 - vy21,
+      c2x: x2 - n_x2 - vx21,
+      c2y: y2 - n_y2 - vy21,
+      ex: x2 - n_x2,
+      ey: y2 - n_y2
+    });
+    a.push({
+      c1x: x1 - n_x2,
+      c1y: y1 - n_y2,
+      c2x: x1 - n_x0,
+      c2y: y1 - n_y0,
+      ex: x0 - n_x0,
+      ey: y0 - n_y0
+    });
+    a.push({
+      c1x: x0 - n_x0 - vx01,
+      c1y: y0 - n_y0 - vy01,
+      c2x: x0 + n_x0 - vx01,
+      c2y: y0 + n_y0 - vy01,
+      ex: x0 + n_x0,
+      ey: y0 + n_y0
+    });
+    a[0].mx = a[0].mx.toFixed(1);
+    a[0].mx = parseFloat(a[0].mx);
+    a[0].my = a[0].my.toFixed(1);
+    a[0].my = parseFloat(a[0].my);
+    for (let i = 1; i < a.length; i++) {
+      a[i].c1x = a[i].c1x.toFixed(1);
+      a[i].c1x = parseFloat(a[i].c1x);
+      a[i].c1y = a[i].c1y.toFixed(1);
+      a[i].c1y = parseFloat(a[i].c1y);
+      a[i].c2x = a[i].c2x.toFixed(1);
+      a[i].c2x = parseFloat(a[i].c2x);
+      a[i].c2y = a[i].c2y.toFixed(1);
+      a[i].c2y = parseFloat(a[i].c2y);
+      a[i].ex = a[i].ex.toFixed(1);
+      a[i].ex = parseFloat(a[i].ex);
+      a[i].ey = a[i].ey.toFixed(1);
+      a[i].ey = parseFloat(a[i].ey);
+    }
+    return a;
+  }
+  bethelDraw(point, is_fill, color) {
+    this.ctx.beginPath();
+    this.ctx.moveTo(point[0].mx, point[0].my);
+    if (undefined != color) {
+      this.ctx.setFillStyle(color);
+      this.ctx.setStrokeStyle(color);
+    } else {
+      this.ctx.setFillStyle(point[0].color);
+      this.ctx.setStrokeStyle(point[0].color);
+    }
+    for (let i = 1; i < point.length; i++) {
+      this.ctx.bezierCurveTo(point[i].c1x, point[i].c1y, point[i].c2x, point[i].c2y, point[i].ex, point[i].ey);
+    }
+    this.ctx.stroke();
+    if (undefined != is_fill) {
+      this.ctx.fill(); //填充图形 ( 后绘制的图形会覆盖前面的图形, 绘制时注意先后顺序 )
+    }
+    this.ctx.draw(true)
+  }
+
+  selectColorEvent(lineColor) {
+    this.lineColor = lineColor;
+  }
+
+  selectSlideValue(slideValue) {
+    switch (slideValue) {
+      case 0:
+        this.lineSize = 0.1;
+        this.lineMin = 0.1;
+        this.lineMax = 0.1;
+        break;
+      case 25:
+        this.lineSize = 1;
+        this.lineMin = 0.5;
+        this.lineMax = 2;
+        break;
+      case 50:
+        this.lineSize = 1.5;
+        this.lineMin = 1;
+        this.lineMax = 3;
+        break;
+      case 75:
+        this.lineSize = 1.5;
+        this.lineMin = 2;
+        this.lineMax = 3.5;
+        break;
+      case 100:
+        this.lineSize = 3;
+        this.lineMin = 2;
+        this.lineMax = 3.5;
+        break;
+    }
+  }
+	
+	saveCanvas(){
+		 return new Promise((resolve,rej) => {
+			uni.canvasToTempFilePath({
+				canvasId: this.canvasName,
+				success: function(res) {
+					console.log(res.tempFilePath)
+					resolve(res.tempFilePath);
+				},
+				 fail:function(err){
+					 console.log('图片生成失败:'+err)
+					 rej(err);
+				 }
+			})
+		})	
+	}	
+}
+
+export default Handwriting;

+ 86 - 0
components/ossutil/uploadFile.js

@@ -0,0 +1,86 @@
+const env = require('./config.js'); //配置文件,在这文件里配置你的OSS keyId和KeySecret,timeout:87600;
+
+const base64 = require('./base64.js');//Base64,hmac,sha1,crypto相关算法
+require('./hmac.js');
+require('./sha1.js');
+const Crypto = require('./crypto.js');
+
+/*
+ *上传文件到阿里云oss
+ *@param - filePath :图片的本地资源路径
+ *@param - dir:表示要传到哪个目录下
+ *@param - successc:成功回调
+ *@param - failc:失败回调
+ */ 
+const uploadFile = function (filePath, dir, successc, failc) {
+  if (!filePath || filePath.length < 9) {
+    uni.showModal({
+      title: '图片错误',
+      content: '请重试',
+      showCancel: false,
+    })
+    return;
+  }
+  
+  //图片名字 可以自行定义,     这里是采用当前的时间戳 + 150内的随机数来给图片命名的
+  const aliyunFileKey = dir + new Date().getTime() + Math.floor(Math.random() * 150) + '.png';
+  
+  const aliyunServerURL = env.uploadImageUrl;//OSS地址,需要https
+  const accessid = env.OSSAccessKeyId;
+  const policyBase64 = getPolicyBase64();
+  const signature = getSignature(policyBase64);//获取签名
+ 
+  uni.uploadFile({
+    url: aliyunServerURL,//开发者服务器 url
+    filePath: filePath,//要上传文件资源的路径
+    name: 'file',//必须填file
+    formData: {
+      'key': aliyunFileKey,
+      'policy': policyBase64,
+      'OSSAccessKeyId': accessid,
+      'signature': signature,
+      'success_action_status': '200',
+    },
+    success: function (res) {
+			console.log(res);
+      if (res.statusCode != 200) {
+        failc(new Error('上传错误:' + JSON.stringify(res)))
+        return;
+      }
+       successc(aliyunServerURL+"/"+aliyunFileKey);
+    },
+    fail: function (err) {
+      err.wxaddinfo = aliyunServerURL;
+      failc(err);
+    },
+  })
+}
+
+const getPolicyBase64 = function () {
+  let date = new Date();
+  date.setHours(date.getHours() + env.timeout);
+  let srcT = date.toISOString();
+  const policyText = {
+    "expiration": srcT, //设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了 
+    "conditions": [
+      ["content-length-range", 0, 5 * 1024 * 1024] // 设置上传文件的大小限制,5mb
+    ]
+  };
+
+  const policyBase64 = base64.encode(JSON.stringify(policyText));
+	console.log(policyBase64);
+  return policyBase64;
+}
+
+const getSignature = function (policyBase64) {
+  const accesskey = env.AccessKeySecret;
+
+  const bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, accesskey, {
+    asBytes: true
+  });
+  const signature = Crypto.util.bytesToBase64(bytes);
+console.log(signature);
+  return signature;
+}
+
+module.exports = uploadFile;

+ 228 - 0
components/redenvelope/index.vue

@@ -0,0 +1,228 @@
+<template>
+	<view>
+		<view class="tabr">
+			<view :class="{on:typeClass=='luck'}" @tap="switchType('luck')">拼手气红包</view><view :class="{on:typeClass=='normal'}"  @tap="switchType('normal')">普通红包</view>
+			<view class="border" :class="typeClass"></view>
+		</view>
+		<view class="content" :class="typeClass">
+			<view class="luck">
+				<view class="row">
+					<view class="term">
+						红包个数
+					</view>
+					<view class="input">
+						<input type="number" v-model="redPacket.number" placeholder="输入红包个数"  /> 个
+					</view>
+				</view>
+				<view class="row">
+					<view class="term">
+						总金额
+					</view>
+					<view class="input">
+						<input type="number" v-model="redPacket.money" placeholder="输入金额" /> 元
+					</view>
+				</view>
+				<view class="tis">
+					小伙伴领取的金额随机
+				</view>
+				<view class="blessing">
+					<input type="text" maxlength="12" placeholder="恭喜发财" v-model="redPacket.title"  />
+				</view>
+				<view class="hand" @tap="hand('luck')">
+					发红包
+				</view>
+			</view>
+			<view class="normal">
+				<view class="row">
+					<view class="term">
+						红包个数
+					</view>
+					<view class="input">
+						<input type="number" v-model="redPacket.number" placeholder="输入红包个数" /> 个
+					</view>
+				</view>
+				<view class="row">
+					<view class="term">
+						单个金额
+					</view>
+					<view class="input">
+						<input type="number" v-model="redPacket.money" placeholder="输入金额" /> 元
+					</view>
+				</view>
+				<view class="tis">
+					小伙伴领取的金额相同
+				</view>
+				<view class="blessing">
+					<input type="text" maxlength="12" placeholder="恭喜发财" v-model="redPacket.title"  />
+				</view>
+				<view class="hand" @tap="hand('normal')">
+					发红包
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:'red-envelope',
+		props: {
+		},
+		data() {
+			return {
+				typeClass:'luck',//normal
+				redPacket: {
+					title: '恭喜发财,大吉大利',
+					money: 0.00,
+					number: 0,
+					status: 0
+				}
+			};
+		},
+		methods:{
+			switchType(type){
+				this.typeClass = type;
+			},
+			hand(type){
+				//判断数据有效性
+				if((!this.redPacket.money)||this.redPacket.money<=0)
+				{
+					return uni.showToast({title:"金额不能为空",icon:'none'});
+				}
+				if(this.redPacket.number!=Math.abs(parseInt(this.redPacket.number))){
+					return uni.showToast({title:"数量填写大于0的整数",icon:'none'});
+				}
+				this.redPacket.title = this.redPacket.title ||'恭喜发财';
+				this.$emit('sendRedPacket',this.redPacket)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	page{
+		background-color: #F6F7F8;
+	}
+	view{
+		display: flex;
+		flex-wrap: wrap;
+	}
+	.tabr{
+		width: 94%;
+		height: 105upx;
+		padding: 0 5%;
+		border-bottom: solid 1upx #dedede;
+		view{
+			width: 50%;
+			height: 100upx;
+			justify-content: center;
+			align-items: center;
+			font-size: 16px;
+			font-weight: 600;
+			color: #171717;
+		}
+		.on{
+			color: #E3653E;
+			font-size: 16px;
+			font-weight: 600;
+		}
+		.border{
+			height: 4px;
+			background-color: #E3653E;
+			-webkit-transition: all .3s ease-out;
+			transition: all .3s ease-out;
+			margin-top: -3px;
+			border-radius: 10px;
+			&.normal{
+				transform: translate3d(100%,0,0);
+			}
+		}
+		
+	}
+	.content{
+		width: 100%;
+		height: 80vh;
+		overflow: hidden;
+		&.normal{
+			.luck{
+				transform: translate3d(-100%,0,0);
+			}
+			.normal{
+				transform: translate3d(0,-100%,0);
+			}
+		}
+		.luck,.normal{
+			
+			transition: all .3s ease-out;
+		}
+		.luck{
+			
+		}
+		.normal{
+			transform: translate3d(100%,-100%,0);
+		}
+		.row,.tis,.blessing,.hand{
+			width: 94%;
+		}
+		.row,.tis,.blessing{
+			border-bottom: #eeeeee solid 3upx;
+		}
+		.row,.blessing{
+			padding: 0 3%;
+			background-color: #fff;
+		}
+		.row,.blessing,.hand{
+			height: 100upx;
+			align-items: center;
+		}
+		.row{
+			font-size: 25rpx;
+			font-weight: 600;
+			color: #171717;
+			justify-content: space-between;
+			flex-wrap: nowrap;
+			.term,.input{
+				width: 50%;
+			}
+			.input{
+				flex-shrink: 0;
+				flex-wrap: nowrap;
+				justify-content: flex-end;
+				align-items: center;
+				input{
+					height: 50upx;
+					display: flex;
+					justify-content: flex-end;
+					align-items: center;
+					text-align: right;
+					margin-right: 20upx;
+					font-size: 30upx;
+				}
+			}
+		}
+		.tis{
+			height: 100upx;
+			padding: 30upx 3%;
+			font-size: 30upx;
+			color: #919191;
+		}
+		.blessing{
+			input{
+				width: 100%;
+				height: 50upx;
+				font-size: 30upx;
+				font-weight: 600;
+			}
+		}	
+		.hand{
+			margin: 30upx 30%;
+			color: #fff;
+			font-size: 34upx;
+			justify-content: center;
+			background-color: #F5A48A;
+			border-radius: 100upx;
+			height: 90upx;
+		}
+	}
+		
+</style>

+ 40 - 0
components/searchInput/index.vue

@@ -0,0 +1,40 @@
+<template>
+	<view class="content_search  u-border-bottom">
+		<view @tap="clickInput">
+			<u-search placeholder="搜索" shape="square" :show-action="false" :bg-color="'#ffffff'" :disabled="true"></u-search>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		searchType: {
+			type: Number,
+			default() {
+				return 0;
+			}
+		},
+	},
+	data() {
+		return {
+			
+		};
+	},
+	methods: {
+		clickInput() {
+			this.$u.route({
+				url:"pageD/search/search",
+				params: {searchType: this.searchType}
+			})
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.content_search {
+	padding: 16rpx;
+	background-color: #F6F7F8;
+}
+</style>

+ 158 - 0
components/selectInput/selectInput.vue

@@ -0,0 +1,158 @@
+<template>
+	<view class="mask"  @tap="clickMask" :class="show?'mask-show':''">
+		<view  style="position: relative;top:0;left:0;text-align:right">
+			<view class="triangle" :style="{'top':triangle_styles}" ></view>
+		</view>
+		<view class="uni-combox__selector" :style="{'top':__selector_styles}" >
+			<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
+				<view class="uni-combox__selector-item" v-for="(item, index) in list" :key="index" @tap.stop="onSelectorClick(index)" hover-class="select-hover-class" >
+					<u-icon :name="item.icon" v-if="item.icon" class="uni-combox__selector-item-icon"></u-icon>
+					<text>{{ item[listKey] }}</text>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		list: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		listKey: {
+			type: String,
+			default: ''
+		},
+		show: {
+			type: Boolean,
+			default: false
+		}
+	},
+	watch: {
+		show(val) {
+			val && this.$emit('on-open');
+		}
+	},
+	data() {
+		return {
+			statusBarHeight:0
+		};
+	},
+	methods: {
+		onSelectorClick(index) {
+			setTimeout(()=>{
+				this.$emit('on-select',index)
+				this.clickMask();
+			},80)
+		},
+		clickMask() {
+			this.$emit('update:show')
+			// #ifdef MP-WEIXIN
+			this.$emit('close',false)
+			// #endif
+		}
+	},
+	created() {
+			uni.getSystemInfo({
+				success:(res)=>{
+					//状态栏的高度 将用来适配刘海屏
+					this.statusBarHeight = res.statusBarHeight;
+				}
+			})
+	},
+	computed:{
+		__selector_styles(){
+			// #ifdef MP-WEIXIN
+			return this.statusBarHeight+48+uni.upx2px(10)+'px'
+			// #endif
+			// #ifndef MP-WEIXIN
+			return '10rpx'
+			// #endif
+		},
+		triangle_styles(){
+			// #ifdef MP-WEIXIN
+			return this.statusBarHeight+48+uni.upx2px(1)+'px'
+			// #endif
+			// #ifndef MP-WEIXIN
+			return '0rpx'
+			// #endif
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.mask {
+	width: 100%;
+	height: 100%;
+	overflow: hidden;
+	position: absolute;
+	top: 0;
+	left: 0;
+	z-index: 0;
+	background-color: transparent;
+	box-sizing: border-box;
+	opacity: 0;
+	transition: all 0.15s ease-in-out;
+}
+.mask-show{
+	opacity: 1;
+	z-index: 1000;
+}
+.uni-combox__selector {
+	position: absolute;
+	top: 10rpx;
+	right: 20rpx;
+	/* #ifdef MP-WEIXIN */
+	right: 40rpx;
+	/* #endif */
+	box-sizing: border-box;
+	width: 28%; // 下拉框宽度
+	background-color: $u-main-color;
+	border-radius: 3px;
+	box-shadow: #dddddd 2px 2px 4px, #dddddd -2px -2px 4px;
+	z-index: 998;
+}
+
+.triangle{
+	position: absolute;
+	width: 0;
+	height: 0;
+	// $u-main-color
+	border-bottom: solid 5px $u-main-color;
+	border-right: solid 5px transparent;
+	border-left: solid 5px transparent;
+	right: 30rpx;
+	top: 0rpx;
+	/* #ifdef MP-WEIXIN */
+	right: 29%;
+	/* #endif */
+	z-index: 999;
+}
+
+.uni-combox__selector-scroll {
+	max-height: 150px;
+	box-sizing: border-box;
+}
+
+.uni-combox__selector-item {
+	/* #ifdef APP-NVUE || MP-WEIXIN */
+	display: flex;
+	/* #endif */
+	font-size: 24rpx;
+	padding: 30rpx ;
+	color: #ffffff;
+	border-bottom: 1rpx solid $u-type-info;
+	&-icon {
+		padding-right: 10rpx;
+	}
+}
+
+.uni-combox__selector-item:last-child {
+	border-bottom: none;
+}
+</style>

+ 134 - 0
components/swiperup.vue

@@ -0,0 +1,134 @@
+<template>
+	<view class="wrap">
+		 <swiper
+	  id="_swiper_up"
+	  :indicator-dots="indicatorDots"
+	  :autoplay="autoplay" :interval="interval" :duration="duration" vertical="true" circular="true">
+	  <block v-for="(item,index) in list" :key="index">
+	    <swiper-item>
+			<view class="itme_text">
+				<view>
+					<image class='gb' src='https://taohaoliang.oss-cn-beijing.aliyuncs.com/tmp/gb.png'></image>
+					<image class='horn' src='https://taohaoliang.oss-cn-beijing.aliyuncs.com/tmp/horn.png'></image>{{item.port}}{{item.goodsName}}&nbsp;&nbsp;({{item.newOld}})&nbsp;&nbsp;{{item.price}}元/吨
+				</view>
+			</view>
+	    </swiper-item>
+	  </block>
+	</swiper>
+	</view>
+	
+</template>
+
+<script>
+	export default {
+		name: 'swiper_up',
+		data() {
+			return {
+				typeList: {
+					left: 'icon-zuo',
+					right: 'icon-you',
+					up: 'icon-shang',
+					down: 'icon-xia'
+				},
+			}
+		},
+		props: {
+			indicatorDots: {
+				type: Boolean,
+				default: false
+			},
+			autoplay: {
+				type: Boolean,
+				default: true
+			},
+			interval: {
+				type: String,
+				default: '3000'
+			},
+			duration: {
+				type: String,
+				default: '1000'
+			},
+			list: {
+				type: Array,
+				default: []
+			},
+		},
+		created(){
+			console.log(this.list,this.autoplay)
+		},
+		onLoad(){
+			console.log(this.list)
+		},
+		methods: {
+			eventClick() {
+				this.$emit('eventClick');
+			}
+		},
+	}
+</script>
+
+<style scoped>
+	.wrap{
+		height: 64rpx;
+		margin-bottom: 20rpx;
+		background: #fff;
+		line-height: 64rpx;
+		position: relative;
+		width:84%;
+		margin:0 auto;
+		margin-top:12px;
+		box-shadow: 2px 2px 4px rgba(0,0,0,0.07);
+	}
+	.wrap::before{
+		content: "";
+		position: absolute;
+		background: #fff;
+		display: block;
+		width: 35px;
+		height: 35px;
+		left: -15px;
+		z-index: 1;
+		-webkit-border-radius: 50%;
+		border-radius: 50%;
+	}
+	.wrap::after{
+		content: "";
+		position: absolute;
+		background: #fff;
+		display: block;
+		width: 35px;
+		height: 35px;
+		right: -15px;
+		z-index: 1;
+		-webkit-border-radius: 50%;
+		border-radius: 50%;
+	}
+	.gb{
+		width:27px;
+		height:21px;
+		vertical-align: middle;
+	}
+	.horn{
+		width:14px;height:14px;
+		vertical-align: middle;
+		margin-right: 10px;
+	}
+	swiper {
+	  width: 100%;
+	  height: 68rpx;
+	  position: absolute;
+	  left: 0rpx;
+	  top: 0rpx;
+	   pointer-events: none;
+	    cursor: default;
+		z-index:2;
+	}
+	 .cuIcon-notification{
+		 margin-right:10rpx;
+	 }
+	.itme_text {
+	  font-size: 26rpx;
+	  z-index: 999;
+	}
+</style>

+ 104 - 0
components/tarbar.vue

@@ -0,0 +1,104 @@
+<template>
+	<view class="tarbar">
+		<view v-for='(item,index) in datas' @tap="tabClick(index,item.urls,item.id)">
+			<!-- <u-icon :color="item.color" :name="item.icon" size="60"></u-icon> -->
+			<image v-if='i==index' style='width:40rpx;height:40rpx;margin-top:10rpx;' :src='item.actionIcon'></image>
+			<image v-else style='width:40rpx;height:40rpx;margin-top:10rpx;' :src='item.icon'></image>
+			<view v-text="item.name"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props:["totarBer"],//从父组件传来的值 来控制点击当前的颜色
+		data() {
+			return {
+				i:0,
+				datas: [{
+						urls: '/pages/home/home',
+						icon: '/static/menu/chat.png',
+						actionIcon: '/static/menu/chat_s.png',
+						name: '消息',
+						id: 1,
+					},
+					{
+						urls: '/pages/addressBook/addressBook',
+						icon: '/static/menu/contact.png',
+						actionIcon: '/static/menu/contact_s.png',
+						name: '通讯录',
+						id: 2,
+					},
+					{
+						urls: '/pageC/find/find',
+						icon: '/static/menu/see.png',
+						actionIcon: '/static/menu/see_s.png',
+						name: '发现',
+						id: 3,
+					},
+					{
+						urls: '/pageC/my/index',
+						icon: '/static/menu/my.png',
+						actionIcon: '/static/menu/my_s.png',
+						name: '我',
+						id: 4,
+					},
+				],
+			}
+		},
+		created() {
+			this.i=this.totarBer.id-1
+		//用来控制是显示当前页面的tarBar图标变亮
+			if(this.totarBer.id==2){
+				this.$set(this.datas[1],'color',this.totarBer.color)
+			}
+			if(this.totarBer.id==1){
+				this.$set(this.datas[0],'color',this.totarBer.color)
+			}
+			if(this.totarBer.id==3){
+				this.$set(this.datas[2],'color',this.totarBer.color)
+			}
+			if(this.totarBer.id==4){
+				this.$set(this.datas[3],'color',this.totarBer.color)
+			}
+			
+			
+		},
+		methods: {
+			tabClick(i,urls, id) {
+				console.log(11111111)
+				this.i=i
+				// if(i==0){
+					uni.switchTab({
+						url: urls
+					});
+				// }else{
+				// 	uni.reLaunch({
+				// 		url: urls
+				// 	});
+				// }
+				
+            
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tarbar {
+		width: 100%;
+		height: 96rpx;
+		display: flex;
+		justify-content: space-around;
+		background-color: #FFFFFF;
+		position: fixed;
+		bottom: 0;
+		z-index: 100;
+		font-size: 26rpx;
+		color: #999999;
+	}
+
+	.tarbar view {
+		text-align: center;
+	}
+</style>

+ 283 - 0
components/u-avatar-cropper/u-avatar-cropper.vue

@@ -0,0 +1,283 @@
+<template>
+	<view class="content">
+		<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				@touchstart="touchStart"
+				@touchmove="touchMove"
+				@touchend="touchEnd"
+				:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
+				canvas-id="cropper"
+			></canvas>
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				:style="{
+					position: 'fixed',
+					top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
+					width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					height: `${cropperOpt.height * cropperOpt.pixelRatio}`
+				}"
+				canvas-id="targetId"
+			></canvas>
+		</view>
+		<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
+			<!-- #ifdef H5 -->
+			<view class="upload" @tap="uploadTap">选择图片</view>
+			<!-- #endif -->
+			<!-- #ifndef H5 -->
+			<view class="upload" @tap="uploadTap">重新选择</view>
+			<!-- #endif -->
+			<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import WeCropper from './weCropper.js';
+export default {
+	props: {
+		// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
+		// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
+		boundStyle: {
+			type: Object,
+			default() {
+				return {
+					lineWidth: 4,
+					borderColor: 'rgb(245, 245, 245)',
+					mask: 'rgba(0, 0, 0, 0.35)'
+				};
+			}
+		}
+		// // 裁剪框宽度,单位rpx
+		// rectWidth: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 裁剪框高度,单位rpx
+		// rectHeight: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出图片宽度,单位rpx
+		// destWidth: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出图片高度,单位rpx
+		// destHeight: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可
+		// fileType: {
+		// 	type: String,
+		// 	default: 'jpg',
+		// },
+		// // 生成的图片质量
+		// // H5上无效,目前不考虑使用此参数
+		// quality: {
+		// 	type: [Number, String],
+		// 	default: 1
+		// }
+	},
+	data() {
+		return {
+			// 底部导航的高度
+			bottomNavHeight: 50,
+			originWidth: 200,
+			width: 0,
+			height: 0,
+			cropperOpt: {
+				id: 'cropper',
+				targetId: 'targetCropper',
+				pixelRatio: 1,
+				width: 0,
+				height: 0,
+				scale: 2.5,
+				zoom: 8,
+				cut: {
+					x: (this.width - this.originWidth) / 2,
+					y: (this.height - this.originWidth) / 2,
+					width: this.originWidth,
+					height: this.originWidth
+				},
+				boundStyle: {
+					lineWidth: uni.upx2px(this.boundStyle.lineWidth),
+					mask: this.boundStyle.mask,
+					color: this.boundStyle.borderColor
+				}
+			},
+			// 裁剪框和输出图片的尺寸,高度默认等于宽度
+			// 输出图片宽度,单位px
+			destWidth: 200,
+			// 裁剪框宽度,单位px
+			rectWidth: 200,
+			// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
+			fileType: 'jpg'
+		};
+	},
+	onLoad(option) {
+		let rectInfo = uni.getSystemInfoSync();
+		this.width = rectInfo.windowWidth;
+		this.height = rectInfo.windowHeight - this.bottomNavHeight;
+		this.cropperOpt.width = this.width;
+		this.cropperOpt.height = this.height;
+		this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
+
+		if (option.destWidth) this.destWidth = option.destWidth;
+		if (option.rectWidth) {
+			let rectWidth = Number(option.rectWidth);
+			this.cropperOpt.cut = {
+				x: (this.width - rectWidth) / 2,
+				y: (this.height - rectWidth) / 2,
+				width: rectWidth,
+				height: rectWidth
+			};
+		}
+		this.rectWidth = option.rectWidth;
+		if (option.fileType) this.fileType = option.fileType;
+		// 初始化
+		this.cropper = new WeCropper(this.cropperOpt)
+			.on('ready', ctx => {
+				// console.log(`wecropper is ready for work!`)
+			})
+			.on('beforeImageLoad', ctx => {
+				// console.log(`before picture loaded, i can do something`)
+			})
+			.on('imageLoad', ctx => {
+				// console.log(`picture loaded`)
+			})
+			.on('beforeDraw', (ctx, instance) => {
+				// console.log(`before canvas draw,i can do something`)
+			});
+		// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
+		uni.setNavigationBarColor({
+			frontColor: '#ffffff',
+			backgroundColor: '#000000'
+		});
+		uni.chooseImage({
+			count: 1, // 默认9
+			sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
+			sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+			success: res => {
+				let src = res.tempFilePaths[0];
+				//  获取裁剪图片资源后,给data添加src属性及其值
+				this.cropper.pushOrign(src);
+			}
+		});
+	},
+	methods: {
+		touchStart(e) {
+			this.cropper.touchStart(e);
+		},
+		touchMove(e) {
+			this.cropper.touchMove(e);
+		},
+		touchEnd(e) {
+			this.cropper.touchEnd(e);
+		},
+		getCropperImage(isPre = false) {
+			let cropper_opt = {
+				destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值
+				destWidth: Number(this.destWidth),
+				fileType: this.fileType
+			};
+			this.cropper.getCropperImage(cropper_opt, (path, err) => {
+				if (err) {
+					uni.showModal({
+						title: '温馨提示',
+						content: err.message
+					});
+				} else {
+					if (isPre) {
+						uni.previewImage({
+							current: '', // 当前显示图片的 http 链接
+							urls: [path] // 需要预览的图片 http 链接列表
+						});
+					} else {
+						uni.$emit('uAvatarCropper', path);
+						this.$u.route({
+							type: 'back'
+						});
+					}
+				}
+			});
+		},
+		uploadTap() {
+			const self = this;
+			uni.chooseImage({
+				count: 1, // 默认9
+				sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
+				sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+				success(res) {
+					const src = res.tempFilePaths[0];
+					//  获取裁剪图片资源后,给data添加src属性及其值
+
+					self.cropper.pushOrign(src);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.content {
+	background: rgba(255, 255, 255, 1);
+}
+
+.cropper {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 99999999999999;
+}
+
+.cropper-buttons {
+	background-color: #000000;
+	color: #eee;
+}
+
+.cropper-wrapper {
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	width: 100%;
+	background-color: #000;
+}
+
+.cropper-buttons {
+	width: 100vw;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	font-size: 28rpx;
+}
+
+.cropper-buttons .upload,
+.cropper-buttons .getCropperImage {
+	width: 50%;
+	text-align: center;
+}
+
+.cropper-buttons .upload {
+	text-align: left;
+	padding-left: 50rpx;
+}
+
+.cropper-buttons .getCropperImage {
+	text-align: right;
+	padding-right: 50rpx;
+}
+</style>

+ 1265 - 0
components/u-avatar-cropper/weCropper.js

@@ -0,0 +1,1265 @@
+/**
+ * we-cropper v1.3.9
+ * (c) 2020 dlhandsome
+ * @license MIT
+ */
+(function(global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+		typeof define === 'function' && define.amd ? define(factory) :
+		(global.WeCropper = factory());
+}(this, (function() {
+	'use strict';
+
+	var device = void 0;
+	var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
+
+	function firstLetterUpper(str) {
+		return str.charAt(0).toUpperCase() + str.slice(1)
+	}
+
+	function setTouchState(instance) {
+		var arg = [],
+			len = arguments.length - 1;
+		while (len-- > 0) arg[len] = arguments[len + 1];
+
+		TOUCH_STATE.forEach(function(key, i) {
+			if (arg[i] !== undefined) {
+				instance[key] = arg[i];
+			}
+		});
+	}
+
+	function validator(instance, o) {
+		Object.defineProperties(instance, o);
+	}
+
+	function getDevice() {
+		if (!device) {
+			device = uni.getSystemInfoSync();
+		}
+		return device
+	}
+
+	var tmp = {};
+
+	var ref = getDevice();
+	var pixelRatio = ref.pixelRatio;
+
+	var DEFAULT = {
+		id: {
+			default: 'cropper',
+			get: function get() {
+				return tmp.id
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("id:" + value + " is invalid"));
+				}
+				tmp.id = value;
+			}
+		},
+		width: {
+			default: 750,
+			get: function get() {
+				return tmp.width
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("width:" + value + " is invalid"));
+				}
+				tmp.width = value;
+			}
+		},
+		height: {
+			default: 750,
+			get: function get() {
+				return tmp.height
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("height:" + value + " is invalid"));
+				}
+				tmp.height = value;
+			}
+		},
+		pixelRatio: {
+			default: pixelRatio,
+			get: function get() {
+				return tmp.pixelRatio
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("pixelRatio:" + value + " is invalid"));
+				}
+				tmp.pixelRatio = value;
+			}
+		},
+		scale: {
+			default: 2.5,
+			get: function get() {
+				return tmp.scale
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("scale:" + value + " is invalid"));
+				}
+				tmp.scale = value;
+			}
+		},
+		zoom: {
+			default: 5,
+			get: function get() {
+				return tmp.zoom
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("zoom:" + value + " is invalid"));
+				} else if (value < 0 || value > 10) {
+					console.error("zoom should be ranged in 0 ~ 10");
+				}
+				tmp.zoom = value;
+			}
+		},
+		src: {
+			default: '',
+			get: function get() {
+				return tmp.src
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("src:" + value + " is invalid"));
+				}
+				tmp.src = value;
+			}
+		},
+		cut: {
+			default: {},
+			get: function get() {
+				return tmp.cut
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("cut:" + value + " is invalid"));
+				}
+				tmp.cut = value;
+			}
+		},
+		boundStyle: {
+			default: {},
+			get: function get() {
+				return tmp.boundStyle
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("boundStyle:" + value + " is invalid"));
+				}
+				tmp.boundStyle = value;
+			}
+		},
+		onReady: {
+			default: null,
+			get: function get() {
+				return tmp.ready
+			},
+			set: function set(value) {
+				tmp.ready = value;
+			}
+		},
+		onBeforeImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.beforeImageLoad
+			},
+			set: function set(value) {
+				tmp.beforeImageLoad = value;
+			}
+		},
+		onImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.imageLoad
+			},
+			set: function set(value) {
+				tmp.imageLoad = value;
+			}
+		},
+		onBeforeDraw: {
+			default: null,
+			get: function get() {
+				return tmp.beforeDraw
+			},
+			set: function set(value) {
+				tmp.beforeDraw = value;
+			}
+		}
+	};
+
+	var ref$1 = getDevice();
+	var windowWidth = ref$1.windowWidth;
+
+	function prepare() {
+		var self = this;
+
+		// v1.4.0 版本中将不再自动绑定we-cropper实例
+		self.attachPage = function() {
+			var pages = getCurrentPages();
+			// 获取到当前page上下文
+			var pageContext = pages[pages.length - 1];
+			// 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
+			Object.defineProperty(pageContext, 'wecropper', {
+				get: function get() {
+					console.warn(
+						'Instance will not be automatically bound to the page after v1.4.0\n\n' +
+						'Please use a custom instance name instead\n\n' +
+						'Example: \n' +
+						'this.mycropper = new WeCropper(options)\n\n' +
+						'// ...\n' +
+						'this.mycropper.getCropperImage()'
+					);
+					return self
+				},
+				configurable: true
+			});
+		};
+
+		self.createCtx = function() {
+			var id = self.id;
+			var targetId = self.targetId;
+
+			if (id) {
+				self.ctx = self.ctx || uni.createCanvasContext(id);
+				self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId);
+			} else {
+				console.error("constructor: create canvas context failed, 'id' must be valuable");
+			}
+		};
+
+		self.deviceRadio = windowWidth / 750;
+	}
+
+	var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !==
+		'undefined' ? self : {};
+
+
+
+
+
+	function createCommonjsModule(fn, module) {
+		return module = {
+			exports: {}
+		}, fn(module, module.exports), module.exports;
+	}
+
+	var tools = createCommonjsModule(function(module, exports) {
+		/**
+		 * String type check
+		 */
+		exports.isStr = function(v) {
+			return typeof v === 'string';
+		};
+		/**
+		 * Number type check
+		 */
+		exports.isNum = function(v) {
+			return typeof v === 'number';
+		};
+		/**
+		 * Array type check
+		 */
+		exports.isArr = Array.isArray;
+		/**
+		 * undefined type check
+		 */
+		exports.isUndef = function(v) {
+			return v === undefined;
+		};
+
+		exports.isTrue = function(v) {
+			return v === true;
+		};
+
+		exports.isFalse = function(v) {
+			return v === false;
+		};
+		/**
+		 * Function type check
+		 */
+		exports.isFunc = function(v) {
+			return typeof v === 'function';
+		};
+		/**
+		 * Quick object check - this is primarily used to tell
+		 * Objects from primitive values when we know the value
+		 * is a JSON-compliant type.
+		 */
+		exports.isObj = exports.isObject = function(obj) {
+			return obj !== null && typeof obj === 'object'
+		};
+
+		/**
+		 * Strict object type check. Only returns true
+		 * for plain JavaScript objects.
+		 */
+		var _toString = Object.prototype.toString;
+		exports.isPlainObject = function(obj) {
+			return _toString.call(obj) === '[object Object]'
+		};
+
+		/**
+		 * Check whether the object has the property.
+		 */
+		var hasOwnProperty = Object.prototype.hasOwnProperty;
+		exports.hasOwn = function(obj, key) {
+			return hasOwnProperty.call(obj, key)
+		};
+
+		/**
+		 * Perform no operation.
+		 * Stubbing args to make Flow happy without leaving useless transpiled code
+		 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
+		 */
+		exports.noop = function(a, b, c) {};
+
+		/**
+		 * Check if val is a valid array index.
+		 */
+		exports.isValidArrayIndex = function(val) {
+			var n = parseFloat(String(val));
+			return n >= 0 && Math.floor(n) === n && isFinite(val)
+		};
+	});
+
+	var tools_7 = tools.isFunc;
+	var tools_10 = tools.isPlainObject;
+
+	var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
+
+	function observer() {
+		var self = this;
+
+		self.on = function(event, fn) {
+			if (EVENT_TYPE.indexOf(event) > -1) {
+				if (tools_7(fn)) {
+					event === 'ready' ?
+						fn(self) :
+						self[("on" + (firstLetterUpper(event)))] = fn;
+				}
+			} else {
+				console.error(("event: " + event + " is invalid"));
+			}
+			return self
+		};
+	}
+
+	function wxPromise(fn) {
+		return function(obj) {
+			var args = [],
+				len = arguments.length - 1;
+			while (len-- > 0) args[len] = arguments[len + 1];
+
+			if (obj === void 0) obj = {};
+			return new Promise(function(resolve, reject) {
+				obj.success = function(res) {
+					resolve(res);
+				};
+				obj.fail = function(err) {
+					reject(err);
+				};
+				fn.apply(void 0, [obj].concat(args));
+			})
+		}
+	}
+
+	function draw(ctx, reserve) {
+		if (reserve === void 0) reserve = false;
+
+		return new Promise(function(resolve) {
+			ctx.draw(reserve, resolve);
+		})
+	}
+
+	var getImageInfo = wxPromise(uni.getImageInfo);
+
+	var canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath);
+
+	var base64 = createCommonjsModule(function(module, exports) {
+		/*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
+		(function(root) {
+
+			// Detect free variables `exports`.
+			var freeExports = 'object' == 'object' && exports;
+
+			// Detect free variable `module`.
+			var freeModule = 'object' == 'object' && module &&
+				module.exports == freeExports && module;
+
+			// Detect free variable `global`, from Node.js or Browserified code, and use
+			// it as `root`.
+			var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal;
+			if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+				root = freeGlobal;
+			}
+
+			/*--------------------------------------------------------------------------*/
+
+			var InvalidCharacterError = function(message) {
+				this.message = message;
+			};
+			InvalidCharacterError.prototype = new Error;
+			InvalidCharacterError.prototype.name = 'InvalidCharacterError';
+
+			var error = function(message) {
+				// Note: the error messages used throughout this file match those used by
+				// the native `atob`/`btoa` implementation in Chromium.
+				throw new InvalidCharacterError(message);
+			};
+
+			var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+			// http://whatwg.org/html/common-microsyntaxes.html#space-character
+			var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g;
+
+			// `decode` is designed to be fully compatible with `atob` as described in the
+			// HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
+			// The optimized base64-decoding algorithm used is based on @atk’s excellent
+			// implementation. https://gist.github.com/atk/1020396
+			var decode = function(input) {
+				input = String(input)
+					.replace(REGEX_SPACE_CHARACTERS, '');
+				var length = input.length;
+				if (length % 4 == 0) {
+					input = input.replace(/==?$/, '');
+					length = input.length;
+				}
+				if (
+					length % 4 == 1 ||
+					// http://whatwg.org/C#alphanumeric-ascii-characters
+					/[^+a-zA-Z0-9/]/.test(input)
+				) {
+					error(
+						'Invalid character: the string to be decoded is not correctly encoded.'
+					);
+				}
+				var bitCounter = 0;
+				var bitStorage;
+				var buffer;
+				var output = '';
+				var position = -1;
+				while (++position < length) {
+					buffer = TABLE.indexOf(input.charAt(position));
+					bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
+					// Unless this is the first of a group of 4 characters…
+					if (bitCounter++ % 4) {
+						// …convert the first 8 bits to a single ASCII character.
+						output += String.fromCharCode(
+							0xFF & bitStorage >> (-2 * bitCounter & 6)
+						);
+					}
+				}
+				return output;
+			};
+
+			// `encode` is designed to be fully compatible with `btoa` as described in the
+			// HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
+			var encode = function(input) {
+				input = String(input);
+				if (/[^\0-\xFF]/.test(input)) {
+					// Note: no need to special-case astral symbols here, as surrogates are
+					// matched, and the input is supposed to only contain ASCII anyway.
+					error(
+						'The string to be encoded contains characters outside of the ' +
+						'Latin1 range.'
+					);
+				}
+				var padding = input.length % 3;
+				var output = '';
+				var position = -1;
+				var a;
+				var b;
+				var c;
+				var buffer;
+				// Make sure any padding is handled outside of the loop.
+				var length = input.length - padding;
+
+				while (++position < length) {
+					// Read three bytes, i.e. 24 bits.
+					a = input.charCodeAt(position) << 16;
+					b = input.charCodeAt(++position) << 8;
+					c = input.charCodeAt(++position);
+					buffer = a + b + c;
+					// Turn the 24 bits into four chunks of 6 bits each, and append the
+					// matching character for each of them to the output.
+					output += (
+						TABLE.charAt(buffer >> 18 & 0x3F) +
+						TABLE.charAt(buffer >> 12 & 0x3F) +
+						TABLE.charAt(buffer >> 6 & 0x3F) +
+						TABLE.charAt(buffer & 0x3F)
+					);
+				}
+
+				if (padding == 2) {
+					a = input.charCodeAt(position) << 8;
+					b = input.charCodeAt(++position);
+					buffer = a + b;
+					output += (
+						TABLE.charAt(buffer >> 10) +
+						TABLE.charAt((buffer >> 4) & 0x3F) +
+						TABLE.charAt((buffer << 2) & 0x3F) +
+						'='
+					);
+				} else if (padding == 1) {
+					buffer = input.charCodeAt(position);
+					output += (
+						TABLE.charAt(buffer >> 2) +
+						TABLE.charAt((buffer << 4) & 0x3F) +
+						'=='
+					);
+				}
+
+				return output;
+			};
+
+			var base64 = {
+				'encode': encode,
+				'decode': decode,
+				'version': '0.1.0'
+			};
+
+			// Some AMD build optimizers, like r.js, check for specific condition patterns
+			// like the following:
+			if (
+				typeof undefined == 'function' &&
+				typeof undefined.amd == 'object' &&
+				undefined.amd
+			) {
+				undefined(function() {
+					return base64;
+				});
+			} else if (freeExports && !freeExports.nodeType) {
+				if (freeModule) { // in Node.js or RingoJS v0.8.0+
+					freeModule.exports = base64;
+				} else { // in Narwhal or RingoJS v0.7.0-
+					for (var key in base64) {
+						base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
+					}
+				}
+			} else { // in Rhino or a web browser
+				root.base64 = base64;
+			}
+
+		}(commonjsGlobal));
+	});
+
+	function makeURI(strData, type) {
+		return 'data:' + type + ';base64,' + strData
+	}
+
+	function fixType(type) {
+		type = type.toLowerCase().replace(/jpg/i, 'jpeg');
+		var r = type.match(/png|jpeg|bmp|gif/)[0];
+		return 'image/' + r
+	}
+
+	function encodeData(data) {
+		var str = '';
+		if (typeof data === 'string') {
+			str = data;
+		} else {
+			for (var i = 0; i < data.length; i++) {
+				str += String.fromCharCode(data[i]);
+			}
+		}
+		return base64.encode(str)
+	}
+
+	/**
+	 * 获取图像区域隐含的像素数据
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param done 完成回调
+	 */
+	function getImageData(canvasId, x, y, width, height, done) {
+		uni.canvasGetImageData({
+			canvasId: canvasId,
+			x: x,
+			y: y,
+			width: width,
+			height: height,
+			success: function success(res) {
+				done(res, null);
+			},
+			fail: function fail(res) {
+				done(null, res);
+			}
+		});
+	}
+
+	/**
+	 * 生成bmp格式图片
+	 * 按照规则生成图片响应头和响应体
+	 * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData
+	 * @returns {*} base64字符串
+	 */
+	function genBitmapImage(oData) {
+		//
+		// BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
+		// BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
+		//
+		var biWidth = oData.width;
+		var biHeight = oData.height;
+		var biSizeImage = biWidth * biHeight * 3;
+		var bfSize = biSizeImage + 54; // total header size = 54 bytes
+
+		//
+		//  typedef struct tagBITMAPFILEHEADER {
+		//  	WORD bfType;
+		//  	DWORD bfSize;
+		//  	WORD bfReserved1;
+		//  	WORD bfReserved2;
+		//  	DWORD bfOffBits;
+		//  } BITMAPFILEHEADER;
+		//
+		var BITMAPFILEHEADER = [
+			// WORD bfType -- The file type signature; must be "BM"
+			0x42, 0x4D,
+			// DWORD bfSize -- The size, in bytes, of the bitmap file
+			bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
+			// WORD bfReserved1 -- Reserved; must be zero
+			0, 0,
+			// WORD bfReserved2 -- Reserved; must be zero
+			0, 0,
+			// DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
+			54, 0, 0, 0
+		];
+
+		//
+		//  typedef struct tagBITMAPINFOHEADER {
+		//  	DWORD biSize;
+		//  	LONG  biWidth;
+		//  	LONG  biHeight;
+		//  	WORD  biPlanes;
+		//  	WORD  biBitCount;
+		//  	DWORD biCompression;
+		//  	DWORD biSizeImage;
+		//  	LONG  biXPelsPerMeter;
+		//  	LONG  biYPelsPerMeter;
+		//  	DWORD biClrUsed;
+		//  	DWORD biClrImportant;
+		//  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
+		//
+		var BITMAPINFOHEADER = [
+			// DWORD biSize -- The number of bytes required by the structure
+			40, 0, 0, 0,
+			// LONG biWidth -- The width of the bitmap, in pixels
+			biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
+			// LONG biHeight -- The height of the bitmap, in pixels
+			biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
+			// WORD biPlanes -- The number of planes for the target device. This value must be set to 1
+			1, 0,
+			// WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
+			// has a maximum of 2^24 colors (16777216, Truecolor)
+			24, 0,
+			// DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
+			0, 0, 0, 0,
+			// DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
+			biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
+			// LONG biXPelsPerMeter, unused
+			0, 0, 0, 0,
+			// LONG biYPelsPerMeter, unused
+			0, 0, 0, 0,
+			// DWORD biClrUsed, the number of color indexes of palette, unused
+			0, 0, 0, 0,
+			// DWORD biClrImportant, unused
+			0, 0, 0, 0
+		];
+
+		var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
+
+		var aImgData = oData.data;
+
+		var strPixelData = '';
+		var biWidth4 = biWidth << 2;
+		var y = biHeight;
+		var fromCharCode = String.fromCharCode;
+
+		do {
+			var iOffsetY = biWidth4 * (y - 1);
+			var strPixelRow = '';
+			for (var x = 0; x < biWidth; x++) {
+				var iOffsetX = x << 2;
+				strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX]);
+			}
+
+			for (var c = 0; c < iPadding; c++) {
+				strPixelRow += String.fromCharCode(0);
+			}
+
+			strPixelData += strPixelRow;
+		} while (--y)
+
+		var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
+
+		return strEncoded
+	}
+
+	/**
+	 * 转换为图片base64
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param type 转换图片类型
+	 * @param done 完成回调
+	 */
+	function convertToImage(canvasId, x, y, width, height, type, done) {
+		if (done === void 0) done = function() {};
+
+		if (type === undefined) {
+			type = 'png';
+		}
+		type = fixType(type);
+		if (/bmp/.test(type)) {
+			getImageData(canvasId, x, y, width, height, function(data, err) {
+				var strData = genBitmapImage(data);
+				tools_7(done) && done(makeURI(strData, 'image/' + type), err);
+			});
+		} else {
+			console.error('暂不支持生成\'' + type + '\'类型的base64图片');
+		}
+	}
+
+	var CanvasToBase64 = {
+		convertToImage: convertToImage,
+		// convertToPNG: function (width, height, done) {
+		//   return convertToImage(width, height, 'png', done)
+		// },
+		// convertToJPEG: function (width, height, done) {
+		//   return convertToImage(width, height, 'jpeg', done)
+		// },
+		// convertToGIF: function (width, height, done) {
+		//   return convertToImage(width, height, 'gif', done)
+		// },
+		convertToBMP: function(ref, done) {
+			if (ref === void 0) ref = {};
+			var canvasId = ref.canvasId;
+			var x = ref.x;
+			var y = ref.y;
+			var width = ref.width;
+			var height = ref.height;
+			if (done === void 0) done = function() {};
+
+			return convertToImage(canvasId, x, y, width, height, 'bmp', done)
+		}
+	};
+
+	function methods() {
+		var self = this;
+
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
+
+		var id = self.id;
+		var targetId = self.targetId;
+		var pixelRatio = self.pixelRatio;
+
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		self.updateCanvas = function(done) {
+			if (self.croperTarget) {
+				//  画布绘制图片
+				self.ctx.drawImage(
+					self.croperTarget,
+					self.imgLeft,
+					self.imgTop,
+					self.scaleWidth,
+					self.scaleHeight
+				);
+			}
+			tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
+
+			self.setBoundStyle(self.boundStyle); //	设置边界样式
+
+			self.ctx.draw(false, done);
+			return self
+		};
+
+		self.pushOrigin = self.pushOrign = function(src) {
+			self.src = src;
+
+			tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
+
+			return getImageInfo({
+					src: src
+				})
+				.then(function(res) {
+					var innerAspectRadio = res.width / res.height;
+					var customAspectRadio = width / height;
+
+					self.croperTarget = res.path;
+
+					if (innerAspectRadio < customAspectRadio) {
+						self.rectX = x;
+						self.baseWidth = width;
+						self.baseHeight = width / innerAspectRadio;
+						self.rectY = y - Math.abs((height - self.baseHeight) / 2);
+					} else {
+						self.rectY = y;
+						self.baseWidth = height * innerAspectRadio;
+						self.baseHeight = height;
+						self.rectX = x - Math.abs((width - self.baseWidth) / 2);
+					}
+
+					self.imgLeft = self.rectX;
+					self.imgTop = self.rectY;
+					self.scaleWidth = self.baseWidth;
+					self.scaleHeight = self.baseHeight;
+
+					self.update();
+
+					return new Promise(function(resolve) {
+						self.updateCanvas(resolve);
+					})
+				})
+				.then(function() {
+					tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self);
+				})
+		};
+
+		self.removeImage = function() {
+			self.src = '';
+			self.croperTarget = '';
+			return draw(self.ctx)
+		};
+
+		self.getCropperBase64 = function(done) {
+			if (done === void 0) done = function() {};
+
+			CanvasToBase64.convertToBMP({
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			}, done);
+		};
+
+		self.getCropperImage = function(opt, fn) {
+			var customOptions = opt;
+
+			var canvasOptions = {
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			};
+
+			var task = function() {
+				return Promise.resolve();
+			};
+
+			if (
+				tools_10(customOptions) &&
+				customOptions.original
+			) {
+				// original mode
+				task = function() {
+					self.targetCtx.drawImage(
+						self.croperTarget,
+						self.imgLeft * pixelRatio,
+						self.imgTop * pixelRatio,
+						self.scaleWidth * pixelRatio,
+						self.scaleHeight * pixelRatio
+					);
+
+					canvasOptions = {
+						canvasId: targetId,
+						x: x * pixelRatio,
+						y: y * pixelRatio,
+						width: width * pixelRatio,
+						height: height * pixelRatio
+					};
+
+					return draw(self.targetCtx)
+				};
+			}
+
+			return task()
+				.then(function() {
+					if (tools_10(customOptions)) {
+						canvasOptions = Object.assign({}, canvasOptions, customOptions);
+					}
+
+					if (tools_7(customOptions)) {
+						fn = customOptions;
+					}
+
+					var arg = canvasOptions.componentContext ?
+						[canvasOptions, canvasOptions.componentContext] :
+						[canvasOptions];
+
+					return canvasToTempFilePath.apply(null, arg)
+				})
+				.then(function(res) {
+					var tempFilePath = res.tempFilePath;
+
+					return tools_7(fn) ?
+						fn.call(self, tempFilePath, null) :
+						tempFilePath
+				})
+				.catch(function(err) {
+					if (tools_7(fn)) {
+						fn.call(self, null, err);
+					} else {
+						throw err
+					}
+				})
+		};
+	}
+
+	/**
+	 * 获取最新缩放值
+	 * @param oldScale 上一次触摸结束后的缩放值
+	 * @param oldDistance 上一次触摸结束后的双指距离
+	 * @param zoom 缩放系数
+	 * @param touch0 第一指touch对象
+	 * @param touch1 第二指touch对象
+	 * @returns {*}
+	 */
+	var getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1) {
+		var xMove, yMove, newDistance;
+		// 计算二指最新距离
+		xMove = Math.round(touch1.x - touch0.x);
+		yMove = Math.round(touch1.y - touch0.y);
+		newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+		return oldScale + 0.001 * zoom * (newDistance - oldDistance)
+	};
+
+	function update() {
+		var self = this;
+
+		if (!self.src) {
+			return
+		}
+
+		self.__oneTouchStart = function(touch) {
+			self.touchX0 = Math.round(touch.x);
+			self.touchY0 = Math.round(touch.y);
+		};
+
+		self.__oneTouchMove = function(touch) {
+			var xMove, yMove;
+			// 计算单指移动的距离
+			if (self.touchended) {
+				return self.updateCanvas()
+			}
+			xMove = Math.round(touch.x - self.touchX0);
+			yMove = Math.round(touch.y - self.touchY0);
+
+			var imgLeft = Math.round(self.rectX + xMove);
+			var imgTop = Math.round(self.rectY + yMove);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__twoTouchStart = function(touch0, touch1) {
+			var xMove, yMove, oldDistance;
+
+			self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2);
+			self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2);
+
+			// 计算两指距离
+			xMove = Math.round(touch1.x - touch0.x);
+			yMove = Math.round(touch1.y - touch0.y);
+			oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+			self.oldDistance = oldDistance;
+		};
+
+		self.__twoTouchMove = function(touch0, touch1) {
+			var oldScale = self.oldScale;
+			var oldDistance = self.oldDistance;
+			var scale = self.scale;
+			var zoom = self.zoom;
+
+			self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
+
+			//  设定缩放范围
+			self.newScale <= 1 && (self.newScale = 1);
+			self.newScale >= scale && (self.newScale = scale);
+
+			self.scaleWidth = Math.round(self.newScale * self.baseWidth);
+			self.scaleHeight = Math.round(self.newScale * self.baseHeight);
+			var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
+			var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__xtouchEnd = function() {
+			self.oldScale = self.newScale;
+			self.rectX = self.imgLeft;
+			self.rectY = self.imgTop;
+		};
+	}
+
+	var handle = {
+		//  图片手势初始监测
+		touchStart: function touchStart(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, true, null, null);
+
+			// 计算第一个触摸点的位置,并参照改点进行缩放
+			self.__oneTouchStart(touch0);
+
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchStart(touch0, touch1);
+			}
+		},
+
+		//  图片手势动态缩放
+		touchMove: function touchMove(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, null, true);
+
+			// 单指手势时触发
+			if (e.touches.length === 1) {
+				self.__oneTouchMove(touch0);
+			}
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchMove(touch0, touch1);
+			}
+		},
+
+		touchEnd: function touchEnd(e) {
+			var self = this;
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, false, false, true);
+			self.__xtouchEnd();
+		}
+	};
+
+	function cut() {
+		var self = this;
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height;
+		// 裁剪框默认高度,即整个画布高度
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		/**
+		 * 设置边界
+		 * @param imgLeft 图片左上角横坐标值
+		 * @param imgTop 图片左上角纵坐标值
+		 */
+		self.outsideBound = function(imgLeft, imgTop) {
+			self.imgLeft = imgLeft >= x ?
+				x :
+				self.scaleWidth + imgLeft - x <= width ?
+				x + width - self.scaleWidth :
+				imgLeft;
+
+			self.imgTop = imgTop >= y ?
+				y :
+				self.scaleHeight + imgTop - y <= height ?
+				y + height - self.scaleHeight :
+				imgTop;
+		};
+
+		/**
+		 * 设置边界样式
+		 * @param color	边界颜色
+		 */
+		self.setBoundStyle = function(ref) {
+			if (ref === void 0) ref = {};
+			var color = ref.color;
+			if (color === void 0) color = '#04b00f';
+			var mask = ref.mask;
+			if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)';
+			var lineWidth = ref.lineWidth;
+			if (lineWidth === void 0) lineWidth = 1;
+
+			var half = lineWidth / 2;
+			var boundOption = [{
+					start: {
+						x: x - half,
+						y: y + 10 - half
+					},
+					step1: {
+						x: x - half,
+						y: y - half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y - half
+					}
+				},
+				{
+					start: {
+						x: x - half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x - half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y + height + half
+					}
+				},
+				{
+					start: {
+						x: x + width - 10 + half,
+						y: y - half
+					},
+					step1: {
+						x: x + width + half,
+						y: y - half
+					},
+					step2: {
+						x: x + width + half,
+						y: y + 10 - half
+					}
+				},
+				{
+					start: {
+						x: x + width + half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x + width + half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + width - 10 + half,
+						y: y + height + half
+					}
+				}
+			];
+
+			// 绘制半透明层
+			self.ctx.beginPath();
+			self.ctx.setFillStyle(mask);
+			self.ctx.fillRect(0, 0, x, boundHeight);
+			self.ctx.fillRect(x, 0, width, y);
+			self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
+			self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
+			self.ctx.fill();
+
+			boundOption.forEach(function(op) {
+				self.ctx.beginPath();
+				self.ctx.setStrokeStyle(color);
+				self.ctx.setLineWidth(lineWidth);
+				self.ctx.moveTo(op.start.x, op.start.y);
+				self.ctx.lineTo(op.step1.x, op.step1.y);
+				self.ctx.lineTo(op.step2.x, op.step2.y);
+				self.ctx.stroke();
+			});
+		};
+	}
+
+	var version = "1.3.9";
+
+	var WeCropper = function WeCropper(params) {
+		var self = this;
+		var _default = {};
+
+		validator(self, DEFAULT);
+
+		Object.keys(DEFAULT).forEach(function(key) {
+			_default[key] = DEFAULT[key].default;
+		});
+		Object.assign(self, _default, params);
+
+		self.prepare();
+		self.attachPage();
+		self.createCtx();
+		self.observer();
+		self.cutt();
+		self.methods();
+		self.init();
+		self.update();
+
+		return self
+	};
+
+	WeCropper.prototype.init = function init() {
+		var self = this;
+		var src = self.src;
+
+		self.version = version;
+
+		typeof self.onReady === 'function' && self.onReady(self.ctx, self);
+
+		if (src) {
+			self.pushOrign(src);
+		} else {
+			self.updateCanvas();
+		}
+		setTouchState(self, false, false, false);
+
+		self.oldScale = 1;
+		self.newScale = 1;
+
+		return self
+	};
+
+	Object.assign(WeCropper.prototype, handle);
+
+	WeCropper.prototype.prepare = prepare;
+	WeCropper.prototype.observer = observer;
+	WeCropper.prototype.methods = methods;
+	WeCropper.prototype.cutt = cut;
+	WeCropper.prototype.update = update;
+
+	return WeCropper;
+
+})));

+ 27 - 0
components/u-parse/components/wxParseAudio.vue

@@ -0,0 +1,27 @@
+<template>
+  <!--增加audio标签支持-->
+  <audio
+    :id="node.attr.id"
+    :class="node.classStr"
+    :style="node.styleStr"
+    :src="node.attr.src"
+    :loop="node.attr.loop"
+    :poster="node.attr.poster"
+    :name="node.attr.name"
+    :author="node.attr.author"
+    controls></audio>
+</template>
+
+<script>
+export default {
+  name: 'wxParseAudio',
+  props: {
+    node: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+};
+</script>

+ 86 - 0
components/u-parse/components/wxParseImg.vue

@@ -0,0 +1,86 @@
+<template>
+  <image
+    :mode="node.attr.mode"
+    :lazy-load="node.attr.lazyLoad"
+    :class="node.classStr"
+    :style="newStyleStr || node.styleStr"
+    :data-src="node.attr.src"
+    :src="node.attr.src"
+    @tap="wxParseImgTap"
+    @load="wxParseImgLoad"
+    />
+</template>
+
+<script>
+export default {
+  name: 'wxParseImg',
+  data() {
+    return {
+      newStyleStr: '',
+      preview: true,
+    };
+  },
+  props: {
+    node: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  methods: {
+    wxParseImgTap(e) {
+      if (!this.preview) return;
+      const { src } = e.currentTarget.dataset;
+      if (!src) return;
+      let parent = this.$parent;
+      while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
+      	parent = parent.$parent;
+      }
+      parent.preview(src, e);
+    },
+    // 图片视觉宽高计算函数区
+    wxParseImgLoad(e) {
+      const { src } = e.currentTarget.dataset;
+      if (!src) return;
+      const { width, height } = e.mp.detail;
+      const recal = this.wxAutoImageCal(width, height);
+      const { imageheight, imageWidth } = recal;
+      const { padding, mode } = this.node.attr;
+      const { styleStr } = this.node;
+      const imageHeightStyle = mode === 'widthFix' ? '' : `height: ${imageheight}px;`;
+      this.newStyleStr = `${styleStr}; ${imageHeightStyle}; width: ${imageWidth}px; padding: 0 ${+padding}px;`;
+    },
+    // 计算视觉优先的图片宽高
+    wxAutoImageCal(originalWidth, originalHeight) {
+      // 获取图片的原始长宽
+      const { padding } = this.node.attr;
+      const windowWidth = this.node.$screen.width - (2 * padding);
+      const results = {};
+
+      if (originalWidth < 60 || originalHeight < 60) {
+        const { src } = this.node.attr;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.removeImageUrl(src);
+        this.preview = false;
+      }
+
+      // 判断按照那种方式进行缩放
+      if (originalWidth > windowWidth) {
+        // 在图片width大于手机屏幕width时候
+        results.imageWidth = windowWidth;
+        results.imageheight = windowWidth * (originalHeight / originalWidth);
+      } else {
+        // 否则展示原来的数据
+        results.imageWidth = originalWidth;
+        results.imageheight = originalHeight;
+      }
+
+      return results;
+    },
+  },
+};
+</script>

+ 107 - 0
components/u-parse/components/wxParseTemplate0.vue

@@ -0,0 +1,107 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--table类型-->
+			<block v-else-if="node.tag == 'table'">
+				<view :class="node.classStr" class="table" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate1';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate0',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;// TODO currentTarget才有dataset
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 99 - 0
components/u-parse/components/wxParseTemplate1.vue

@@ -0,0 +1,99 @@
+<template>
+	<view :class="(node.tag == 'li' ? node.classStr : (node.node==='text'?'text':''))">
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<!-- <view :class="node.classStr" :style="node.styleStr"> -->
+				<view :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate2';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate1',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 97 - 0
components/u-parse/components/wxParseTemplate10.vue

@@ -0,0 +1,97 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate11';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate10',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 87 - 0
components/u-parse/components/wxParseTemplate11.vue

@@ -0,0 +1,87 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<!--button类型-->
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					{{node.text}}
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					{{node.text}}
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					{{node.text}}
+				</view>
+			</block>
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate11',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate2.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate3';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate2',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate3.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate4';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate3',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate4.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate5';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate4',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate5.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate6';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate5',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate6.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate7';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate6',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate7.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate8';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate7',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate8.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate9';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate8',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
components/u-parse/components/wxParseTemplate9.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate10';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate9',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 15 - 0
components/u-parse/components/wxParseVideo.vue

@@ -0,0 +1,15 @@
+<template>
+  <!--增加video标签支持,并循环添加-->
+  <view :class="node.classStr" :style="node.styleStr">
+    <video :class="node.classStr" class="video-video" :src="node.attr.src"></video>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'wxParseVideo',
+  props: {
+    node: {},
+  },
+};
+</script>

+ 261 - 0
components/u-parse/libs/html2json.js

@@ -0,0 +1,261 @@
+/**
+ * html2Json 改造来自: https://github.com/Jxck/html2json
+ *
+ *
+ * author: Di (微信小程序开发工程师)
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
+ *               垂直微信小程序开发交流社区
+ *
+ * github地址: https://github.com/icindy/wxParse
+ *
+ * for: 微信小程序富文本解析
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
+ */
+
+import wxDiscode from './wxDiscode';
+import HTMLParser from './htmlparser';
+
+function makeMap(str) {
+  const obj = {};
+  const items = str.split(',');
+  for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+  return obj;
+}
+
+// Block Elements - HTML 5
+const block = makeMap('br,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
+
+// Inline Elements - HTML 5
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
+
+// Elements that you can, intentionally, leave open
+// (and which close themselves)
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+function removeDOCTYPE(html) {
+  const isDocument = /<body.*>([^]*)<\/body>/.test(html);
+  return isDocument ? RegExp.$1 : html;
+}
+
+function trimHtml(html) {
+  return html
+    .replace(/<!--.*?-->/gi, '')
+    .replace(/\/\*.*?\*\//gi, '')
+    .replace(/[ ]+</gi, '<')
+    .replace(/<script[^]*<\/script>/gi, '')
+    .replace(/<style[^]*<\/style>/gi, '');
+}
+
+function getScreenInfo() {
+  const screen = {};
+  wx.getSystemInfo({
+    success: (res) => {
+      screen.width = res.windowWidth;
+      screen.height = res.windowHeight;
+    },
+  });
+  return screen;
+}
+
+function html2json(html, customHandler, imageProp, host) {
+  // 处理字符串
+  html = removeDOCTYPE(html);
+  html = trimHtml(html);
+  html = wxDiscode.strDiscode(html);
+  // 生成node节点
+  const bufArray = [];
+  const results = {
+    nodes: [],
+    imageUrls: [],
+  };
+
+	const screen = getScreenInfo();
+  function Node(tag) {
+    this.node = 'element';
+    this.tag = tag;
+		
+		this.$screen = screen;
+  }
+
+  HTMLParser(html, {
+    start(tag, attrs, unary) {
+      // node for this element
+      const node = new Node(tag);
+
+      if (bufArray.length !== 0) {
+        const parent = bufArray[0];
+        if (parent.nodes === undefined) {
+          parent.nodes = [];
+        }
+      }
+
+      if (block[tag]) {
+        node.tagType = 'block';
+      } else if (inline[tag]) {
+        node.tagType = 'inline';
+      } else if (closeSelf[tag]) {
+        node.tagType = 'closeSelf';
+      }
+
+      node.attr = attrs.reduce((pre, attr) => {
+        const { name } = attr;
+        let { value } = attr;
+        if (name === 'class') {
+          node.classStr = value;
+        }
+        // has multi attibutes
+        // make it array of attribute
+        if (name === 'style') {
+          node.styleStr = value;
+        }
+        if (value.match(/ /)) {
+          value = value.split(' ');
+        }
+
+        // if attr already exists
+        // merge it
+        if (pre[name]) {
+          if (Array.isArray(pre[name])) {
+            // already array, push to last
+            pre[name].push(value);
+          } else {
+            // single value, make it array
+            pre[name] = [pre[name], value];
+          }
+        } else {
+          // not exist, put it
+          pre[name] = value;
+        }
+
+        return pre;
+      }, {});
+
+      // 优化样式相关属性
+      if (node.classStr) {
+        node.classStr += ` ${node.tag}`;
+      } else {
+        node.classStr = node.tag;
+      }
+      if (node.tagType === 'inline') {
+        node.classStr += ' inline';
+      }
+
+      // 对img添加额外数据
+      if (node.tag === 'img') {
+        let imgUrl = node.attr.src;
+        imgUrl = wxDiscode.urlToHttpUrl(imgUrl, imageProp.domain);
+        Object.assign(node.attr, imageProp, {
+          src: imgUrl || '',
+        });
+        if (imgUrl) {
+          results.imageUrls.push(imgUrl);
+        }
+      }
+
+      // 处理a标签属性
+      if (node.tag === 'a') {
+        node.attr.href = node.attr.href || '';
+      }
+
+      // 处理font标签样式属性
+      if (node.tag === 'font') {
+        const fontSize = [
+          'x-small',
+          'small',
+          'medium',
+          'large',
+          'x-large',
+          'xx-large',
+          '-webkit-xxx-large',
+        ];
+        const styleAttrs = {
+          color: 'color',
+          face: 'font-family',
+          size: 'font-size',
+        };
+        if (!node.styleStr) node.styleStr = '';
+        Object.keys(styleAttrs).forEach((key) => {
+          if (node.attr[key]) {
+            const value = key === 'size' ? fontSize[node.attr[key] - 1] : node.attr[key];
+            node.styleStr += `${styleAttrs[key]}: ${value};`;
+          }
+        });
+      }
+
+      // 临时记录source资源
+      if (node.tag === 'source') {
+        results.source = node.attr.src;
+      }
+
+      if (customHandler.start) {
+        customHandler.start(node, results);
+      }
+
+      if (unary) {
+        // if this tag doesn't have end tag
+        // like <img src="hoge.png"/>
+        // add to parents
+        const parent = bufArray[0] || results;
+        if (parent.nodes === undefined) {
+          parent.nodes = [];
+        }
+        parent.nodes.push(node);
+      } else {
+        bufArray.unshift(node);
+      }
+    },
+    end(tag) {
+      // merge into parent tag
+      const node = bufArray.shift();
+      if (node.tag !== tag) {
+        console.error('invalid state: mismatch end tag');
+      }
+
+      // 当有缓存source资源时于于video补上src资源
+      if (node.tag === 'video' && results.source) {
+        node.attr.src = results.source;
+        delete results.source;
+      }
+
+      if (customHandler.end) {
+        customHandler.end(node, results);
+      }
+
+      if (bufArray.length === 0) {
+        results.nodes.push(node);
+      } else {
+        const parent = bufArray[0];
+        if (!parent.nodes) {
+          parent.nodes = [];
+        }
+        parent.nodes.push(node);
+      }
+    },
+    chars(text) {
+      if (!text.trim()) return;
+
+      const node = {
+        node: 'text',
+        text,
+      };
+
+      if (customHandler.chars) {
+        customHandler.chars(node, results);
+      }
+
+      if (bufArray.length === 0) {
+        results.nodes.push(node);
+      } else {
+        const parent = bufArray[0];
+        if (parent.nodes === undefined) {
+          parent.nodes = [];
+        }
+        parent.nodes.push(node);
+      }
+    },
+  });
+
+  return results;
+}
+
+export default html2json;

+ 156 - 0
components/u-parse/libs/htmlparser.js

@@ -0,0 +1,156 @@
+/**
+ *
+ * htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
+ *
+ * author: Di (微信小程序开发工程师)
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
+ *               垂直微信小程序开发交流社区
+ *
+ * github地址: https://github.com/icindy/wxParse
+ *
+ * for: 微信小程序富文本解析
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
+ */
+// Regular Expressions for parsing tags and attributes
+
+const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z0-9_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+const attr = /([a-zA-Z0-9_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+
+function makeMap(str) {
+  const obj = {};
+  const items = str.split(',');
+  for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+  return obj;
+}
+
+// Empty Elements - HTML 5
+const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
+
+// Block Elements - HTML 5
+const block = makeMap('address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
+
+// Inline Elements - HTML 5
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
+
+// Elements that you can, intentionally, leave open
+// (and which close themselves)
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+// Attributes that have their values filled in disabled="disabled"
+const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
+
+function HTMLParser(html, handler) {
+  let index;
+  let chars;
+  let match;
+  let last = html;
+  const stack = [];
+
+  stack.last = () => stack[stack.length - 1];
+
+  function parseEndTag(tag, tagName) {
+    // If no tag name is provided, clean shop
+    let pos;
+    if (!tagName) {
+      pos = 0;
+    } else {
+      // Find the closest opened tag of the same type
+      tagName = tagName.toLowerCase();
+      for (pos = stack.length - 1; pos >= 0; pos -= 1) {
+        if (stack[pos] === tagName) break;
+      }
+    }
+    if (pos >= 0) {
+      // Close all the open elements, up the stack
+      for (let i = stack.length - 1; i >= pos; i -= 1) {
+        if (handler.end) handler.end(stack[i]);
+      }
+
+      // Remove the open elements from the stack
+      stack.length = pos;
+    }
+  }
+
+  function parseStartTag(tag, tagName, rest, unary) {
+    tagName = tagName.toLowerCase();
+
+    if (block[tagName]) {
+      while (stack.last() && inline[stack.last()]) {
+        parseEndTag('', stack.last());
+      }
+    }
+
+    if (closeSelf[tagName] && stack.last() === tagName) {
+      parseEndTag('', tagName);
+    }
+
+    unary = empty[tagName] || !!unary;
+
+    if (!unary) stack.push(tagName);
+
+    if (handler.start) {
+      const attrs = [];
+
+      rest.replace(attr, function genAttr(matches, name) {
+        const value = arguments[2] || arguments[3] || arguments[4] || (fillAttrs[name] ? name : '');
+
+        attrs.push({
+          name,
+          value,
+          escaped: value.replace(/(^|[^\\])"/g, '$1\\"'), // "
+        });
+      });
+
+      if (handler.start) {
+        handler.start(tagName, attrs, unary);
+      }
+    }
+  }
+
+  while (html) {
+    chars = true;
+
+    if (html.indexOf('</') === 0) {
+      match = html.match(endTag);
+
+      if (match) {
+        html = html.substring(match[0].length);
+        match[0].replace(endTag, parseEndTag);
+        chars = false;
+      }
+
+      // start tag
+    } else if (html.indexOf('<') === 0) {
+      match = html.match(startTag);
+
+      if (match) {
+        html = html.substring(match[0].length);
+        match[0].replace(startTag, parseStartTag);
+        chars = false;
+      }
+    }
+
+    if (chars) {
+      index = html.indexOf('<');
+      let text = '';
+      while (index === 0) {
+        text += '<';
+        html = html.substring(1);
+        index = html.indexOf('<');
+      }
+      text += index < 0 ? html : html.substring(0, index);
+      html = index < 0 ? '' : html.substring(index);
+
+      if (handler.chars) handler.chars(text);
+    }
+
+    if (html === last) throw new Error(`Parse Error: ${html}`);
+    last = html;
+  }
+
+  // Clean up any remaining tags
+  parseEndTag();
+}
+
+export default HTMLParser;

+ 195 - 0
components/u-parse/libs/wxDiscode.js

@@ -0,0 +1,195 @@
+// HTML 支持的数学符号
+function strNumDiscode(str) {
+  str = str.replace(/&forall;/g, '∀');
+  str = str.replace(/&part;/g, '∂');
+  str = str.replace(/&exist;/g, '∃');
+  str = str.replace(/&empty;/g, '∅');
+  str = str.replace(/&nabla;/g, '∇');
+  str = str.replace(/&isin;/g, '∈');
+  str = str.replace(/&notin;/g, '∉');
+  str = str.replace(/&ni;/g, '∋');
+  str = str.replace(/&prod;/g, '∏');
+  str = str.replace(/&sum;/g, '∑');
+  str = str.replace(/&minus;/g, '−');
+  str = str.replace(/&lowast;/g, '∗');
+  str = str.replace(/&radic;/g, '√');
+  str = str.replace(/&prop;/g, '∝');
+  str = str.replace(/&infin;/g, '∞');
+  str = str.replace(/&ang;/g, '∠');
+  str = str.replace(/&and;/g, '∧');
+  str = str.replace(/&or;/g, '∨');
+  str = str.replace(/&cap;/g, '∩');
+  str = str.replace(/&cup;/g, '∪');
+  str = str.replace(/&int;/g, '∫');
+  str = str.replace(/&there4;/g, '∴');
+  str = str.replace(/&sim;/g, '∼');
+  str = str.replace(/&cong;/g, '≅');
+  str = str.replace(/&asymp;/g, '≈');
+  str = str.replace(/&ne;/g, '≠');
+  str = str.replace(/&le;/g, '≤');
+  str = str.replace(/&ge;/g, '≥');
+  str = str.replace(/&sub;/g, '⊂');
+  str = str.replace(/&sup;/g, '⊃');
+  str = str.replace(/&nsub;/g, '⊄');
+  str = str.replace(/&sube;/g, '⊆');
+  str = str.replace(/&supe;/g, '⊇');
+  str = str.replace(/&oplus;/g, '⊕');
+  str = str.replace(/&otimes;/g, '⊗');
+  str = str.replace(/&perp;/g, '⊥');
+  str = str.replace(/&sdot;/g, '⋅');
+  return str;
+}
+
+// HTML 支持的希腊字母
+function strGreeceDiscode(str) {
+  str = str.replace(/&Alpha;/g, 'Α');
+  str = str.replace(/&Beta;/g, 'Β');
+  str = str.replace(/&Gamma;/g, 'Γ');
+  str = str.replace(/&Delta;/g, 'Δ');
+  str = str.replace(/&Epsilon;/g, 'Ε');
+  str = str.replace(/&Zeta;/g, 'Ζ');
+  str = str.replace(/&Eta;/g, 'Η');
+  str = str.replace(/&Theta;/g, 'Θ');
+  str = str.replace(/&Iota;/g, 'Ι');
+  str = str.replace(/&Kappa;/g, 'Κ');
+  str = str.replace(/&Lambda;/g, 'Λ');
+  str = str.replace(/&Mu;/g, 'Μ');
+  str = str.replace(/&Nu;/g, 'Ν');
+  str = str.replace(/&Xi;/g, 'Ν');
+  str = str.replace(/&Omicron;/g, 'Ο');
+  str = str.replace(/&Pi;/g, 'Π');
+  str = str.replace(/&Rho;/g, 'Ρ');
+  str = str.replace(/&Sigma;/g, 'Σ');
+  str = str.replace(/&Tau;/g, 'Τ');
+  str = str.replace(/&Upsilon;/g, 'Υ');
+  str = str.replace(/&Phi;/g, 'Φ');
+  str = str.replace(/&Chi;/g, 'Χ');
+  str = str.replace(/&Psi;/g, 'Ψ');
+  str = str.replace(/&Omega;/g, 'Ω');
+
+  str = str.replace(/&alpha;/g, 'α');
+  str = str.replace(/&beta;/g, 'β');
+  str = str.replace(/&gamma;/g, 'γ');
+  str = str.replace(/&delta;/g, 'δ');
+  str = str.replace(/&epsilon;/g, 'ε');
+  str = str.replace(/&zeta;/g, 'ζ');
+  str = str.replace(/&eta;/g, 'η');
+  str = str.replace(/&theta;/g, 'θ');
+  str = str.replace(/&iota;/g, 'ι');
+  str = str.replace(/&kappa;/g, 'κ');
+  str = str.replace(/&lambda;/g, 'λ');
+  str = str.replace(/&mu;/g, 'μ');
+  str = str.replace(/&nu;/g, 'ν');
+  str = str.replace(/&xi;/g, 'ξ');
+  str = str.replace(/&omicron;/g, 'ο');
+  str = str.replace(/&pi;/g, 'π');
+  str = str.replace(/&rho;/g, 'ρ');
+  str = str.replace(/&sigmaf;/g, 'ς');
+  str = str.replace(/&sigma;/g, 'σ');
+  str = str.replace(/&tau;/g, 'τ');
+  str = str.replace(/&upsilon;/g, 'υ');
+  str = str.replace(/&phi;/g, 'φ');
+  str = str.replace(/&chi;/g, 'χ');
+  str = str.replace(/&psi;/g, 'ψ');
+  str = str.replace(/&omega;/g, 'ω');
+  str = str.replace(/&thetasym;/g, 'ϑ');
+  str = str.replace(/&upsih;/g, 'ϒ');
+  str = str.replace(/&piv;/g, 'ϖ');
+  str = str.replace(/&middot;/g, '·');
+  return str;
+}
+
+function strcharacterDiscode(str) {
+  // 加入常用解析
+  str = str.replace(/&nbsp;/g, ' ');
+  str = str.replace(/&ensp;/g, ' ');
+  str = str.replace(/&emsp;/g, ' ');
+  str = str.replace(/&quot;/g, "'");
+  str = str.replace(/&amp;/g, '&');
+  str = str.replace(/&lt;/g, '<');
+  str = str.replace(/&gt;/g, '>');
+  str = str.replace(/&#8226;/g, '•');
+
+  return str;
+}
+
+// HTML 支持的其他实体
+function strOtherDiscode(str) {
+  str = str.replace(/&OElig;/g, 'Œ');
+  str = str.replace(/&oelig;/g, 'œ');
+  str = str.replace(/&Scaron;/g, 'Š');
+  str = str.replace(/&scaron;/g, 'š');
+  str = str.replace(/&Yuml;/g, 'Ÿ');
+  str = str.replace(/&fnof;/g, 'ƒ');
+  str = str.replace(/&circ;/g, 'ˆ');
+  str = str.replace(/&tilde;/g, '˜');
+  str = str.replace(/&ensp;/g, '');
+  str = str.replace(/&emsp;/g, '');
+  str = str.replace(/&thinsp;/g, '');
+  str = str.replace(/&zwnj;/g, '');
+  str = str.replace(/&zwj;/g, '');
+  str = str.replace(/&lrm;/g, '');
+  str = str.replace(/&rlm;/g, '');
+  str = str.replace(/&ndash;/g, '–');
+  str = str.replace(/&mdash;/g, '—');
+  str = str.replace(/&lsquo;/g, '‘');
+  str = str.replace(/&rsquo;/g, '’');
+  str = str.replace(/&sbquo;/g, '‚');
+  str = str.replace(/&ldquo;/g, '“');
+  str = str.replace(/&rdquo;/g, '”');
+  str = str.replace(/&bdquo;/g, '„');
+  str = str.replace(/&dagger;/g, '†');
+  str = str.replace(/&Dagger;/g, '‡');
+  str = str.replace(/&bull;/g, '•');
+  str = str.replace(/&hellip;/g, '…');
+  str = str.replace(/&permil;/g, '‰');
+  str = str.replace(/&prime;/g, '′');
+  str = str.replace(/&Prime;/g, '″');
+  str = str.replace(/&lsaquo;/g, '‹');
+  str = str.replace(/&rsaquo;/g, '›');
+  str = str.replace(/&oline;/g, '‾');
+  str = str.replace(/&euro;/g, '€');
+  str = str.replace(/&trade;/g, '™');
+
+  str = str.replace(/&larr;/g, '←');
+  str = str.replace(/&uarr;/g, '↑');
+  str = str.replace(/&rarr;/g, '→');
+  str = str.replace(/&darr;/g, '↓');
+  str = str.replace(/&harr;/g, '↔');
+  str = str.replace(/&crarr;/g, '↵');
+  str = str.replace(/&lceil;/g, '⌈');
+  str = str.replace(/&rceil;/g, '⌉');
+
+  str = str.replace(/&lfloor;/g, '⌊');
+  str = str.replace(/&rfloor;/g, '⌋');
+  str = str.replace(/&loz;/g, '◊');
+  str = str.replace(/&spades;/g, '♠');
+  str = str.replace(/&clubs;/g, '♣');
+  str = str.replace(/&hearts;/g, '♥');
+
+  str = str.replace(/&diams;/g, '♦');
+  str = str.replace(/&#39;/g, "'");
+  return str;
+}
+
+function strDiscode(str) {
+  str = strNumDiscode(str);
+  str = strGreeceDiscode(str);
+  str = strcharacterDiscode(str);
+  str = strOtherDiscode(str);
+  return str;
+}
+
+function urlToHttpUrl(url, domain) {
+  if (/^\/\//.test(url)) {
+    return `https:${url}`;
+  } else if (/^\//.test(url)) {
+    return `https://${domain}${url}`;
+  }
+  return url;
+}
+
+export default {
+  strDiscode,
+  urlToHttpUrl,
+};

+ 102 - 0
components/u-parse/readme.md

@@ -0,0 +1,102 @@
+## uParse 适用于 uni-app/mpvue 的富文本解析组件
+
+> 支持 Html、Markdown 解析,Fork自: [mpvue-wxParse](https://github.com/F-loat/mpvue-wxParse)
+
+
+## 属性
+
+| 名称             | 类型          | 默认值        | 描述               |
+| -----------------|--------------- | ------------- | ----------------  |
+| loading          | Boolean        | false         | 数据加载状态       |
+| className        | String         | —             | 自定义 class 名称  |
+| content          | String         | —             | 渲染内容           |
+| noData           | String         | 数据不能为空   | 空数据时的渲染展示  |
+| startHandler     | Function       | 见源码         | 自定义 parser 函数 |
+| endHandler       | Function       | null          | 自定义 parser 函数 |
+| charsHandler     | Function       | null          | 自定义 parser 函数 |
+| imageProp        | Object         | 见下文        | 图片相关参数        |
+
+### 自定义 parser 函数具体介绍
+
+* 传入的参数为当前节点 `node` 对象及解析结果 `results` 对象,例如 `startHandler(node, results)`
+* 无需返回值,通过对传入的参数直接操作来完成需要的改动
+* 自定义函数会在原解析函数处理之后执行
+
+### imageProp 对象具体属性
+
+| 名称              | 类型           | 默认值        | 描述                |
+| -----------------|--------------- | ------------- | ------------------ |
+| mode             | String         | 'aspectFit'   | 图片裁剪、缩放的模式 |
+| padding          | Number         | 0             | 图片内边距          |
+| lazyLoad         | Boolean        | false         | 图片懒加载          |
+| domain           | String         | ''            | 图片服务域名        |
+
+## 事件
+
+| 名称             | 参数              | 描述              |
+| -----------------|----------------- | ----------------  |
+| preview          | 图片地址,原始事件 | 预览图片时触发     |
+| navigate         | 链接地址,原始事件 | 点击链接时触发     |
+
+## 基本使用方法
+
+
+``` vue
+<template>
+  <div>
+    <u-parse :content="article" @preview="preview" @navigate="navigate" />
+  </div>
+</template>
+
+<script>
+import uParse from '@/components/u-parse/u-parse.vue'
+
+export default {
+  components: {
+    uParse
+  },
+  data () {
+    return {
+      article: '<div>我是HTML代码</div>'
+    }
+  },
+  methods: {
+    preview(src, e) {
+      // do something
+    },
+    navigate(href, e) {
+      // do something
+    }
+  }
+}
+</script>
+
+<style>
+@import url("@/components/u-parse/u-parse.css");
+</style>
+```
+
+
+## 渲染 Markdown
+
+> 先将 markdown 转换为 html 即可
+
+```
+npm install marked
+```
+
+``` js
+import marked from 'marked'
+import uParse from '@/components/u-parse/u-parse.vue'
+
+export default {
+  components: {
+    uParse
+  },
+  data () {
+    return {
+      article: marked(`#hello, markdown!`)
+    }
+  }
+}
+```

+ 232 - 0
components/u-parse/u-parse.css

@@ -0,0 +1,232 @@
+/**
+ * author: Di (微信小程序开发工程师)
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
+ *         垂直微信小程序开发交流社区
+ *
+ * github地址: https://github.com/icindy/wxParse
+ *
+ * for: 微信小程序富文本解析
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
+ */
+
+.wxParse {
+  width: 100%;
+  font-family: Helvetica, sans-serif;
+  font-size: 30upx;
+  color: #666;
+  line-height: 1.8;
+}
+
+.wxParse view {
+  word-break: hyphenate;
+}
+
+.wxParse .inline {
+  display: inline;
+  margin: 0;
+  padding: 0;
+}
+
+.wxParse .div {
+  margin: 0;
+  padding: 0;
+}
+
+.wxParse .h1 .text {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+.wxParse .h2 .text {
+  font-size: 1.5em;
+  margin: 0.83em 0;
+}
+.wxParse .h3 .text {
+  font-size: 1.17em;
+  margin: 1em 0;
+}
+.wxParse .h4 .text {
+  margin: 1.33em 0;
+}
+.wxParse .h5 .text {
+  font-size: 0.83em;
+  margin: 1.67em 0;
+}
+.wxParse .h6 .text {
+  font-size: 0.67em;
+  margin: 2.33em 0;
+}
+
+.wxParse .h1 .text,
+.wxParse .h2 .text,
+.wxParse .h3 .text,
+.wxParse .h4 .text,
+.wxParse .h5 .text,
+.wxParse .h6 .text,
+.wxParse .b,
+.wxParse .strong {
+  font-weight: bolder;
+}
+
+
+.wxParse .p {
+  margin: 1em 0;
+}
+
+.wxParse .i,
+.wxParse .cite,
+.wxParse .em,
+.wxParse .var,
+.wxParse .address {
+  font-style: italic;
+}
+
+.wxParse .pre,
+.wxParse .tt,
+.wxParse .code,
+.wxParse .kbd,
+.wxParse .samp {
+  font-family: monospace;
+}
+.wxParse .pre {
+  overflow: auto;
+  background: #f5f5f5;
+  padding: 16upx;
+  white-space: pre;
+  margin: 1em 0upx;
+}
+.wxParse .code {
+  display: inline;
+  background: #f5f5f5;
+}
+
+.wxParse .big {
+  font-size: 1.17em;
+}
+
+.wxParse .small,
+.wxParse .sub,
+.wxParse .sup {
+  font-size: 0.83em;
+}
+
+.wxParse .sub {
+  vertical-align: sub;
+}
+.wxParse .sup {
+  vertical-align: super;
+}
+
+.wxParse .s,
+.wxParse .strike,
+.wxParse .del {
+  text-decoration: line-through;
+}
+
+.wxParse .strong,
+.wxParse .s {
+  display: inline;
+}
+
+.wxParse .a {
+  color: deepskyblue;
+}
+
+.wxParse .video {
+  text-align: center;
+  margin: 22upx 0;
+}
+
+.wxParse .video-video {
+  width: 100%;
+}
+
+.wxParse .img {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  max-width: 100%;
+  overflow: hidden;
+}
+
+.wxParse .blockquote {
+  margin: 10upx 0;
+  padding: 22upx 0 22upx 22upx;
+  font-family: Courier, Calibri, "宋体";
+  background: #f5f5f5;
+  border-left: 6upx solid #dbdbdb;
+}
+.wxParse .blockquote .p {
+  margin: 0;
+}
+
+.wxParse .ul, .wxParse .ol {
+  display: block;
+  margin: 1em 0;
+  padding-left: 33upx;
+}
+.wxParse .ol {
+  list-style-type: disc;
+}
+.wxParse .ol {
+  list-style-type: decimal;
+}
+.wxParse .ol>weixin-parse-template,.wxParse .ul>weixin-parse-template {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+
+.wxParse .ol>.li,.wxParse .ul>.li {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+.wxParse .ul .ul, .wxParse .ol .ul {
+  list-style-type: circle;
+}
+.wxParse .ol .ol .ul, .wxParse .ol .ul .ul, .wxParse .ul .ol .ul, .wxParse .ul .ul .ul {
+    list-style-type: square;
+}
+
+.wxParse .u {
+  text-decoration: underline;
+}
+.wxParse .hide {
+  display: none;
+}
+.wxParse .del {
+  display: inline;
+}
+.wxParse .figure {
+  overflow: hidden;
+}
+
+.wxParse .table {
+  width: 100%;
+}
+.wxParse .thead, .wxParse .tfoot, .wxParse .tr {
+  display: flex;
+  flex-direction: row;
+}
+.wxParse .tr {
+  width:100%;
+  display: flex;
+  border-right: 2upx solid #e0e0e0;
+  border-bottom: 2upx solid #e0e0e0;
+}
+.wxParse .th,
+.wxParse .td {
+  display: flex;
+  width: 1276upx;
+  overflow: auto;
+  flex: 1;
+  padding: 11upx;
+  border-left: 2upx solid #e0e0e0;
+}
+.wxParse .td:last {
+  border-top: 2upx solid #e0e0e0;
+}
+.wxParse .th {
+  background: #f0f0f0;
+  border-top: 2upx solid #e0e0e0;
+}

+ 118 - 0
components/u-parse/u-parse.vue

@@ -0,0 +1,118 @@
+<!--**
+ * forked from:https://github.com/F-loat/mpvue-wxParse
+ *
+ * github地址: https://github.com/dcloudio/uParse
+ *
+ * for: uni-app框架下 富文本解析
+ */-->
+
+<template>
+<!--基础元素-->
+<div class="wxParse" :class="className" v-if="!loading">
+  <block v-for="(node,index) of nodes" :key="index">
+    <wxParseTemplate :node="node" />
+  </block>
+</div>
+</template>
+
+<script>
+import HtmlToJson from './libs/html2json';
+import wxParseTemplate from './components/wxParseTemplate0';
+
+export default {
+  name: 'wxParse',
+  props: {
+    loading: {
+      type: Boolean,
+      default: false,
+    },
+    className: {
+      type: String,
+      default: '',
+    },
+    content: {
+      type: String,
+      default: '',
+    },
+    noData: {
+      type: String,
+      default: '<div style="color: red;">数据不能为空</div>',
+    },
+    startHandler: {
+      type: Function,
+      default() {
+        return (node) => {
+          node.attr.class = null;
+          node.attr.style = null;
+        };
+      },
+    },
+    endHandler: {
+      type: Function,
+      default: null,
+    },
+    charsHandler: {
+      type: Function,
+      default: null,
+    },
+    imageProp: {
+      type: Object,
+      default() {
+        return {
+          mode: 'aspectFit',
+          padding: 0,
+          lazyLoad: false,
+          domain: '',
+        };
+      },
+    },
+  },
+  components: {
+    wxParseTemplate,
+  },
+  data() {
+    return {
+      imageUrls: [],
+    };
+  },
+  computed: {
+    nodes() {
+      const {
+        content,
+        noData,
+        imageProp,
+        startHandler,
+        endHandler,
+        charsHandler,
+      } = this;
+      const parseData = content || noData;
+      const customHandler = {
+        start: startHandler,
+        end: endHandler,
+        chars: charsHandler,
+      };
+      const results = HtmlToJson(parseData, customHandler, imageProp, this);
+      this.imageUrls = results.imageUrls;
+      console.log(results)
+      return results.nodes;
+    },
+  },
+  methods: {
+    navigate(href, $event) {
+      this.$emit('navigate', href, $event);
+    },
+    preview(src, $event) {
+      if (!this.imageUrls.length) return;
+      wx.previewImage({
+        current: src,
+        urls: this.imageUrls,
+      });
+      this.$emit('preview', src, $event);
+    },
+    removeImageUrl(src) {
+      const { imageUrls } = this;
+      imageUrls.splice(imageUrls.indexOf(src), 1);
+    },
+  },
+};
+</script>

+ 19 - 0
components/uni-cell.vue

@@ -0,0 +1,19 @@
+<template>
+    <!-- #ifdef APP-NVUE -->
+    <cell>
+        <slot />
+    </cell>
+    <!-- #endif -->
+    <!-- #ifndef APP-NVUE -->
+    <view>
+        <slot />
+    </view>
+    <!-- #endif -->
+</template>
+
+<script>
+    export default {
+        name: 'UniCell',
+        props: {}
+    }
+</script>

+ 129 - 0
components/uni-list.vue

@@ -0,0 +1,129 @@
+<template>
+	<!-- #ifdef APP-VUE -->
+	<view class="uni-list">
+		<slot />
+	</list>
+	<!-- #endif -->
+	<!-- #ifdef APP-NVUE -->
+	<list class="uni-list" :enableBackToTop="enableBackToTop" loadmoreoffset="15" :scroll-y="scrollY" @loadmore="loadMore">
+		<slot />
+	</list>
+	<!-- #endif -->
+	<!-- #ifdef H5 || MP-WEIXIN || MP-QQ -->
+	<scroll-view class="uni-list" :enableBackToTop="enableBackToTop" :scroll-y="scrollY" @scrolltolower="loadMore">
+		<slot />
+	</scroll-view>
+	<!-- #endif -->
+	<!-- #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO -->
+	<scroll-view class="uni-list" :scroll-y="scrollY" @scrolltolower="loadMore">
+		<slot />
+	</scroll-view>
+	<!-- #endif -->
+</template>
+
+<script src="./uni-refresh.wxs" module="refresh" lang="wxs"></script>
+
+<script>
+	export default {
+		name: 'UniList',
+		'mp-weixin': {
+			options: {
+				multipleSlots: false
+			}
+		},
+		data() {
+			return {}
+		},
+		props: {
+			enableBackToTop: {
+				type: [Boolean, String],
+				default: false
+			},
+			scrollY: {
+				type: [Boolean, String],
+				default: true
+			}
+		},
+		created() {
+			// #ifndef APP-NVUE
+			this.pullDown = {
+				threshold: 95,
+				maxHeight: 200,
+				callRefresh: 'onrefresh',
+				callPullingDown: 'onpullingdown',
+				refreshSelector: '.uni-refresh'
+			};
+			this.height = 0;
+			this.canPullDown = false;
+			this.refreshInstance = {};
+			// #endif
+		},
+		methods: {
+			loadMore(e) {
+				this.$emit("scrolltolower");
+			},
+			ontouchstart(e) {
+				if (!this.canPullDown) {
+					console.log("canPullDown", this.canPullDown);
+					return
+				}
+
+				this.height = 0;
+				this.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
+				this._updateRefresh(0);
+				this.refreshInstance.callMethod("onchange", true);
+			},
+			ontouchmove(e) {
+				if (!this.canPullDown) {
+					return
+				}
+
+				var oldHeight = this.height;
+				var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
+				var newHeight = endY - this.touchStartY;
+				if (newHeight > this.pullDown.maxHeight) {
+					return;
+				}
+
+				this._updateRefresh(newHeight);
+
+				newHeight = newHeight < this.pullDown.maxHeight ? newHeight : this.pullDown.maxHeight;
+				this.height = newHeight;
+
+				this.refreshInstance.callMethod(this.pullDown.callPullingDown, {
+					height: newHeight
+				});
+			},
+			ontouchend(e) {
+				if (!this.canPullDown) {
+					return
+				}
+
+				this.refreshInstance.callMethod("onchange", false);
+
+				if (this.height > this.pullDown.threshold) {
+					refreshInstance.callMethod(this.pullDown.callRefresh);
+					return;
+				}
+
+				this._updateRefresh(0);
+			},
+			_updateRefresh(height) {
+				this.refreshInstance.setStyle({
+					'height': height
+				});
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-list {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		position: relative;
+		flex-direction: column;
+	}
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 26 - 0
components/uni-load-more.vue


+ 194 - 0
components/uni-load-more/uni-load-more.vue

@@ -0,0 +1,194 @@
+<template>
+	<view class="uni-load-more">
+		<view class="uni-load-more__img" v-show="status === 'loading' && showIcon">
+			<view class="load1">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+			<view class="load2">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+			<view class="load3">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+		</view>
+		<text class="uni-load-more__text" :style="{color:color}">{{status === 'more' ? contentText.contentdown : (status === 'loading' ? contentText.contentrefresh : contentText.contentnomore)}}</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "uni-load-more",
+		props: {
+			status: {
+				//上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
+				type: String,
+				default: 'more'
+			},
+			showIcon: {
+				type: Boolean,
+				default: true
+			},
+			color: {
+				type: String,
+				default: "#777777"
+			},
+			contentText: {
+				type: Object,
+				default () {
+					return {
+						contentdown: "上拉显示更多",
+						contentrefresh: "正在加载...",
+						contentnomore: "没有更多数据了"
+					};
+				}
+			}
+		},
+		data() {
+			return {}
+		}
+	}
+</script>
+
+<style>
+	@charset "UTF-8";
+
+	.uni-load-more {
+		display: flex;
+		flex-direction: row;
+		height: 80upx;
+		align-items: center;
+		justify-content: center
+	}
+
+	.uni-load-more__text {
+		font-size: 28upx;
+		color: #999
+	}
+
+	.uni-load-more__img {
+		height: 24px;
+		width: 24px;
+		margin-right: 10px
+	}
+
+	.uni-load-more__img>view {
+		position: absolute
+	}
+
+	.uni-load-more__img>view view {
+		width: 6px;
+		height: 2px;
+		border-top-left-radius: 1px;
+		border-bottom-left-radius: 1px;
+		background: #999;
+		position: absolute;
+		opacity: .2;
+		transform-origin: 50%;
+		animation: load 1.56s ease infinite
+	}
+
+	.uni-load-more__img>view view:nth-child(1) {
+		transform: rotate(90deg);
+		top: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(2) {
+		transform: rotate(180deg);
+		top: 11px;
+		right: 0
+	}
+
+	.uni-load-more__img>view view:nth-child(3) {
+		transform: rotate(270deg);
+		bottom: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(4) {
+		top: 11px;
+		left: 0
+	}
+
+	.load1,
+	.load2,
+	.load3 {
+		height: 24px;
+		width: 24px
+	}
+
+	.load2 {
+		transform: rotate(30deg)
+	}
+
+	.load3 {
+		transform: rotate(60deg)
+	}
+
+	.load1 view:nth-child(1) {
+		animation-delay: 0s
+	}
+
+	.load2 view:nth-child(1) {
+		animation-delay: .13s
+	}
+
+	.load3 view:nth-child(1) {
+		animation-delay: .26s
+	}
+
+	.load1 view:nth-child(2) {
+		animation-delay: .39s
+	}
+
+	.load2 view:nth-child(2) {
+		animation-delay: .52s
+	}
+
+	.load3 view:nth-child(2) {
+		animation-delay: .65s
+	}
+
+	.load1 view:nth-child(3) {
+		animation-delay: .78s
+	}
+
+	.load2 view:nth-child(3) {
+		animation-delay: .91s
+	}
+
+	.load3 view:nth-child(3) {
+		animation-delay: 1.04s
+	}
+
+	.load1 view:nth-child(4) {
+		animation-delay: 1.17s
+	}
+
+	.load2 view:nth-child(4) {
+		animation-delay: 1.3s
+	}
+
+	.load3 view:nth-child(4) {
+		animation-delay: 1.43s
+	}
+
+	@-webkit-keyframes load {
+		0% {
+			opacity: 1
+		}
+
+		100% {
+			opacity: .2
+		}
+	}
+</style>

+ 202 - 0
components/uni-number-box.vue

@@ -0,0 +1,202 @@
+<template>
+	<view class="uni-numbox">
+		<view class="uni-numbox-minus" 
+			@click="_calcValue('subtract')"
+		>
+			<text class="yticon icon--jianhao" :class="minDisabled?'uni-numbox-disabled': ''" ></text>
+		</view>
+		<input 
+			class="uni-numbox-value" 
+			type="number" 
+			:disabled="disabled"
+			v-model="inputValue" 
+			
+			@blur="_onBlur"
+		>
+		<view 
+			class="uni-numbox-plus" 
+			@click="_calcValue('add')"
+		>
+			<text class="yticon icon-jia2" :class="maxDisabled?'uni-numbox-disabled': ''" ></text>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		name: 'uni-number-box',
+		props: {
+			isMax: {
+				type: Boolean,
+				default: false
+			},
+			isMin: {
+				type: Boolean,
+				default: false
+			},
+			index: {
+				type: Number,
+				default: 0
+			},
+			value: {
+				type: Number,
+				default: 0
+			},
+			min: {
+				type: Number,
+				default: -Infinity
+			},
+			max: {
+				type: Number,
+				default: Infinity
+			},
+			step: {
+				type: Number,
+				default: 1
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				inputValue: this.value,
+				minDisabled: false,
+				maxDisabled: false
+			}
+		},
+		created(){
+			this.maxDisabled = this.isMax;
+			this.minDisabled = this.isMin;
+		},
+		computed: {
+				
+		},
+		watch: {
+			inputValue(number) {
+				const data = {
+					number: number,
+					index: this.index
+				}
+				this.$emit('eventChange', data);
+			},
+			value(number){
+				this.inputValue = number;
+			}
+		},
+		methods: {
+			_calcValue(type) {
+				const scale = this._getDecimalScale();
+				let value = this.inputValue * scale;
+				let newValue = 0;
+				let step = this.step * scale;
+				
+				if(type === 'subtract'){
+					newValue = value - step;
+					if (newValue <= this.min){
+						this.minDisabled = true;
+					}
+					if(newValue < this.min){
+						newValue = this.min
+					}
+					if(newValue < this.max && this.maxDisabled === true){
+						this.maxDisabled = false;
+					}
+				}else if(type === 'add'){
+					newValue = value + step;
+					if (newValue >= this.max){
+						this.maxDisabled = true;
+					}
+					if(newValue > this.max){
+						newValue = this.max
+					}
+					if(newValue > this.min && this.minDisabled === true){
+						this.minDisabled = false;
+					}
+				}
+				if(newValue === value){
+					return;
+				}
+				this.inputValue = newValue / scale; 
+			},
+			_getDecimalScale() {
+				let scale = 1;
+				// 浮点型
+				if (~~this.step !== this.step) {
+					scale = Math.pow(10, (this.step + '').split('.')[1].length);
+				}
+				return scale;
+			},
+			_onBlur(event) {
+				let value = event.detail.value;
+				if (!value) {
+					this.inputValue = 0;
+					return
+				}
+				value = +value;
+				if (value > this.max) {
+					value = this.max;
+				} else if (value < this.min) {
+					value = this.min
+				}
+
+				this.inputValue = value
+			}
+		}
+	}
+</script>
+<style>
+	.uni-numbox {
+		position:absolute;
+		left: 30upx;
+		bottom: 0;
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+		width:230upx;
+		height: 70upx;
+		background:#f5f5f5;
+	}
+
+	.uni-numbox-minus,
+	.uni-numbox-plus {
+		margin: 0;
+		background-color: #f5f5f5;
+		width: 70upx;
+		height: 100%;
+		line-height: 70upx;
+		text-align: center;
+		position: relative;
+	}
+	.uni-numbox-minus .yticon,
+	.uni-numbox-plus .yticon{
+		font-size: 36upx;
+		color: #555;
+	}
+
+	.uni-numbox-minus {
+		border-right: none;
+		border-top-left-radius: 6upx;
+		border-bottom-left-radius: 6upx;
+	}
+
+	.uni-numbox-plus {
+		border-left: none;
+		border-top-right-radius: 6upx;
+		border-bottom-right-radius: 6upx;
+	}
+
+	.uni-numbox-value {
+		position: relative;
+		background-color: #f5f5f5;
+		width: 90upx;
+		height: 50upx;
+		text-align: center;
+		padding: 0;
+		font-size: 30upx;
+	}
+
+	.uni-numbox-disabled.yticon {
+		color: #d6d6d6;
+	}
+</style>

+ 65 - 0
components/uni-refresh.vue

@@ -0,0 +1,65 @@
+<template>
+    <!-- #ifdef APP-NVUE -->
+    <refresh :display="display" @refresh="onrefresh" @pullingdown="onpullingdown">
+        <slot />
+    </refresh>
+    <!-- #endif -->
+    <!-- #ifndef APP-NVUE -->
+    <view ref="uni-refresh" class="uni-refresh" v-show="isShow">
+        <slot />
+    </view>
+    <!-- #endif -->
+</template>
+
+<script>
+    export default {
+        name: 'UniRefresh',
+        props: {
+            display: {
+                type: [String],
+                default: "hide"
+            }
+        },
+        data() {
+            return {
+                pulling: false
+            }
+        },
+        computed: {
+            isShow() {
+                if (this.display === "show" || this.pulling === true) {
+                    return true;
+                }
+                return false;
+            }
+        },
+        created() {},
+        methods: {
+            onchange(value) {
+                this.pulling = value;
+            },
+            onrefresh(e) {
+                this.$emit("refresh", e);
+            },
+            onpullingdown(e) {
+                // #ifdef APP-NVUE
+                this.$emit("pullingdown", e);
+                // #endif
+                // #ifndef APP-NVUE
+                var detail = {
+                    viewHeight: 90,
+                    pullingDistance: e.height
+                }
+                this.$emit("pullingdown", detail);
+                // #endif
+            }
+        }
+    }
+</script>
+
+<style>
+    .uni-refresh {
+        height: 0;
+        overflow: hidden;
+    }
+</style>

+ 85 - 0
components/uni-refresh.wxs

@@ -0,0 +1,85 @@
+var pullDown = {
+    threshold: 95,
+    maxHeight: 200,
+    callRefresh: 'onrefresh',
+    callPullingDown: 'onpullingdown',
+    refreshSelector: '.uni-refresh'
+};
+
+function ready(newValue, oldValue, ownerInstance, instance) {
+    var state = instance.getState()
+    state.canPullDown = newValue;
+    console.log(newValue);
+}
+
+function touchStart(e, instance) {
+    var state = instance.getState();
+    state.refreshInstance = instance.selectComponent(pullDown.refreshSelector);
+    state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined);
+    if (!state.canPullDown) {
+        return
+    }
+
+    state.height = 0;
+    state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
+    state.refreshInstance.setStyle({
+        'height': 0
+    });
+    state.refreshInstance.callMethod("onchange", true);
+}
+
+function touchMove(e, ownerInstance) {
+    var instance = e.instance;
+    var state = instance.getState();
+    if (!state.canPullDown) {
+        return
+    }
+
+    var oldHeight = state.height;
+    var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
+    var height = endY - state.touchStartY;
+    if (height > pullDown.maxHeight) {
+        return;
+    }
+
+    var refreshInstance = state.refreshInstance;
+    refreshInstance.setStyle({
+        'height': height + 'px'
+    });
+
+    height = height < pullDown.maxHeight ? height : pullDown.maxHeight;
+    state.height = height;
+    refreshInstance.callMethod(pullDown.callPullingDown, {
+        height: height
+    });
+}
+
+function touchEnd(e, ownerInstance) {
+    var state = e.instance.getState();
+    if (!state.canPullDown) {
+        return
+    }
+
+    state.refreshInstance.callMethod("onchange", false);
+
+    var refreshInstance = state.refreshInstance;
+    if (state.height > pullDown.threshold) {
+        refreshInstance.callMethod(pullDown.callRefresh);
+        return;
+    }
+
+    refreshInstance.setStyle({
+        'height': 0
+    });
+}
+
+function propObserver(newValue, oldValue, instance) {
+    pullDown = newValue;
+}
+
+module.exports = {
+    touchmove: touchMove,
+    touchstart: touchStart,
+    touchend: touchEnd,
+    propObserver: propObserver
+}

+ 249 - 0
components/uni-table/uni-table.vue

@@ -0,0 +1,249 @@
+<template>
+    <view class="uni-table-scroll" :class="{'table--border':border,'border-none':!noData}">
+        <view class="uni-table" :style="{'min-width':minWidth+'px'}" :class="{ 'table--stripe':stripe}">
+            <slot></slot>
+            <view v-if="noData" class="uni-table-loading">
+                <view class="uni-table-text">{{emptyText}}</view>
+            </view>
+            <view v-show="loading" class="uni-table-mask">
+                <div class="uni-table--loader"></div>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+	/**
+	 * Table 表格
+	 * @description 用于展示多条结构类似的数据
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=
+	 * @property {Boolean} 	border 				是否带有纵向边框
+	 * @property {Boolean} 	stripe 				是否显示斑马线
+	 * @property {Boolean} 	type 				是否开启多选
+	 * @property {String} 	emptyText 			空数据时显示的文本内容
+	 * @property {Boolean} 	loading 			显示加载中
+	 * @event {Function} 	selection-change 	开启多选时,当选择项发生变化时会触发该事件
+	 */
+    export default {
+        name: 'uniTable',
+        options: {
+            virtualHost: true
+        },
+        props: {
+            // 是否有竖线
+            border: {
+                type: Boolean,
+                default: false
+            },
+            // 是否显示斑马线
+            stripe: {
+                type: Boolean,
+                default: false
+            },
+            // 多选
+            type: {
+                type: String,
+                default: ''
+            },
+            // 没有更多数据
+            emptyText: {
+                type: String,
+                default: '没有更多数据'
+            },
+            loading: {
+                type: Boolean,
+                default: false
+            },
+        },
+        data() {
+            return {
+                noData: true,
+                minWidth:0
+            };
+        },
+        watch: {
+            loading(val) {
+
+            }
+        },
+        created() {
+            // 定义tr的实例数组
+            this.trChildren = []
+            this.backData = []
+        },
+        methods: {
+            isNodata() {
+                if (this.trChildren.length > 1) {
+                    this.noData = false
+                } else {
+                    this.noData = true
+                }
+            },
+            check(child, check) {
+                const childDom = this.trChildren.find((item, index) => child === item)
+                const childDomIndex = this.trChildren.findIndex((item, index) => child === item)
+                if (childDomIndex === 0) {
+                    if (childDom.value !== check) {
+                        this.backData = []
+                        this.trChildren.map((item, index) => item.value = check)
+                    }
+                    this.trChildren.forEach((item, index) => {
+                        if (index > 0 && item.value) {
+                            this.backData.push(index - 1)
+                        }
+                    })
+
+                } else {
+                    if (!check) {
+                        this.trChildren[0].value = false
+                    }
+                    childDom.value = check
+                    if (check) {
+                        this.backData.push(childDomIndex - 1)
+                    } else {
+                        const index = this.backData.findIndex(item => item === (childDomIndex - 1))
+                        this.backData.splice(index, 1)
+                    }
+                    const domCheckAll = this.trChildren.find((item, index) => index > 0 && !item.value)
+                    if (!domCheckAll) {
+                        this.trChildren[0].value = true
+                    }
+                }
+
+                this.$emit('selection-change', {
+                    detail: {
+                        index: this.backData.sort(),
+                        value: []
+                    }
+                })
+
+            }
+        }
+    }
+</script>
+
+<style lang="scss">
+    .uni-table-scroll {
+        width: 100%;
+        overflow-x: auto;
+    }
+
+    .uni-table {
+        position: relative;
+        width: 100%;
+        display: table;
+        box-sizing: border-box;
+        border-radius: 5px;
+        box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
+        overflow-x: auto;
+		background-color: #fff;
+        /deep/ .uni-table-tr:nth-child(n+2) {
+            &:hover {
+                background-color: #f5f7fa;
+            }
+        }
+    }
+
+    .table--border {
+        border: 1px #ddd solid;
+    }
+
+    .border-none {
+        border-bottom: none;
+    }
+
+    .table--stripe {
+        /deep/ .uni-table-tr:nth-child(2n+3) {
+            background-color: #fafafa;
+        }
+
+    }
+
+    /* 表格加载、无数据样式 */
+    .uni-table-loading {
+        position: relative;
+        display: table-row;
+        height: 50px;
+        line-height: 50px;
+    }
+
+    .uni-table-text {
+        position: absolute;
+        right: 0;
+        left: 0;
+        text-align: center;
+        font-size: 14px;
+        color: #999;
+    }
+
+    .uni-table-mask {
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        margin: auto;
+        background-color: rgba(255, 255, 255, 0.8);
+        z-index: 99;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        transition: all 0.5s;
+    }
+
+    .uni-table--loader {
+        width: 30px;
+        height: 30px;
+        border: 2px solid #aaa;
+        // border-bottom-color: transparent;
+        border-radius: 50%;
+        animation: 2s uni-table--loader linear infinite;
+        position: relative;
+    }
+
+    @keyframes uni-table--loader {
+        0% {
+            transform: rotate(360deg);
+        }
+
+        10% {
+            border-left-color: transparent;
+        }
+
+        20% {
+            border-bottom-color: transparent;
+        }
+
+        30% {
+            border-right-color: transparent;
+        }
+
+        40% {
+            border-top-color: transparent;
+        }
+
+        50% {
+            transform: rotate(0deg);
+        }
+
+        60% {
+            border-top-color: transparent;
+        }
+
+        70% {
+            border-left-color: transparent;
+        }
+
+        80% {
+            border-bottom-color: transparent;
+        }
+
+        90% {
+            border-right-color: transparent;
+        }
+
+        100% {
+            transform: rotate(-360deg);
+        }
+    }
+</style>

+ 74 - 0
components/uni-td/uni-td.vue

@@ -0,0 +1,74 @@
+<template>
+    <!-- :class="{'table--border':border}"  -->
+	<view class="uni-table-td" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Td 单元格
+	 * @description 表格中的标准单元格组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=
+	 * @property {Number} 	align = [left|center|right]	单元格对齐方式
+	 */
+	export default {
+		name: 'uniTd',
+		options: {
+			virtualHost: true
+		},
+		props: {
+			width: {
+				type: [String, Number],
+				default: ''
+			},
+			align: {
+				type: String,
+				default: 'left'
+			}
+		},
+		data() {
+			return {
+				border: false
+			};
+		},
+		created() {
+			this.root = this.getTable()
+			this.border = this.root.border
+		},
+		methods: {
+			/**
+			 * 获取父元素实例
+			 */
+			getTable() {
+				let parent = this.$parent;
+				let parentName = parent.$options.name;
+				while (parentName !== 'uniTable') {
+					parent = parent.$parent;
+					if (!parent) return false;
+					parentName = parent.$options.name;
+				}
+				return parent;
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	.uni-table-td {
+		display: table-cell;
+		padding: 1px 1px;
+		// text-align: center;
+		vertical-align: middle;
+		border-bottom: 1px #ddd solid;
+		// min-width: 150px;
+		color: #666;
+		font-size: 12px;
+        box-sizing: border-box;
+	}
+
+	.table--border {
+		border-right: 1px #ddd solid;
+	}
+
+</style>

+ 78 - 0
components/uni-th/uni-th.vue

@@ -0,0 +1,78 @@
+<template>
+	<view class="uni-table-th" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Th 表头
+	 * @description 表格内的表头单元格组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=
+	 * @property {Number} 	width 						单元格宽度
+	 * @property {Number} 	align = [left|center|right]	单元格对齐方式
+	 * @value left   	单元格文字左侧对齐
+	 * @value center	单元格文字居中
+	 * @value right		单元格文字右侧对齐
+	 */
+
+	export default {
+		name: 'uniTh',
+		options: {
+			virtualHost: true
+		},
+		props: {
+			width: {
+				type: [String, Number],
+				default: ''
+			},
+			align: {
+				type: String,
+				default: 'left'
+			}
+		},
+		data() {
+			return {
+				border:false
+			};
+		},
+		created() {
+			this.root = this.getTable('uniTable')
+			this.rootTr = this.getTable('uniTr')
+            this.rootTr.minWidthUpdate(this.width?this.width:140)
+			this.border = this.root.border
+		},
+		methods:{
+			/**
+			 * 获取父元素实例
+			 */
+			getTable(name) {
+				let parent = this.$parent;
+				let parentName = parent.$options.name;
+				while (parentName !== name) {
+					parent = parent.$parent;
+					if (!parent) return false;
+					parentName = parent.$options.name;
+				}
+				return parent;
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.uni-table-th {
+		padding: 12px 10px;
+		display: table-cell;
+		// text-align: center;
+		color: #333;
+		font-weight: 500;
+		border-bottom: 1px #ddd solid;
+		font-size: 12px;
+        // background-color: #efefef;
+        box-sizing: border-box;
+	}
+	.table--border {
+		border-right: 1px #ddd solid;
+	}
+</style>

+ 117 - 0
components/uni-tr/uni-tr.vue

@@ -0,0 +1,117 @@
+<template>
+    <view class="uni-table-tr">
+        <checkbox-group v-if="selection === 'selection'" class="checkbox" :class="{'tr-table--border':border}" @change="change">
+            <label>
+                <checkbox value="check" :checked="value"/>
+            </label>
+        </checkbox-group>
+        <slot></slot>
+    </view>
+</template>
+
+<script>
+	/**
+	 * Tr 表格行组件
+	 * @description 表格行组件 仅包含 th,td 组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=
+	 */
+    export default {
+        name: 'uniTr',
+        options: {
+            virtualHost: true
+        },
+        data() {
+            return {
+                value: false,
+                border: false,
+                selection:false,
+                widthThArr:[]
+            };
+        },
+        created() {
+            this.root = this.getTable()
+            this.border = this.root.border
+            this.selection = this.root.type
+            this.root.trChildren.push(this)
+            this.root.isNodata()
+        },
+        mounted() {
+            if(this.widthThArr.length > 0){
+                const selectionWidth = this.selection === 'selection'? 50:0
+                this.root.minWidth =  this.widthThArr.reduce((a,b)=> Number(a) + Number(b)) + selectionWidth
+            }
+        },
+        destroyed() {
+            const index = this.root.trChildren.findIndex(i=>i===this)
+            this.root.trChildren.splice(index,1)
+            this.root.isNodata()
+        },
+        methods: {
+            minWidthUpdate(width){
+                this.widthThArr.push(width)
+            },
+            change(e) {
+                this.root.trChildren.forEach((item) => {
+                    if (item === this) {
+                        this.root.check(this,e.detail.value.length > 0 ? true : false)
+                    }
+                })
+            },
+            /**
+             * 获取父元素实例
+             */
+            getTable() {
+                let parent = this.$parent;
+                let parentName = parent.$options.name;
+                while (parentName !== 'uniTable') {
+                    parent = parent.$parent;
+                    if (!parent) return false;
+                    parentName = parent.$options.name;
+                }
+                return parent;
+            },
+        }
+    }
+</script>
+
+<style lang="scss">
+    .uni-table-tr {
+        display: table-row;
+        transition: all .3s;
+        box-sizing: border-box;
+    }
+
+    .checkbox {
+        padding: 12px 8px;
+        width: 26px;
+        padding-left: 12px;
+        display: table-cell;
+        // text-align: center;
+        vertical-align: middle;
+        color: #333;
+        font-weight: 500;
+        border-bottom: 1px #ddd solid;
+        font-size: 14px;
+    }
+
+    .tr-table--border {
+        border-right: 1px #ddd solid;
+    }
+
+    .uni-table-tr {
+        /deep/ .uni-table-th {
+            &.table--border:last-child {
+                border-right: none;
+            }
+        }
+        /deep/ .uni-table-td {
+            &.table--border:last-child {
+                border-right: none;
+            }
+        }
+    }
+
+
+
+
+</style>

+ 226 - 0
components/upload-images.vue

@@ -0,0 +1,226 @@
+<template>
+	<view class="upload-content">
+		<block v-for="(item, index) in imageList" :key="index">
+			<view class="upload-item">
+				<image class="upload-img" :src="item.filePath" mode="aspectFit" @click="previewImage(index)"></image>
+				<image class="upload-del-btn" 
+					@click="delImage(index)" 
+					src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjNBODgzQjUwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjNBODgzQjYwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGM0E4ODNCMzA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGM0E4ODNCNDA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuYQTIAAAAHNSURBVHjazJgxasMwFIbjnsD4BDmCLxDQDZrNQ5bcILlBvXvpDRLIWmiHFkyH2t0LyVbwEkPpbE/eYlUCGYQax096UuIffkIgij7ryU/vyZuYy2e+Z56K70R8lsJcOfPn5AriMCvmZ2YKdMW8EQ/hRA9iEorwXlpZtObMRySQ6kysvrFWloHUEIcmUBuHUDLccmxQskErt7wyVLdy00tQ4Q2g5De2V9kNwWjffiNDA33f/42i6E13Qj5uNptlwJD+SyODuaqqqi/KVBTFOxSKA7VtW/NxSZI8AcbE2nuraZpvKgSBk6G4drsdBOwogz1CwAghH/JEl+BUKJ1VltPHHjoIAoeE4l53YFqb+RKcBSgqyqXhtxEKZwkKB3YOThYCqjPuCDoHZwGK3mGLtdPp5LmqTI1Dqe4p26EkNqA4iE6eg2z+iQ0okyQMATvYgLIIt9Y6knTyFBIudHqIq3BpmkL60VJ9O0sXZY8MBwSLtdNGEAQ/i8XiVXcz83EcEPDbuq/fzMdYWt+6GTmMsX2rh9q3TtsxNrzXhKt1rwg6rR1Dhdjqo7QMlWOvoWTF4imxQMRF7eaL8L5ohmwrLgDB8pCQc8DlcG7y538CDABJNGPqfaJgfgAAAABJRU5ErkJggg==" 
+					mode="scaleToFill">
+				</image>
+				<view class="upload-progress" v-if="item.progress < 100">{{item.progress}}%</view>
+			</view>
+		</block>
+		<view class="upload-add-btn" v-if="rduLength > 0" @click="chooseImage"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			imageList: []
+		};
+	},
+	props: {
+		url: {
+			type: String,
+			value: '' //上传接口地址
+		},
+		count: {
+			type: Number,
+			value: 4 //单次可选择的图片数量
+		},
+		length: {
+			type: Number,
+			value: 50 //可上传总数量
+		}
+	},
+	computed: {
+		rduLength(){
+			return this.length - this.imageList.length;
+		}
+	},
+	methods: {
+		//选择图片
+		chooseImage: function(){
+			uni.chooseImage({
+				count: this.rduLength < this.count ? this.rduLength : this.count, //最多可以选择的图片张数,默认9
+				sizeType: ['original', 'compressed'], //original 原图,compressed 压缩图,默认二者都有
+				sourceType: ['album'], //album 从相册选图,camera 使用相机,默认二者都有
+				success: (res)=> {
+					const images = res.tempFilePaths;
+					this.uploadFiles(images);
+				}
+			});
+		},
+		//上传图片
+		async uploadFiles(images){
+			this.imageList.push({
+				filePath: images[0],
+				progress: 0
+			});
+			uni.showLoading({
+				title: '请稍后..',
+				mask: true,
+			})
+			try{
+				const uploadUrl = await this.uploadImage(images[0]);
+			}catch(err){
+				console.log(err);
+				return;
+			}
+			
+			if(uploadUrl !== false){
+				images.splice(0, 1);
+				this.imageList[this.imageList.length - 1].src = uploadUrl;
+
+				//判断是否需要继续上传
+				if(images.length > 0 && this.rduLength > 0){
+					this.uploadFiles(images);
+				}else{
+					uni.hideLoading();
+				}
+			}else{
+				//上传失败处理
+				this.imageList.pop();
+				uni.hideLoading();
+				uni.showToast({
+					title: '上传中出现问题,已终止上传',
+					icon: 'none',
+					mask: true,
+					duration: 2000
+				});
+			}
+		},
+		uploadImage: function(file){
+			return new Promise((resolve, reject)=> {
+				//发送给后端的附加参数
+				const formData = {
+					thumb_mode: 1,  
+				};
+				this.uploadTask = uni.uploadFile({
+					url: this.url, 
+					filePath: file,
+					name: 'file',
+					formData: formData,
+					success(uploadFileResult){
+						const uploadFileRes = JSON.parse(uploadFileResult.data) || {};
+						if(uploadFileRes.status === 1 && uploadFileRes.data){
+							resolve(uploadFileRes.data);
+						}else{
+							reject('接口返回错误');
+						}
+					}, 
+					fail(){
+						reject('网络链接错误');
+					}
+				});
+				//上传进度
+				this.uploadTask.onProgressUpdate((progressRes)=> {
+					this.imageList[this.imageList.length - 1].progress = progressRes.progress;
+				});
+			});
+		},
+		//删除图片
+		delImage: function(index){
+			uni.showModal({
+				content: '确定要放弃这张图片么?',
+				success: (confirmRes)=> {
+					if (confirmRes.confirm) {
+						this.imageList.splice(index, 1);
+					} 
+				}
+			});
+		},
+		//预览图片
+		previewImage: function(index){
+			const urls = [];
+			this.imageList.forEach((item)=> {
+				urls.push(item.filePath);
+			})
+			uni.previewImage({
+				current: urls[index],
+				urls: urls,
+				indicator: "number"
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+	.upload-content{
+		padding:24upx 0 0 28upx;
+		background-color: #fff;
+		overflow:hidden;
+	}
+	.upload-item{
+		position: relative;
+		float:left;
+		width:150upx;
+		height:150upx;
+		margin-right:30upx;
+		margin-bottom:30upx;
+		&:nth-child(4n){
+			margin-right:0;
+		}
+		.upload-img{
+			width:100%;
+			height:100%;
+			border-radius:8upx;
+		}
+		.upload-del-btn{
+			position: absolute;
+			right:-16upx;
+			top:-14upx;
+			width:36upx;
+			height:36upx;
+			border: 4upx solid #fff;
+			border-radius: 100px;
+		}
+		.upload-progress{
+			position: absolute;
+			left:0;
+			top:0;
+			display:flex;
+			align-items:center;
+			justify-content: center;
+			width:100%;
+			height:100%;
+			background-color: rgba(0,0,0,.4);
+			color:#fff;
+			font-size:24upx;
+			border-radius:8upx;
+		}
+	}
+	.upload-add-btn {
+		position: relative;
+		float:left;
+		width: 150upx;
+		height: 150upx;
+		z-index: 99;
+		border-radius:8upx;
+		background:#f9f9f9;
+		&:before,
+		&:after {
+			content: " ";
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			-webkit-transform: translate(-50%, -50%);
+			transform: translate(-50%, -50%);
+			width: 4upx;
+			height: 60upx;
+			background-color: #d6d6d6;
+		}
+		&:after {
+			width: 60upx;
+			height: 4upx;
+		}
+		&:active {
+			background-color: #f7f7f7;
+		}
+	}
+
+</style>

+ 600 - 0
components/upload.vue

@@ -0,0 +1,600 @@
+<template>
+	<view class="u-upload" v-if="!disabled">
+		<view
+			v-if="showUploadList"
+			class="u-list-item u-preview-wrap"
+			v-for="(item, index) in lists"
+			:key="index"
+			:style="{
+				width: width + 'rpx',
+				height: width + 'rpx'
+			}"
+		>
+			<view
+				v-if="deletable"
+				class="u-delete-icon"
+				@tap.stop="deleteItem(index)"
+				:style="{
+					background: delBgColor
+				}"
+			>
+				<u-icon class="u-icon" :name="delIcon" size="20" :color="delColor"></u-icon>
+			</view>
+			<u-line-progress
+				v-if="showProgress && item.progress > 0 && !item.error"
+				:show-percent="false"
+				height="16"
+				class="u-progress"
+				:percent="item.progress"
+			></u-line-progress>
+			<view @tap.stop="retry(index)" v-if="item.error" class="u-error-btn">点击重试</view>
+			<image @tap.stop="doPreviewImage(item.url || item.path, index)" class="u-preview-image" v-if="!item.isImage" :src="item.url || item.path" :mode="imageMode"></image>
+		</view>
+		<slot name="file" :file="lists"></slot>
+		<view style="display: inline-block;" @tap="selectFile" v-if="maxCount > lists.length">
+			<slot name="addBtn"></slot>
+			<view
+				v-if="!customBtn"
+				class="u-list-item u-add-wrap"
+				hover-class="u-add-wrap__hover"
+				hover-stay-time="150"
+				:style="{
+					width: width + 'rpx',
+					height: width + 'rpx'
+				}"
+			>
+				<u-icon name="plus" class="u-add-btn" size="40"></u-icon>
+				<view class="u-add-tips">{{ uploadText }}</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * upload 图片上传
+ * @description 该组件用于上传图片场景
+ * @tutorial https://www.uviewui.com/components/upload.html
+ * @property {String} action 服务器上传地址
+ * @property {String Number} max-count 最大选择图片的数量(默认99)
+ * @property {Boolean} custom-btn 如果需要自定义选择图片的按钮,设置为true(默认false)
+ * @property {Boolean} show-progress 是否显示进度条(默认true)
+ * @property {Boolean} disabled 是否启用(显示/移仓)组件(默认false)
+ * @property {String} image-mode 预览图片等显示模式,可选值为uni的image的mode属性值(默认aspectFill)
+ * @property {String} del-icon 右上角删除图标名称,只能为uView内置图标
+ * @property {String} del-bg-color 右上角关闭按钮的背景颜色
+ * @property {String} del-color 右上角关闭按钮图标的颜色
+ * @property {Object} header 上传携带的头信息,对象形式
+ * @property {Object} form-data 上传额外携带的参数
+ * @property {String} name 上传文件的字段名,供后端获取使用(默认file)
+ * @property {Array<String>} size-type original 原图,compressed 压缩图,默认二者都有(默认['original', 'compressed'])
+ * @property {Array<String>} source-type 选择图片的来源,album-从相册选图,camera-使用相机,默认二者都有(默认['album', 'camera'])
+ * @property {Boolean} preview-full-image	是否可以通过uni.previewImage预览已选择的图片(默认true)
+ * @property {Boolean} multiple	是否开启图片多选,部分安卓机型不支持(默认true)
+ * @property {Boolean} deletable 是否显示删除图片的按钮(默认true)
+ * @property {String Number} max-size 选择单个文件的最大大小,单位B(byte),默认不限制(默认Number.MAX_VALUE)
+ * @property {Array<Object>} file-list 默认显示的图片列表,数组元素为对象,必须提供url属性
+ * @property {Boolean} upload-text 选择图片按钮的提示文字(默认“选择图片”)
+ * @property {Boolean} auto-upload 选择完图片是否自动上传,见上方说明(默认true)
+ * @property {Boolean} show-tips 特殊情况下是否自动提示toast,见上方说明(默认true)
+ * @property {Boolean} show-upload-list 是否显示组件内部的图片预览(默认true)
+ * @event {Function} on-oversize 图片大小超出最大允许大小
+ * @event {Function} on-preview 全屏预览图片时触发
+ * @event {Function} on-remove 移除图片时触发
+ * @event {Function} on-success 图片上传成功时触发
+ * @event {Function} on-change 图片上传后,无论成功或者失败都会触发
+ * @event {Function} on-error 图片上传失败时触发
+ * @event {Function} on-progress 图片上传过程中的进度变化过程触发
+ * @event {Function} on-uploaded 所有图片上传完毕触发
+ * @event {Function} on-choose-complete 每次选择图片后触发,只是让外部可以得知每次选择后,内部的文件列表
+ * @example <u-upload :action="action" :file-list="fileList" ></u-upload>
+ */
+export default {
+	name: 'upload',
+	props: {
+		//是否显示组件自带的图片预览功能
+		showUploadList: {
+			type: Boolean,
+			default: true
+		},
+		// 后端地址
+		action: {
+			type: String,
+			default: ''
+		},
+		// 最大上传数量
+		maxCount: {
+			type: [String, Number],
+			default: 52
+		},
+		//  是否显示进度条
+		showProgress: {
+			type: Boolean,
+			default: true
+		},
+		// 是否启用
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 预览上传的图片时的裁剪模式,和image组件mode属性一致
+		imageMode: {
+			type: String,
+			default: 'aspectFill'
+		},
+		// 头部信息
+		header: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 额外携带的参数
+		formData: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 上传的文件字段名
+		name: {
+			type: String,
+			default: 'file'
+		},
+		// 所选的图片的尺寸, 可选值为original compressed
+		sizeType: {
+			type: Array,
+			default() {
+				return ['original', 'compressed'];
+			}
+		},
+		sourceType: {
+			type: Array,
+			default() {
+				return ['album', 'camera'];
+			}
+		},
+		// 是否在点击预览图后展示全屏图片预览
+		previewFullImage: {
+			type: Boolean,
+			default: true
+		},
+		// 是否开启图片多选,部分安卓机型不支持
+		multiple: {
+			type: Boolean,
+			default: true
+		},
+		// 是否展示删除按钮
+		deletable: {
+			type: Boolean,
+			default: true
+		},
+		// 文件大小限制,单位为byte
+		maxSize: {
+			type: [String, Number],
+			default: Number.MAX_VALUE
+		},
+		// 显示已上传的文件列表
+		fileList: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		// 上传区域的提示文字
+		uploadText: {
+			type: String,
+			default: '选择图片'
+		},
+		// 是否自动上传
+		autoUpload: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示toast消息提示
+		showTips: {
+			type: Boolean,
+			default: true
+		},
+		// 是否通过slot自定义传入选择图标的按钮
+		customBtn: {
+			type: Boolean,
+			default: false
+		},
+		// 内部预览图片区域和选择图片按钮的区域宽度,高等于宽
+		width: {
+			type: [String, Number],
+			default: 200
+		},
+		// 右上角关闭按钮的背景颜色
+		delBgColor: {
+			type: String,
+			default: '#fa3534'
+		},
+		// 右上角关闭按钮的叉号图标的颜色
+		delColor: {
+			type: String,
+			default: '#ffffff'
+		},
+		// 右上角删除图标名称,只能为uView内置图标
+		delIcon: {
+			type: String,
+			default: 'close'
+		},
+		// 如果上传后的返回值为json字符串,是否自动转json
+		toJson: {
+			type: Boolean,
+			default: true
+		},
+		// 上传前的钩子,每个文件上传前都会执行
+		beforeUpload: {
+			type: Function,
+			default: null
+		}
+	},
+	mounted() {},
+	data() {
+		return {
+			lists: [],
+			isInCount: true,
+			uploading: false
+		};
+	},
+	watch: {
+		fileList: {
+			immediate: true,
+			handler(val) {
+				val.map(value => {
+					this.lists.push({ url: value.url, error: false, progress: 100 });
+				});
+			}
+		}
+	},
+	methods: {
+		// 清除列表
+		clear() {
+			this.lists = [];
+			// 如果是清空形式的话,发出"on-list-change"事件
+			this.$emit('on-list-change', this.lists);
+		},
+		// 重新上传队列中上传失败的所有文件
+		reUpload() {
+			this.uploadFile();
+		},
+		// 选择图片
+		selectFile() {
+			if (this.disabled) return;
+			const { name = '', maxCount, multiple, maxSize, sizeType, lists, camera, compressed, maxDuration, sourceType } = this;
+			let chooseFile = null;
+			const newMaxCount = maxCount - lists.length;
+			// 设置为只选择图片的时候使用 chooseImage 来实现
+			chooseFile = new Promise((resolve, reject) => {
+				uni.chooseImage({
+					count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
+					sourceType: sourceType,
+					sizeType,
+					success: resolve,
+					fail: reject
+				});
+			});
+			chooseFile
+				.then(res => {
+					let file = null;
+					let listOldLength = this.lists.length;
+					res.tempFiles.map((val, index) => {
+						// 如果是非多选,index大于等于1或者超出最大限制数量时,不处理
+						if (!multiple && index >= 1) return;
+						if (val.size > maxSize) {
+							this.$emit('on-oversize', val, this.lists);
+							this.showToast('超出允许的文件大小');
+						} else {
+							if (maxCount <= lists.length) {
+								this.$emit('on-exceed', val, this.lists);
+								this.showToast('超出最大允许的文件个数');
+								return;
+							}
+							let fileName = '';
+							let fileNameLength='';
+							let fileFormat = '';
+							//#ifdef H5
+								fileName = val.name.lastIndexOf(".");
+								fileNameLength = val.name.length;
+								fileFormat = val.name.substring(fileName + 1, fileNameLength).toLowerCase();
+							//#endif
+							//#ifndef H5
+								fileName = val.path.lastIndexOf(".");
+								fileNameLength = val.path.length;
+								fileFormat = val.path.substring(fileName + 1, fileNameLength).toLowerCase();
+							//#endif
+							lists.push({
+								url: val.path,
+								fileType: fileFormat,
+								progress: 0,
+								error: false
+							});
+							// 列表发生改变,发出事件,第二个参数为当前发生变化的项的索引
+							this.$emit('on-list-change', this.lists);
+						}
+					});
+					// 每次图片选择完,抛出一个事件,并将当前内部选择的图片数组抛出去
+					this.$emit('on-choose-complete', this.lists);
+					if (this.autoUpload) this.uploadFile(listOldLength);
+				})
+				.catch(error => {
+					// this.$emit('on-error', error);
+				});
+		},
+		// 提示用户消息
+		showToast(message, force = false) {
+			if (this.showTips || force) {
+				uni.showToast({
+					title: message,
+					icon: 'none'
+				});
+			}
+		},
+		// 该方法供用户通过ref调用,手动上传
+		upload() {
+			this.uploadFile();
+		},
+		// 对失败的图片重新上传
+		retry(index) {
+			this.lists[index].progress = 0;
+			this.lists[index].error = false;
+			this.lists[index].response = null;
+			uni.showLoading({
+				title: '重新上传'
+			});
+			this.uploadFile(index);
+		},
+		// 上传图片
+		async uploadFile(index = 0) {
+			if (this.disabled) return;
+			if (this.uploading) return;
+			// 全部上传完成
+			if (index >= this.lists.length) {
+				this.$emit('on-uploaded', this.lists);
+				return;
+			}
+			// 检查上传地址
+			if (!this.action) {
+				this.showToast('请配置上传地址', true);
+				return;
+			}
+			// 检查是否是已上传或者正在上传中
+			if (this.lists[index].progress == 100) {
+				if (this.autoUpload == false) this.uploadFile(index + 1);
+				return;
+			}
+			// 执行before-upload钩子
+			if(this.beforeUpload && typeof(this.beforeUpload) === 'function') {
+				// 执行回调,同时传入索引和文件列表当作参数
+				let beforeResponse = this.beforeUpload(index, this.lists);
+				// 判断是否返回了promise
+				if (!!beforeResponse && typeof beforeResponse.then === 'function') {
+					await beforeResponse.then(res => {
+						// promise返回成功,不进行动作,继续上传
+					}).catch(err => {
+						// 进入catch回调的话,继续下一张
+						return this.uploadFile(index + 1);
+					})
+				} else if(beforeResponse === false) {
+					 // 如果返回false,继续下一张图片的上传
+					return this.uploadFile(index + 1);
+				}
+			}
+			this.lists[index].error = false;
+			this.uploading = true;
+			// 创建上传对象
+			const task = uni.uploadFile({
+				url: this.action,
+				filePath: this.lists[index].url,
+				name: this.name,
+				formData: this.formData,
+				header: this.header,
+				success: res => {
+					// 判断是否json字符串,将其转为json格式
+					let data = this.toJson && this.checkIsJSON(res.data) ? JSON.parse(res.data) : res.data;
+					if (![200, 201].includes(res.statusCode)) {
+						this.uploadError(index, data);
+					} else {
+						// 上传成功
+						this.lists[index].response = data;
+						this.lists[index].progress = 100;
+						this.lists[index].error = false;
+						this.$emit('on-success', data, index, this.lists);
+					}
+				},
+				fail: e => {
+					this.uploadError(index, e);
+				},
+				complete: res => {
+					uni.hideLoading();
+					this.uploading = false;
+					this.uploadFile(index + 1);
+					this.$emit('on-change', res, index, this.lists);
+				}
+			});
+			task.onProgressUpdate(res => {
+				if (res.progress > 0) {
+					this.lists[index].progress = res.progress;
+					this.$emit('on-progress', res, index, this.lists);
+				}
+			});
+		},
+		// 上传失败
+		uploadError(index, err) {
+			this.lists[index].progress = 0;
+			this.lists[index].error = true;
+			this.lists[index].response = null;
+			this.$emit('on-error', err, index, this.lists);
+			this.showToast('上传失败,请重试');
+		},
+		// 删除一个图片
+		deleteItem(index) {
+			uni.showModal({
+				title: '提示',
+				content: '您确定要删除此项吗?',
+				success: res => {
+					if (res.confirm) {
+						if (this.lists[index].process < 100 && this.lists[index].process > 0) {
+							typeof this.lists[index].uploadTask != 'undefined' && this.lists[index].uploadTask.abort();
+						}
+						this.lists.splice(index, 1);
+						this.$forceUpdate();
+						this.$emit('on-remove', index, this.lists);
+						this.showToast('移除成功');
+						// 列表发生改变,发出事件
+						this.$emit('on-list-change', this.lists);
+					}
+				}
+			});
+		},
+		// 用户通过ref手动的形式,移除一张图片
+		remove(index) {
+			// 判断索引的合法范围
+			if (index >= 0 && index < this.lists.length) {
+				this.lists.splice(index, 1);
+				this.$emit('on-list-change', this.lists);
+			}
+		},
+		// 预览图片
+		doPreviewImage(url, index) {
+			if (!this.previewFullImage) return;
+			const images = this.lists.map(item => item.url || item.path);
+			uni.previewImage({
+				urls: images,
+				current: url,
+				success: () => {
+					this.$emit('on-preview', url, this.lists);
+				},
+				fail: () => {
+					uni.showToast({
+						title: '预览图片失败',
+						icon: 'none'
+					});
+				}
+			});
+		},
+		// 判断是否json字符串
+		checkIsJSON(str) {
+			if (typeof str == 'string') {
+				try {
+					var obj = JSON.parse(str);
+					if (typeof obj == 'object' && obj) {
+						return true;
+					} else {
+						return false;
+					}
+				} catch (e) {
+					return false;
+				}
+			}
+			return false;
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+// nvue不能用标签命名样式
+/* #ifndef APP-NVUE */
+image {
+	display: inline-block;
+	// 解决图片加载时可能会瞬间变形的问题
+	will-change: transform;
+}
+
+view,
+text {
+	box-sizing: border-box;
+	flex-direction: row;
+}
+/* #endif */
+.u-upload {
+	display: flex;
+	flex-wrap: wrap;
+	align-items: center;
+}
+
+.u-list-item {
+	width: 200rpx;
+	height: 200rpx;
+	overflow: hidden;
+	margin: 10rpx;
+	background: rgb(244, 245, 246);
+	position: relative;
+	border-radius: 10rpx;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.u-preview-wrap {
+	border: 1px solid rgb(235, 236, 238);
+}
+
+.u-add-wrap {
+	flex-direction: column;
+	color: $u-content-color;
+	font-size: 28rpx;
+}
+
+.u-add-tips {
+	margin-top: 20rpx;
+}
+
+.u-add-wrap__hover {
+	background-color: rgb(235, 236, 238);
+}
+
+.u-preview-image {
+	display: block;
+	width: 100%;
+	height: 100%;
+	border-radius: 10rpx;
+}
+
+.u-delete-icon {
+	position: absolute;
+	top: 10rpx;
+	right: 10rpx;
+	z-index: 10;
+	background-color: $u-type-error;
+	border-radius: 100rpx;
+	width: 44rpx;
+	height: 44rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.u-icon {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.u-progress {
+	position: absolute;
+	bottom: 10rpx;
+	left: 8rpx;
+	right: 8rpx;
+	z-index: 9;
+	width: auto;
+}
+
+.u-error-btn {
+	color: #ffffff;
+	background-color: $u-type-error;
+	font-size: 20rpx;
+	padding: 4px 0;
+	text-align: center;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	z-index: 9;
+	line-height: 1;
+}
+</style>

+ 21 - 0
components/uview-ui/LICENSE

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

+ 106 - 0
components/uview-ui/README.md

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

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

@@ -0,0 +1,190 @@
+<template>
+	<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
+	    length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
+		<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
+			{{tips.text}}
+		</view>
+		<block v-for="(item, index) in list" :key="index">
+			<view 
+				@touchmove.stop.prevent 
+				@tap="itemClick(index)" 
+				:style="[itemStyle(index)]" 
+				class="u-action-sheet-item u-line-1" 
+				:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
+				:hover-stay-time="150"
+			>
+				<text>{{item.text}}</text>
+				<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text>
+			</view>
+		</block>
+		<view class="u-gab" v-if="cancelBtn">
+		</view>
+		<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
+		    :hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view>
+	</u-popup>
+</template>
+
+<script>
+	/**
+	 * actionSheet 操作菜单
+	 * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
+	 * @tutorial https://www.uviewui.com/components/actionSheet.html
+	 * @property {Array<Object>} list 按钮的文字数组,见官方文档示例
+	 * @property {Object} tips 顶部的提示文字,见官方文档示例
+	 * @property {String} cancel-text 取消按钮的提示文字
+	 * @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true)
+	 * @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0)
+	 * @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true)
+	 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
+	 * @property {Number String} z-index z-index值(默认1075)
+	 * @property {String} cancel-text 取消按钮的提示文字
+	 * @event {Function} click 点击ActionSheet列表项时触发
+	 * @event {Function} close 点击取消按钮时触发
+	 * @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
+	 */
+	export default {
+		name: "u-action-sheet",
+		props: {
+			// 点击遮罩是否可以关闭actionsheet
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
+			list: {
+				type: Array,
+				default () {
+					// 如下
+					// return [{
+					// 	text: '确定',
+					// 	color: '',
+					// 	fontSize: ''
+					// }]
+					return [];
+				}
+			},
+			// 顶部的提示文字
+			tips: {
+				type: Object,
+				default () {
+					return {
+						text: '',
+						color: '',
+						fontSize: '26'
+					}
+				}
+			},
+			// 底部的取消按钮
+			cancelBtn: {
+				type: Boolean,
+				default: true
+			},
+			// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: false
+			},
+			// 通过双向绑定控制组件的弹出与收起
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 弹出的顶部圆角值
+			borderRadius: {
+				type: [String, Number],
+				default: 0
+			},
+			// 弹出的z-index值
+			zIndex: {
+				type: [String, Number],
+				default: 0
+			},
+			// 取消按钮的文字提示
+			cancelText: {
+				type: String,
+				default: '取消'
+			}
+		},
+		computed: {
+			// 顶部提示的样式
+			tipsStyle() {
+				let style = {};
+				if (this.tips.color) style.color = this.tips.color;
+				if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
+				return style;
+			},
+			// 操作项目的样式
+			itemStyle() {
+				return (index) => {
+					let style = {};
+					if (this.list[index].color) style.color = this.list[index].color;
+					if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
+					// 选项被禁用的样式
+					if (this.list[index].disabled) style.color = '#c0c4cc';
+					return style;
+				}
+			},
+			uZIndex() {
+				// 如果用户有传递z-index值,优先使用
+				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+			}
+		},
+		methods: {
+			// 点击取消按钮
+			close() {
+				// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
+				// 这是一个vue发送事件的特殊用法
+				this.popupClose();
+				this.$emit('close');
+			},
+			// 弹窗关闭
+			popupClose() {
+				this.$emit('input', false);
+			},
+			// 点击某一个item
+			itemClick(index) {
+				// disabled的项禁止点击
+				if(this.list[index].disabled) return;
+				this.$emit('click', index);
+				this.$emit('input', false);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+
+	.u-tips {
+		font-size: 26rpx;
+		text-align: center;
+		padding: 34rpx 0;
+		line-height: 1;
+		color: $u-tips-color;
+	}
+
+	.u-action-sheet-item {
+		@include vue-flex;;
+		line-height: 1;
+		justify-content: center;
+		align-items: center;
+		font-size: 32rpx;
+		padding: 34rpx 0;
+		flex-direction: column;
+	}
+	
+	.u-action-sheet-item__subtext {
+		font-size: 24rpx;
+		color: $u-tips-color;
+		margin-top: 20rpx;
+	}
+
+	.u-gab {
+		height: 12rpx;
+		background-color: rgb(234, 234, 236);
+	}
+
+	.u-actionsheet-cancel {
+		color: $u-main-color;
+	}
+</style>

+ 256 - 0
components/uview-ui/components/u-alert-tips/u-alert-tips.vue

@@ -0,0 +1,256 @@
+<template>
+	<view class="u-alert-tips" v-if="show" :class="[
+		!show ? 'u-close-alert-tips': '',
+		type ? 'u-alert-tips--bg--' + type + '-light' : '',
+		type ? 'u-alert-tips--border--' + type + '-disabled' : '',
+	]" :style="{
+		backgroundColor: bgColor,
+		borderColor: borderColor
+	}">
+		<view class="u-icon-wrap">
+			<u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon>
+		</view>
+		<view class="u-alert-content" @tap.stop="click">
+			<view class="u-alert-title" :style="[uTitleStyle]">
+				{{title}}
+			</view>
+			<view v-if="description" class="u-alert-desc" :style="[descStyle]">
+				{{description}}
+			</view>
+		</view>
+		<view class="u-icon-wrap">
+			<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc"
+			 :size="22" class="u-close-icon" :style="{
+				top: description ? '18rpx' : '24rpx'
+			}"></u-icon>
+		</view>
+		<text v-if="closeAble && closeText" class="u-close-text" :style="{
+			top: description ? '18rpx' : '24rpx'
+		}">{{closeText}}</text>
+	</view>
+</template>
+
+<script>
+	/**
+	 * alertTips 警告提示
+	 * @description 警告提示,展现需要关注的信息
+	 * @tutorial https://uviewui.com/components/alertTips.html
+	 * @property {String} title 显示的标题文字
+	 * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
+	 * @property {String} type 关闭按钮(默认为叉号icon图标)
+	 * @property {String} icon 图标名称
+	 * @property {Object} icon-style 图标的样式,对象形式
+	 * @property {Object} title-style 标题的样式,对象形式
+	 * @property {Object} desc-style 描述的样式,对象形式
+	 * @property {String} close-able 用文字替代关闭图标,close-able为true时有效
+	 * @property {Boolean} show-icon 是否显示左边的辅助图标
+	 * @property {Boolean} show 显示或隐藏组件
+	 * @event {Function} click 点击组件时触发
+	 * @event {Function} close 点击关闭按钮时触发
+	 */
+	export default {
+		name: 'u-alert-tips',
+		props: {
+			// 显示文字
+			title: {
+				type: String,
+				default: ''
+			},
+			// 主题,success/warning/info/error
+			type: {
+				type: String,
+				default: 'warning'
+			},
+			// 辅助性文字
+			description: {
+				type: String,
+				default: ''
+			},
+			// 是否可关闭
+			closeAble: {
+				type: Boolean,
+				default: false
+			},
+			// 关闭按钮自定义文本
+			closeText: {
+				type: String,
+				default: ''
+			},
+			// 是否显示图标
+			showIcon: {
+				type: Boolean,
+				default: false
+			},
+			// 文字颜色,如果定义了color值,icon会失效
+			color: {
+				type: String,
+				default: ''
+			},
+			// 背景颜色
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			// 边框颜色
+			borderColor: {
+				type: String,
+				default: ''
+			},
+			// 是否显示
+			show: {
+				type: Boolean,
+				default: true
+			},
+			// 左边显示的icon
+			icon: {
+				type: String,
+				default: ''
+			},
+			// icon的样式
+			iconStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			},
+			// 标题的样式
+			titleStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			},
+			// 描述文字的样式
+			descStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			},
+		},
+		data() {
+			return {
+			}
+		},
+		computed: {
+			uTitleStyle() {
+				let style = {};
+				// 如果有描述文字的话,标题进行加粗
+				style.fontWeight = this.description ? 500 : 'normal';
+				// 将用户传入样式对象和style合并,传入的优先级比style高,同属性会被覆盖
+				return this.$u.deepMerge(style, this.titleStyle);
+			},
+			uIcon() {
+				// 如果有设置icon名称就使用,否则根据type主题,推定一个默认的图标
+				return this.icon ? this.icon : this.$u.type2icon(this.type);
+			},
+			uIconType() {
+				// 如果有设置图标的样式,优先使用,没有的话,则用type的样式
+				return Object.keys(this.iconStyle).length ? '' : this.type;
+			}
+		},
+		methods: {
+			// 点击内容
+			click() {
+				this.$emit('click');
+			},
+			// 点击关闭按钮
+			close() {
+				this.$emit('close');
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-alert-tips {
+		@include vue-flex;
+		align-items: center;
+		padding: 16rpx 30rpx;
+		border-radius: 8rpx;
+		position: relative;
+		transition: all 0.3s linear;
+		border: 1px solid #fff;
+		
+		&--bg--primary-light {
+			background-color: $u-type-primary-light;
+		}
+		
+		&--bg--info-light {
+			background-color: $u-type-info-light;
+		}
+		
+		&--bg--success-light {
+			background-color: $u-type-success-light;
+		}
+		
+		&--bg--warning-light {
+			background-color: $u-type-warning-light;
+		}
+		
+		&--bg--error-light {
+			background-color: $u-type-error-light;
+		}
+		
+		&--border--primary-disabled {
+			border-color: $u-type-primary-disabled;
+		}
+		
+		&--border--success-disabled {
+			border-color: $u-type-success-disabled;
+		}
+		
+		&--border--error-disabled {
+			border-color: $u-type-error-disabled;
+		}
+		
+		&--border--warning-disabled {
+			border-color: $u-type-warning-disabled;
+		}
+		
+		&--border--info-disabled {
+			border-color: $u-type-info-disabled;
+		}
+	}
+
+	.u-close-alert-tips {
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.u-icon {
+		margin-right: 16rpx;
+	}
+
+	.u-alert-title {
+		font-size: 28rpx;
+		color: $u-main-color;
+	}
+
+	.u-alert-desc {
+		font-size: 26rpx;
+		text-align: left;
+		color: $u-content-color;
+	}
+
+	.u-close-icon {
+		position: absolute;
+		top: 20rpx;
+		right: 20rpx;
+	}
+
+	.u-close-hover {
+		color: red;
+	}
+	
+	.u-close-text {
+		font-size: 24rpx;
+		color: $u-tips-color;
+		position: absolute;
+		top: 20rpx;
+		right: 20rpx;
+		line-height: 1;
+	}
+</style>

+ 290 - 0
components/uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue

@@ -0,0 +1,290 @@
+<template>
+	<view class="content">
+		<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				@touchstart="touchStart"
+				@touchmove="touchMove"
+				@touchend="touchEnd"
+				:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
+				canvas-id="cropper"
+				id="cropper"
+			></canvas>
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				:style="{
+					position: 'fixed',
+					top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
+					width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					height: `${cropperOpt.height * cropperOpt.pixelRatio}`
+				}"
+				canvas-id="targetId"
+				id="targetId"
+			></canvas>
+		</view>
+		<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
+			<!-- #ifdef H5 -->
+			<view class="upload" @tap="uploadTap">选择图片</view>
+			<!-- #endif -->
+			<!-- #ifndef H5 -->
+			<view class="upload" @tap="uploadTap">重新选择</view>
+			<!-- #endif -->
+			<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import WeCropper from './weCropper.js';
+export default {
+	props: {
+		// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
+		// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
+		boundStyle: {
+			type: Object,
+			default() {
+				return {
+					lineWidth: 4,
+					borderColor: 'rgb(245, 245, 245)',
+					mask: 'rgba(0, 0, 0, 0.35)'
+				};
+			}
+		}
+		// // 裁剪框宽度,单位rpx
+		// rectWidth: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 裁剪框高度,单位rpx
+		// rectHeight: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出图片宽度,单位rpx
+		// destWidth: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出图片高度,单位rpx
+		// destHeight: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可
+		// fileType: {
+		// 	type: String,
+		// 	default: 'jpg',
+		// },
+		// // 生成的图片质量
+		// // H5上无效,目前不考虑使用此参数
+		// quality: {
+		// 	type: [Number, String],
+		// 	default: 1
+		// }
+	},
+	data() {
+		return {
+			// 底部导航的高度
+			bottomNavHeight: 50,
+			originWidth: 200,
+			width: 0,
+			height: 0,
+			cropperOpt: {
+				id: 'cropper',
+				targetId: 'targetCropper',
+				pixelRatio: 1,
+				width: 0,
+				height: 0,
+				scale: 2.5,
+				zoom: 8,
+				cut: {
+					x: (this.width - this.originWidth) / 2,
+					y: (this.height - this.originWidth) / 2,
+					width: this.originWidth,
+					height: this.originWidth
+				},
+				boundStyle: {
+					lineWidth: uni.upx2px(this.boundStyle.lineWidth),
+					mask: this.boundStyle.mask,
+					color: this.boundStyle.borderColor
+				}
+			},
+			// 裁剪框和输出图片的尺寸,高度默认等于宽度
+			// 输出图片宽度,单位px
+			destWidth: 200,
+			// 裁剪框宽度,单位px
+			rectWidth: 200,
+			// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
+			fileType: 'jpg',
+			src: '', // 选择的图片路径,用于在点击确定时,判断是否选择了图片
+		};
+	},
+	onLoad(option) {
+		let rectInfo = uni.getSystemInfoSync();
+		this.width = rectInfo.windowWidth;
+		this.height = rectInfo.windowHeight - this.bottomNavHeight;
+		this.cropperOpt.width = this.width;
+		this.cropperOpt.height = this.height;
+		this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
+
+		if (option.destWidth) this.destWidth = option.destWidth;
+		if (option.rectWidth) {
+			let rectWidth = Number(option.rectWidth);
+			this.cropperOpt.cut = {
+				x: (this.width - rectWidth) / 2,
+				y: (this.height - rectWidth) / 2,
+				width: rectWidth,
+				height: rectWidth
+			};
+		}
+		this.rectWidth = option.rectWidth;
+		if (option.fileType) this.fileType = option.fileType;
+		// 初始化
+		this.cropper = new WeCropper(this.cropperOpt)
+			.on('ready', ctx => {
+				// wecropper is ready for work!
+			})
+			.on('beforeImageLoad', ctx => {
+				// before picture loaded, i can do something
+			})
+			.on('imageLoad', ctx => {
+				// picture loaded
+			})
+			.on('beforeDraw', (ctx, instance) => {
+				// before canvas draw,i can do something
+			});
+		// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
+		uni.setNavigationBarColor({
+			frontColor: '#ffffff',
+			backgroundColor: '#000000'
+		});
+		uni.chooseImage({
+			count: 1, // 默认9
+			sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
+			sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+			success: res => {
+				this.src = res.tempFilePaths[0];
+				//  获取裁剪图片资源后,给data添加src属性及其值
+				this.cropper.pushOrign(this.src);
+			}
+		});
+	},
+	methods: {
+		touchStart(e) {
+			this.cropper.touchStart(e);
+		},
+		touchMove(e) {
+			this.cropper.touchMove(e);
+		},
+		touchEnd(e) {
+			this.cropper.touchEnd(e);
+		},
+		getCropperImage(isPre = false) {
+			if(!this.src) return this.$u.toast('请先选择图片再裁剪');
+
+			let cropper_opt = {
+				destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值
+				destWidth: Number(this.destWidth),
+				fileType: this.fileType
+			};
+			this.cropper.getCropperImage(cropper_opt, (path, err) => {
+				if (err) {
+					uni.showModal({
+						title: '温馨提示',
+						content: err.message
+					});
+				} else {
+					if (isPre) {
+						uni.previewImage({
+							current: '', // 当前显示图片的 http 链接
+							urls: [path] // 需要预览的图片 http 链接列表
+						});
+					} else {
+						uni.$emit('uAvatarCropper', path);
+						this.$u.route({
+							type: 'back'
+						});
+					}
+				}
+			});
+		},
+		uploadTap() {
+			const self = this;
+			uni.chooseImage({
+				count: 1, // 默认9
+				sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
+				sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+				success: (res) => {
+					self.src = res.tempFilePaths[0];
+					//  获取裁剪图片资源后,给data添加src属性及其值
+
+					self.cropper.pushOrign(this.src);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+@import '../../libs/css/style.components.scss';
+
+.content {
+	background: rgba(255, 255, 255, 1);
+}
+
+.cropper {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 11;
+}
+
+.cropper-buttons {
+	background-color: #000000;
+	color: #eee;
+}
+
+.cropper-wrapper {
+	position: relative;
+	@include vue-flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	width: 100%;
+	background-color: #000;
+}
+
+.cropper-buttons {
+	width: 100vw;
+	@include vue-flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	font-size: 28rpx;
+}
+
+.cropper-buttons .upload,
+.cropper-buttons .getCropperImage {
+	width: 50%;
+	text-align: center;
+}
+
+.cropper-buttons .upload {
+	text-align: left;
+	padding-left: 50rpx;
+}
+
+.cropper-buttons .getCropperImage {
+	text-align: right;
+	padding-right: 50rpx;
+}
+</style>

+ 1265 - 0
components/uview-ui/components/u-avatar-cropper/weCropper.js

@@ -0,0 +1,1265 @@
+/**
+ * we-cropper v1.3.9
+ * (c) 2020 dlhandsome
+ * @license MIT
+ */
+(function(global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+		typeof define === 'function' && define.amd ? define(factory) :
+		(global.WeCropper = factory());
+}(this, (function() {
+	'use strict';
+
+	var device = void 0;
+	var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
+
+	function firstLetterUpper(str) {
+		return str.charAt(0).toUpperCase() + str.slice(1)
+	}
+
+	function setTouchState(instance) {
+		var arg = [],
+			len = arguments.length - 1;
+		while (len-- > 0) arg[len] = arguments[len + 1];
+
+		TOUCH_STATE.forEach(function(key, i) {
+			if (arg[i] !== undefined) {
+				instance[key] = arg[i];
+			}
+		});
+	}
+
+	function validator(instance, o) {
+		Object.defineProperties(instance, o);
+	}
+
+	function getDevice() {
+		if (!device) {
+			device = uni.getSystemInfoSync();
+		}
+		return device
+	}
+
+	var tmp = {};
+
+	var ref = getDevice();
+	var pixelRatio = ref.pixelRatio;
+
+	var DEFAULT = {
+		id: {
+			default: 'cropper',
+			get: function get() {
+				return tmp.id
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("id:" + value + " is invalid"));
+				}
+				tmp.id = value;
+			}
+		},
+		width: {
+			default: 750,
+			get: function get() {
+				return tmp.width
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("width:" + value + " is invalid"));
+				}
+				tmp.width = value;
+			}
+		},
+		height: {
+			default: 750,
+			get: function get() {
+				return tmp.height
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("height:" + value + " is invalid"));
+				}
+				tmp.height = value;
+			}
+		},
+		pixelRatio: {
+			default: pixelRatio,
+			get: function get() {
+				return tmp.pixelRatio
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("pixelRatio:" + value + " is invalid"));
+				}
+				tmp.pixelRatio = value;
+			}
+		},
+		scale: {
+			default: 2.5,
+			get: function get() {
+				return tmp.scale
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("scale:" + value + " is invalid"));
+				}
+				tmp.scale = value;
+			}
+		},
+		zoom: {
+			default: 5,
+			get: function get() {
+				return tmp.zoom
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("zoom:" + value + " is invalid"));
+				} else if (value < 0 || value > 10) {
+					console.error("zoom should be ranged in 0 ~ 10");
+				}
+				tmp.zoom = value;
+			}
+		},
+		src: {
+			default: '',
+			get: function get() {
+				return tmp.src
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("src:" + value + " is invalid"));
+				}
+				tmp.src = value;
+			}
+		},
+		cut: {
+			default: {},
+			get: function get() {
+				return tmp.cut
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("cut:" + value + " is invalid"));
+				}
+				tmp.cut = value;
+			}
+		},
+		boundStyle: {
+			default: {},
+			get: function get() {
+				return tmp.boundStyle
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("boundStyle:" + value + " is invalid"));
+				}
+				tmp.boundStyle = value;
+			}
+		},
+		onReady: {
+			default: null,
+			get: function get() {
+				return tmp.ready
+			},
+			set: function set(value) {
+				tmp.ready = value;
+			}
+		},
+		onBeforeImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.beforeImageLoad
+			},
+			set: function set(value) {
+				tmp.beforeImageLoad = value;
+			}
+		},
+		onImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.imageLoad
+			},
+			set: function set(value) {
+				tmp.imageLoad = value;
+			}
+		},
+		onBeforeDraw: {
+			default: null,
+			get: function get() {
+				return tmp.beforeDraw
+			},
+			set: function set(value) {
+				tmp.beforeDraw = value;
+			}
+		}
+	};
+
+	var ref$1 = getDevice();
+	var windowWidth = ref$1.windowWidth;
+
+	function prepare() {
+		var self = this;
+
+		// v1.4.0 版本中将不再自动绑定we-cropper实例
+		self.attachPage = function() {
+			var pages = getCurrentPages();
+			// 获取到当前page上下文
+			var pageContext = pages[pages.length - 1];
+			// 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
+			Object.defineProperty(pageContext, 'wecropper', {
+				get: function get() {
+					console.warn(
+						'Instance will not be automatically bound to the page after v1.4.0\n\n' +
+						'Please use a custom instance name instead\n\n' +
+						'Example: \n' +
+						'this.mycropper = new WeCropper(options)\n\n' +
+						'// ...\n' +
+						'this.mycropper.getCropperImage()'
+					);
+					return self
+				},
+				configurable: true
+			});
+		};
+
+		self.createCtx = function() {
+			var id = self.id;
+			var targetId = self.targetId;
+
+			if (id) {
+				self.ctx = self.ctx || uni.createCanvasContext(id);
+				self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId);
+			} else {
+				console.error("constructor: create canvas context failed, 'id' must be valuable");
+			}
+		};
+
+		self.deviceRadio = windowWidth / 750;
+	}
+
+	var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !==
+		'undefined' ? self : {};
+
+
+
+
+
+	function createCommonjsModule(fn, module) {
+		return module = {
+			exports: {}
+		}, fn(module, module.exports), module.exports;
+	}
+
+	var tools = createCommonjsModule(function(module, exports) {
+		/**
+		 * String type check
+		 */
+		exports.isStr = function(v) {
+			return typeof v === 'string';
+		};
+		/**
+		 * Number type check
+		 */
+		exports.isNum = function(v) {
+			return typeof v === 'number';
+		};
+		/**
+		 * Array type check
+		 */
+		exports.isArr = Array.isArray;
+		/**
+		 * undefined type check
+		 */
+		exports.isUndef = function(v) {
+			return v === undefined;
+		};
+
+		exports.isTrue = function(v) {
+			return v === true;
+		};
+
+		exports.isFalse = function(v) {
+			return v === false;
+		};
+		/**
+		 * Function type check
+		 */
+		exports.isFunc = function(v) {
+			return typeof v === 'function';
+		};
+		/**
+		 * Quick object check - this is primarily used to tell
+		 * Objects from primitive values when we know the value
+		 * is a JSON-compliant type.
+		 */
+		exports.isObj = exports.isObject = function(obj) {
+			return obj !== null && typeof obj === 'object'
+		};
+
+		/**
+		 * Strict object type check. Only returns true
+		 * for plain JavaScript objects.
+		 */
+		var _toString = Object.prototype.toString;
+		exports.isPlainObject = function(obj) {
+			return _toString.call(obj) === '[object Object]'
+		};
+
+		/**
+		 * Check whether the object has the property.
+		 */
+		var hasOwnProperty = Object.prototype.hasOwnProperty;
+		exports.hasOwn = function(obj, key) {
+			return hasOwnProperty.call(obj, key)
+		};
+
+		/**
+		 * Perform no operation.
+		 * Stubbing args to make Flow happy without leaving useless transpiled code
+		 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
+		 */
+		exports.noop = function(a, b, c) {};
+
+		/**
+		 * Check if val is a valid array index.
+		 */
+		exports.isValidArrayIndex = function(val) {
+			var n = parseFloat(String(val));
+			return n >= 0 && Math.floor(n) === n && isFinite(val)
+		};
+	});
+
+	var tools_7 = tools.isFunc;
+	var tools_10 = tools.isPlainObject;
+
+	var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
+
+	function observer() {
+		var self = this;
+
+		self.on = function(event, fn) {
+			if (EVENT_TYPE.indexOf(event) > -1) {
+				if (tools_7(fn)) {
+					event === 'ready' ?
+						fn(self) :
+						self[("on" + (firstLetterUpper(event)))] = fn;
+				}
+			} else {
+				console.error(("event: " + event + " is invalid"));
+			}
+			return self
+		};
+	}
+
+	function wxPromise(fn) {
+		return function(obj) {
+			var args = [],
+				len = arguments.length - 1;
+			while (len-- > 0) args[len] = arguments[len + 1];
+
+			if (obj === void 0) obj = {};
+			return new Promise(function(resolve, reject) {
+				obj.success = function(res) {
+					resolve(res);
+				};
+				obj.fail = function(err) {
+					reject(err);
+				};
+				fn.apply(void 0, [obj].concat(args));
+			})
+		}
+	}
+
+	function draw(ctx, reserve) {
+		if (reserve === void 0) reserve = false;
+
+		return new Promise(function(resolve) {
+			ctx.draw(reserve, resolve);
+		})
+	}
+
+	var getImageInfo = wxPromise(uni.getImageInfo);
+
+	var canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath);
+
+	var base64 = createCommonjsModule(function(module, exports) {
+		/*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
+		(function(root) {
+
+			// Detect free variables `exports`.
+			var freeExports = 'object' == 'object' && exports;
+
+			// Detect free variable `module`.
+			var freeModule = 'object' == 'object' && module &&
+				module.exports == freeExports && module;
+
+			// Detect free variable `global`, from Node.js or Browserified code, and use
+			// it as `root`.
+			var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal;
+			if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+				root = freeGlobal;
+			}
+
+			/*--------------------------------------------------------------------------*/
+
+			var InvalidCharacterError = function(message) {
+				this.message = message;
+			};
+			InvalidCharacterError.prototype = new Error;
+			InvalidCharacterError.prototype.name = 'InvalidCharacterError';
+
+			var error = function(message) {
+				// Note: the error messages used throughout this file match those used by
+				// the native `atob`/`btoa` implementation in Chromium.
+				throw new InvalidCharacterError(message);
+			};
+
+			var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+			// http://whatwg.org/html/common-microsyntaxes.html#space-character
+			var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g;
+
+			// `decode` is designed to be fully compatible with `atob` as described in the
+			// HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
+			// The optimized base64-decoding algorithm used is based on @atk’s excellent
+			// implementation. https://gist.github.com/atk/1020396
+			var decode = function(input) {
+				input = String(input)
+					.replace(REGEX_SPACE_CHARACTERS, '');
+				var length = input.length;
+				if (length % 4 == 0) {
+					input = input.replace(/==?$/, '');
+					length = input.length;
+				}
+				if (
+					length % 4 == 1 ||
+					// http://whatwg.org/C#alphanumeric-ascii-characters
+					/[^+a-zA-Z0-9/]/.test(input)
+				) {
+					error(
+						'Invalid character: the string to be decoded is not correctly encoded.'
+					);
+				}
+				var bitCounter = 0;
+				var bitStorage;
+				var buffer;
+				var output = '';
+				var position = -1;
+				while (++position < length) {
+					buffer = TABLE.indexOf(input.charAt(position));
+					bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
+					// Unless this is the first of a group of 4 characters…
+					if (bitCounter++ % 4) {
+						// …convert the first 8 bits to a single ASCII character.
+						output += String.fromCharCode(
+							0xFF & bitStorage >> (-2 * bitCounter & 6)
+						);
+					}
+				}
+				return output;
+			};
+
+			// `encode` is designed to be fully compatible with `btoa` as described in the
+			// HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
+			var encode = function(input) {
+				input = String(input);
+				if (/[^\0-\xFF]/.test(input)) {
+					// Note: no need to special-case astral symbols here, as surrogates are
+					// matched, and the input is supposed to only contain ASCII anyway.
+					error(
+						'The string to be encoded contains characters outside of the ' +
+						'Latin1 range.'
+					);
+				}
+				var padding = input.length % 3;
+				var output = '';
+				var position = -1;
+				var a;
+				var b;
+				var c;
+				var buffer;
+				// Make sure any padding is handled outside of the loop.
+				var length = input.length - padding;
+
+				while (++position < length) {
+					// Read three bytes, i.e. 24 bits.
+					a = input.charCodeAt(position) << 16;
+					b = input.charCodeAt(++position) << 8;
+					c = input.charCodeAt(++position);
+					buffer = a + b + c;
+					// Turn the 24 bits into four chunks of 6 bits each, and append the
+					// matching character for each of them to the output.
+					output += (
+						TABLE.charAt(buffer >> 18 & 0x3F) +
+						TABLE.charAt(buffer >> 12 & 0x3F) +
+						TABLE.charAt(buffer >> 6 & 0x3F) +
+						TABLE.charAt(buffer & 0x3F)
+					);
+				}
+
+				if (padding == 2) {
+					a = input.charCodeAt(position) << 8;
+					b = input.charCodeAt(++position);
+					buffer = a + b;
+					output += (
+						TABLE.charAt(buffer >> 10) +
+						TABLE.charAt((buffer >> 4) & 0x3F) +
+						TABLE.charAt((buffer << 2) & 0x3F) +
+						'='
+					);
+				} else if (padding == 1) {
+					buffer = input.charCodeAt(position);
+					output += (
+						TABLE.charAt(buffer >> 2) +
+						TABLE.charAt((buffer << 4) & 0x3F) +
+						'=='
+					);
+				}
+
+				return output;
+			};
+
+			var base64 = {
+				'encode': encode,
+				'decode': decode,
+				'version': '0.1.0'
+			};
+
+			// Some AMD build optimizers, like r.js, check for specific condition patterns
+			// like the following:
+			if (
+				typeof undefined == 'function' &&
+				typeof undefined.amd == 'object' &&
+				undefined.amd
+			) {
+				undefined(function() {
+					return base64;
+				});
+			} else if (freeExports && !freeExports.nodeType) {
+				if (freeModule) { // in Node.js or RingoJS v0.8.0+
+					freeModule.exports = base64;
+				} else { // in Narwhal or RingoJS v0.7.0-
+					for (var key in base64) {
+						base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
+					}
+				}
+			} else { // in Rhino or a web browser
+				root.base64 = base64;
+			}
+
+		}(commonjsGlobal));
+	});
+
+	function makeURI(strData, type) {
+		return 'data:' + type + ';base64,' + strData
+	}
+
+	function fixType(type) {
+		type = type.toLowerCase().replace(/jpg/i, 'jpeg');
+		var r = type.match(/png|jpeg|bmp|gif/)[0];
+		return 'image/' + r
+	}
+
+	function encodeData(data) {
+		var str = '';
+		if (typeof data === 'string') {
+			str = data;
+		} else {
+			for (var i = 0; i < data.length; i++) {
+				str += String.fromCharCode(data[i]);
+			}
+		}
+		return base64.encode(str)
+	}
+
+	/**
+	 * 获取图像区域隐含的像素数据
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param done 完成回调
+	 */
+	function getImageData(canvasId, x, y, width, height, done) {
+		uni.canvasGetImageData({
+			canvasId: canvasId,
+			x: x,
+			y: y,
+			width: width,
+			height: height,
+			success: function success(res) {
+				done(res, null);
+			},
+			fail: function fail(res) {
+				done(null, res);
+			}
+		});
+	}
+
+	/**
+	 * 生成bmp格式图片
+	 * 按照规则生成图片响应头和响应体
+	 * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData
+	 * @returns {*} base64字符串
+	 */
+	function genBitmapImage(oData) {
+		//
+		// BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
+		// BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
+		//
+		var biWidth = oData.width;
+		var biHeight = oData.height;
+		var biSizeImage = biWidth * biHeight * 3;
+		var bfSize = biSizeImage + 54; // total header size = 54 bytes
+
+		//
+		//  typedef struct tagBITMAPFILEHEADER {
+		//  	WORD bfType;
+		//  	DWORD bfSize;
+		//  	WORD bfReserved1;
+		//  	WORD bfReserved2;
+		//  	DWORD bfOffBits;
+		//  } BITMAPFILEHEADER;
+		//
+		var BITMAPFILEHEADER = [
+			// WORD bfType -- The file type signature; must be "BM"
+			0x42, 0x4D,
+			// DWORD bfSize -- The size, in bytes, of the bitmap file
+			bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
+			// WORD bfReserved1 -- Reserved; must be zero
+			0, 0,
+			// WORD bfReserved2 -- Reserved; must be zero
+			0, 0,
+			// DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
+			54, 0, 0, 0
+		];
+
+		//
+		//  typedef struct tagBITMAPINFOHEADER {
+		//  	DWORD biSize;
+		//  	LONG  biWidth;
+		//  	LONG  biHeight;
+		//  	WORD  biPlanes;
+		//  	WORD  biBitCount;
+		//  	DWORD biCompression;
+		//  	DWORD biSizeImage;
+		//  	LONG  biXPelsPerMeter;
+		//  	LONG  biYPelsPerMeter;
+		//  	DWORD biClrUsed;
+		//  	DWORD biClrImportant;
+		//  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
+		//
+		var BITMAPINFOHEADER = [
+			// DWORD biSize -- The number of bytes required by the structure
+			40, 0, 0, 0,
+			// LONG biWidth -- The width of the bitmap, in pixels
+			biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
+			// LONG biHeight -- The height of the bitmap, in pixels
+			biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
+			// WORD biPlanes -- The number of planes for the target device. This value must be set to 1
+			1, 0,
+			// WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
+			// has a maximum of 2^24 colors (16777216, Truecolor)
+			24, 0,
+			// DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
+			0, 0, 0, 0,
+			// DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
+			biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
+			// LONG biXPelsPerMeter, unused
+			0, 0, 0, 0,
+			// LONG biYPelsPerMeter, unused
+			0, 0, 0, 0,
+			// DWORD biClrUsed, the number of color indexes of palette, unused
+			0, 0, 0, 0,
+			// DWORD biClrImportant, unused
+			0, 0, 0, 0
+		];
+
+		var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
+
+		var aImgData = oData.data;
+
+		var strPixelData = '';
+		var biWidth4 = biWidth << 2;
+		var y = biHeight;
+		var fromCharCode = String.fromCharCode;
+
+		do {
+			var iOffsetY = biWidth4 * (y - 1);
+			var strPixelRow = '';
+			for (var x = 0; x < biWidth; x++) {
+				var iOffsetX = x << 2;
+				strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX]);
+			}
+
+			for (var c = 0; c < iPadding; c++) {
+				strPixelRow += String.fromCharCode(0);
+			}
+
+			strPixelData += strPixelRow;
+		} while (--y)
+
+		var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
+
+		return strEncoded
+	}
+
+	/**
+	 * 转换为图片base64
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param type 转换图片类型
+	 * @param done 完成回调
+	 */
+	function convertToImage(canvasId, x, y, width, height, type, done) {
+		if (done === void 0) done = function() {};
+
+		if (type === undefined) {
+			type = 'png';
+		}
+		type = fixType(type);
+		if (/bmp/.test(type)) {
+			getImageData(canvasId, x, y, width, height, function(data, err) {
+				var strData = genBitmapImage(data);
+				tools_7(done) && done(makeURI(strData, 'image/' + type), err);
+			});
+		} else {
+			console.error('暂不支持生成\'' + type + '\'类型的base64图片');
+		}
+	}
+
+	var CanvasToBase64 = {
+		convertToImage: convertToImage,
+		// convertToPNG: function (width, height, done) {
+		//   return convertToImage(width, height, 'png', done)
+		// },
+		// convertToJPEG: function (width, height, done) {
+		//   return convertToImage(width, height, 'jpeg', done)
+		// },
+		// convertToGIF: function (width, height, done) {
+		//   return convertToImage(width, height, 'gif', done)
+		// },
+		convertToBMP: function(ref, done) {
+			if (ref === void 0) ref = {};
+			var canvasId = ref.canvasId;
+			var x = ref.x;
+			var y = ref.y;
+			var width = ref.width;
+			var height = ref.height;
+			if (done === void 0) done = function() {};
+
+			return convertToImage(canvasId, x, y, width, height, 'bmp', done)
+		}
+	};
+
+	function methods() {
+		var self = this;
+
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
+
+		var id = self.id;
+		var targetId = self.targetId;
+		var pixelRatio = self.pixelRatio;
+
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		self.updateCanvas = function(done) {
+			if (self.croperTarget) {
+				//  画布绘制图片
+				self.ctx.drawImage(
+					self.croperTarget,
+					self.imgLeft,
+					self.imgTop,
+					self.scaleWidth,
+					self.scaleHeight
+				);
+			}
+			tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
+
+			self.setBoundStyle(self.boundStyle); //	设置边界样式
+
+			self.ctx.draw(false, done);
+			return self
+		};
+
+		self.pushOrigin = self.pushOrign = function(src) {
+			self.src = src;
+
+			tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
+
+			return getImageInfo({
+					src: src
+				})
+				.then(function(res) {
+					var innerAspectRadio = res.width / res.height;
+					var customAspectRadio = width / height;
+
+					self.croperTarget = res.path;
+
+					if (innerAspectRadio < customAspectRadio) {
+						self.rectX = x;
+						self.baseWidth = width;
+						self.baseHeight = width / innerAspectRadio;
+						self.rectY = y - Math.abs((height - self.baseHeight) / 2);
+					} else {
+						self.rectY = y;
+						self.baseWidth = height * innerAspectRadio;
+						self.baseHeight = height;
+						self.rectX = x - Math.abs((width - self.baseWidth) / 2);
+					}
+
+					self.imgLeft = self.rectX;
+					self.imgTop = self.rectY;
+					self.scaleWidth = self.baseWidth;
+					self.scaleHeight = self.baseHeight;
+
+					self.update();
+
+					return new Promise(function(resolve) {
+						self.updateCanvas(resolve);
+					})
+				})
+				.then(function() {
+					tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self);
+				})
+		};
+
+		self.removeImage = function() {
+			self.src = '';
+			self.croperTarget = '';
+			return draw(self.ctx)
+		};
+
+		self.getCropperBase64 = function(done) {
+			if (done === void 0) done = function() {};
+
+			CanvasToBase64.convertToBMP({
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			}, done);
+		};
+
+		self.getCropperImage = function(opt, fn) {
+			var customOptions = opt;
+
+			var canvasOptions = {
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			};
+
+			var task = function() {
+				return Promise.resolve();
+			};
+
+			if (
+				tools_10(customOptions) &&
+				customOptions.original
+			) {
+				// original mode
+				task = function() {
+					self.targetCtx.drawImage(
+						self.croperTarget,
+						self.imgLeft * pixelRatio,
+						self.imgTop * pixelRatio,
+						self.scaleWidth * pixelRatio,
+						self.scaleHeight * pixelRatio
+					);
+
+					canvasOptions = {
+						canvasId: targetId,
+						x: x * pixelRatio,
+						y: y * pixelRatio,
+						width: width * pixelRatio,
+						height: height * pixelRatio
+					};
+
+					return draw(self.targetCtx)
+				};
+			}
+
+			return task()
+				.then(function() {
+					if (tools_10(customOptions)) {
+						canvasOptions = Object.assign({}, canvasOptions, customOptions);
+					}
+
+					if (tools_7(customOptions)) {
+						fn = customOptions;
+					}
+
+					var arg = canvasOptions.componentContext ?
+						[canvasOptions, canvasOptions.componentContext] :
+						[canvasOptions];
+
+					return canvasToTempFilePath.apply(null, arg)
+				})
+				.then(function(res) {
+					var tempFilePath = res.tempFilePath;
+
+					return tools_7(fn) ?
+						fn.call(self, tempFilePath, null) :
+						tempFilePath
+				})
+				.catch(function(err) {
+					if (tools_7(fn)) {
+						fn.call(self, null, err);
+					} else {
+						throw err
+					}
+				})
+		};
+	}
+
+	/**
+	 * 获取最新缩放值
+	 * @param oldScale 上一次触摸结束后的缩放值
+	 * @param oldDistance 上一次触摸结束后的双指距离
+	 * @param zoom 缩放系数
+	 * @param touch0 第一指touch对象
+	 * @param touch1 第二指touch对象
+	 * @returns {*}
+	 */
+	var getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1) {
+		var xMove, yMove, newDistance;
+		// 计算二指最新距离
+		xMove = Math.round(touch1.x - touch0.x);
+		yMove = Math.round(touch1.y - touch0.y);
+		newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+		return oldScale + 0.001 * zoom * (newDistance - oldDistance)
+	};
+
+	function update() {
+		var self = this;
+
+		if (!self.src) {
+			return
+		}
+
+		self.__oneTouchStart = function(touch) {
+			self.touchX0 = Math.round(touch.x);
+			self.touchY0 = Math.round(touch.y);
+		};
+
+		self.__oneTouchMove = function(touch) {
+			var xMove, yMove;
+			// 计算单指移动的距离
+			if (self.touchended) {
+				return self.updateCanvas()
+			}
+			xMove = Math.round(touch.x - self.touchX0);
+			yMove = Math.round(touch.y - self.touchY0);
+
+			var imgLeft = Math.round(self.rectX + xMove);
+			var imgTop = Math.round(self.rectY + yMove);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__twoTouchStart = function(touch0, touch1) {
+			var xMove, yMove, oldDistance;
+
+			self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2);
+			self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2);
+
+			// 计算两指距离
+			xMove = Math.round(touch1.x - touch0.x);
+			yMove = Math.round(touch1.y - touch0.y);
+			oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+			self.oldDistance = oldDistance;
+		};
+
+		self.__twoTouchMove = function(touch0, touch1) {
+			var oldScale = self.oldScale;
+			var oldDistance = self.oldDistance;
+			var scale = self.scale;
+			var zoom = self.zoom;
+
+			self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
+
+			//  设定缩放范围
+			self.newScale <= 1 && (self.newScale = 1);
+			self.newScale >= scale && (self.newScale = scale);
+
+			self.scaleWidth = Math.round(self.newScale * self.baseWidth);
+			self.scaleHeight = Math.round(self.newScale * self.baseHeight);
+			var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
+			var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__xtouchEnd = function() {
+			self.oldScale = self.newScale;
+			self.rectX = self.imgLeft;
+			self.rectY = self.imgTop;
+		};
+	}
+
+	var handle = {
+		//  图片手势初始监测
+		touchStart: function touchStart(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, true, null, null);
+
+			// 计算第一个触摸点的位置,并参照改点进行缩放
+			self.__oneTouchStart(touch0);
+
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchStart(touch0, touch1);
+			}
+		},
+
+		//  图片手势动态缩放
+		touchMove: function touchMove(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, null, true);
+
+			// 单指手势时触发
+			if (e.touches.length === 1) {
+				self.__oneTouchMove(touch0);
+			}
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchMove(touch0, touch1);
+			}
+		},
+
+		touchEnd: function touchEnd(e) {
+			var self = this;
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, false, false, true);
+			self.__xtouchEnd();
+		}
+	};
+
+	function cut() {
+		var self = this;
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height;
+		// 裁剪框默认高度,即整个画布高度
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		/**
+		 * 设置边界
+		 * @param imgLeft 图片左上角横坐标值
+		 * @param imgTop 图片左上角纵坐标值
+		 */
+		self.outsideBound = function(imgLeft, imgTop) {
+			self.imgLeft = imgLeft >= x ?
+				x :
+				self.scaleWidth + imgLeft - x <= width ?
+				x + width - self.scaleWidth :
+				imgLeft;
+
+			self.imgTop = imgTop >= y ?
+				y :
+				self.scaleHeight + imgTop - y <= height ?
+				y + height - self.scaleHeight :
+				imgTop;
+		};
+
+		/**
+		 * 设置边界样式
+		 * @param color	边界颜色
+		 */
+		self.setBoundStyle = function(ref) {
+			if (ref === void 0) ref = {};
+			var color = ref.color;
+			if (color === void 0) color = '#04b00f';
+			var mask = ref.mask;
+			if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)';
+			var lineWidth = ref.lineWidth;
+			if (lineWidth === void 0) lineWidth = 1;
+
+			var half = lineWidth / 2;
+			var boundOption = [{
+					start: {
+						x: x - half,
+						y: y + 10 - half
+					},
+					step1: {
+						x: x - half,
+						y: y - half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y - half
+					}
+				},
+				{
+					start: {
+						x: x - half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x - half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y + height + half
+					}
+				},
+				{
+					start: {
+						x: x + width - 10 + half,
+						y: y - half
+					},
+					step1: {
+						x: x + width + half,
+						y: y - half
+					},
+					step2: {
+						x: x + width + half,
+						y: y + 10 - half
+					}
+				},
+				{
+					start: {
+						x: x + width + half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x + width + half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + width - 10 + half,
+						y: y + height + half
+					}
+				}
+			];
+
+			// 绘制半透明层
+			self.ctx.beginPath();
+			self.ctx.setFillStyle(mask);
+			self.ctx.fillRect(0, 0, x, boundHeight);
+			self.ctx.fillRect(x, 0, width, y);
+			self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
+			self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
+			self.ctx.fill();
+
+			boundOption.forEach(function(op) {
+				self.ctx.beginPath();
+				self.ctx.setStrokeStyle(color);
+				self.ctx.setLineWidth(lineWidth);
+				self.ctx.moveTo(op.start.x, op.start.y);
+				self.ctx.lineTo(op.step1.x, op.step1.y);
+				self.ctx.lineTo(op.step2.x, op.step2.y);
+				self.ctx.stroke();
+			});
+		};
+	}
+
+	var version = "1.3.9";
+
+	var WeCropper = function WeCropper(params) {
+		var self = this;
+		var _default = {};
+
+		validator(self, DEFAULT);
+
+		Object.keys(DEFAULT).forEach(function(key) {
+			_default[key] = DEFAULT[key].default;
+		});
+		Object.assign(self, _default, params);
+
+		self.prepare();
+		self.attachPage();
+		self.createCtx();
+		self.observer();
+		self.cutt();
+		self.methods();
+		self.init();
+		self.update();
+
+		return self
+	};
+
+	WeCropper.prototype.init = function init() {
+		var self = this;
+		var src = self.src;
+
+		self.version = version;
+
+		typeof self.onReady === 'function' && self.onReady(self.ctx, self);
+
+		if (src) {
+			self.pushOrign(src);
+		} else {
+			self.updateCanvas();
+		}
+		setTouchState(self, false, false, false);
+
+		self.oldScale = 1;
+		self.newScale = 1;
+
+		return self
+	};
+
+	Object.assign(WeCropper.prototype, handle);
+
+	WeCropper.prototype.prepare = prepare;
+	WeCropper.prototype.observer = observer;
+	WeCropper.prototype.methods = methods;
+	WeCropper.prototype.cutt = cut;
+	WeCropper.prototype.update = update;
+
+	return WeCropper;
+
+})));

Diferenças do arquivo suprimidas por serem muito extensas
+ 24 - 0
components/uview-ui/components/u-avatar/u-avatar.vue


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

@@ -0,0 +1,153 @@
+<template>
+	<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{
+		bottom: bottom + 'rpx',
+		right: right + 'rpx',
+		borderRadius: mode == 'circle' ? '10000rpx' : '8rpx',
+		zIndex: uZIndex,
+		opacity: opacity
+	}, customStyle]">
+		<view class="u-back-top__content" v-if="!$slots.default && !$slots.$default">
+			<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
+			<view class="u-back-top__content__tips">
+				{{tips}}
+			</view>
+		</view>
+		<slot v-else />
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'u-back-top',
+		props: {
+			// 返回顶部的形状,circle-圆形,square-方形
+			mode: {
+				type: String,
+				default: 'circle'
+			},
+			// 自定义图标
+			icon: {
+				type: String,
+				default: 'arrow-upward'
+			},
+			// 提示文字
+			tips: {
+				type: String,
+				default: ''
+			},
+			// 返回顶部滚动时间
+			duration: {
+				type: [Number, String],
+				default: 100
+			},
+			// 滚动距离
+			scrollTop: {
+				type: [Number, String],
+				default: 0
+			},
+			// 距离顶部多少距离显示,单位rpx
+			top: {
+				type: [Number, String],
+				default: 400
+			},
+			// 返回顶部按钮到底部的距离,单位rpx
+			bottom: {
+				type: [Number, String],
+				default: 200
+			},
+			// 返回顶部按钮到右边的距离,单位rpx
+			right: {
+				type: [Number, String],
+				default: 40
+			},
+			// 层级
+			zIndex: {
+				type: [Number, String],
+				default: '9'
+			},
+			// 图标的样式,对象形式
+			iconStyle: {
+				type: Object,
+				default() {
+					return {
+						color: '#909399',
+						fontSize: '38rpx'
+					}
+				}
+			},
+			// 整个组件的样式
+			customStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			}
+		},
+		watch: {
+			showBackTop(nVal, oVal) {
+				// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度
+				// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果
+				if(nVal) {
+					this.uZIndex = this.zIndex;
+					this.opacity = 1;
+				} else {
+					this.uZIndex = -1;
+					this.opacity = 0;
+				}
+			}
+		},
+		computed: {
+			showBackTop() {
+				// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值
+				// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮
+				return this.scrollTop > uni.upx2px(this.top);
+			},
+		},
+		data() {
+			return {
+				// 不透明度,为了让组件有一个显示和隐藏的过渡动画
+				opacity: 0,
+				// 组件的z-index值,隐藏时设置为-1,就会看不到
+				uZIndex: -1
+			}
+		},
+		methods: {
+			backToTop() {
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: this.duration
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-back-top {
+		width: 80rpx;
+		height: 80rpx;
+		position: fixed;
+		z-index: 9;
+		@include vue-flex;
+		flex-direction: column;
+		justify-content: center;
+		background-color: #E1E1E1;
+		color: $u-content-color;
+		align-items: center;
+		transition: opacity 0.4s;
+		
+		&__content {
+			@include vue-flex;
+			flex-direction: column;
+			align-items: center;
+			
+			&__tips {
+				font-size: 24rpx;
+				transform: scale(0.8);
+				line-height: 1;
+			}
+		}
+	}
+</style>

+ 216 - 0
components/uview-ui/components/u-badge/u-badge.vue

@@ -0,0 +1,216 @@
+<template>
+	<view v-if="show" class="u-badge" :class="[
+			isDot ? 'u-badge-dot' : '', 
+			size == 'mini' ? 'u-badge-mini' : '',
+			type ? 'u-badge--bg--' + type : ''
+		]" :style="[{
+			top: offset[0] + 'rpx',
+			right: offset[1] + 'rpx',
+			fontSize: fontSize + 'rpx',
+			position: absolute ? 'absolute' : 'static',
+			color: color,
+			backgroundColor: bgColor
+		}, boxStyle]"
+	>
+		{{showText}}
+	</view>
+</template>
+
+<script>
+	/**
+	 * badge 角标
+	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+	 * @tutorial https://www.uviewui.com/components/badge.html
+	 * @property {String Number} count 展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为0且show-zero为false时隐藏
+	 * @property {Boolean} is-dot 不展示数字,只有一个小点(默认false)
+	 * @property {Boolean} absolute 组件是否绝对定位,为true时,offset参数才有效(默认true)
+	 * @property {String Number} overflow-count 展示封顶的数字值(默认99)
+	 * @property {String} type 使用预设的背景颜色(默认error)
+	 * @property {Boolean} show-zero 当数值为 0 时,是否展示 Badge(默认false)
+	 * @property {String} size Badge的尺寸,设为mini会得到小一号的Badge(默认default)
+	 * @property {Array} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,单位rpx。absolute为true时有效(默认[20, 20])
+	 * @property {String} color 字体颜色(默认#ffffff)
+	 * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
+	 * @property {Boolean} is-center 组件中心点是否和父组件右上角重合,优先级比offset高,如设置,offset参数会失效(默认false)
+	 * @example <u-badge type="error" count="7"></u-badge>
+	 */
+	export default {
+		name: 'u-badge',
+		props: {
+			// primary,warning,success,error,info
+			type: {
+				type: String,
+				default: 'error'
+			},
+			// default, mini
+			size: {
+				type: String,
+				default: 'default'
+			},
+			//是否是圆点
+			isDot: {
+				type: Boolean,
+				default: false
+			},
+			// 显示的数值内容
+			count: {
+				type: [Number, String],
+			},
+			// 展示封顶的数字值
+			overflowCount: {
+				type: Number,
+				default: 99
+			},
+			// 当数值为 0 时,是否展示 Badge
+			showZero: {
+				type: Boolean,
+				default: false
+			},
+			// 位置偏移
+			offset: {
+				type: Array,
+				default: () => {
+					return [20, 20]
+				}
+			},
+			// 是否开启绝对定位,开启了offset才会起作用
+			absolute: {
+				type: Boolean,
+				default: true
+			},
+			// 字体大小
+			fontSize: {
+				type: [String, Number],
+				default: '24'
+			},
+			// 字体演示
+			color: {
+				type: String,
+				default: '#ffffff'
+			},
+			// badge的背景颜色
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			// 是否让badge组件的中心点和父组件右上角重合,配置的话,offset将会失效
+			isCenter: {
+				type: Boolean,
+				default: false
+			}
+		},
+		computed: {
+			// 是否将badge中心与父组件右上角重合
+			boxStyle() {
+				let style = {};
+				if(this.isCenter) {
+					style.top = 0;
+					style.right = 0;
+					// Y轴-50%,意味着badge向上移动了badge自身高度一半,X轴50%,意味着向右移动了自身宽度一半
+					style.transform = "translateY(-50%) translateX(50%)";
+				} else {
+					style.top = this.offset[0] + 'rpx';
+					style.right = this.offset[1] + 'rpx';
+					style.transform = "translateY(0) translateX(0)";
+				}
+				// 如果尺寸为mini,后接上scal()
+				if(this.size == 'mini') {
+					style.transform = style.transform + " scale(0.8)";
+				}
+				return style;
+			},
+			// isDot类型时,不显示文字
+			showText() {
+				if(this.isDot) return '';
+				else {
+					if(this.count > this.overflowCount) return `${this.overflowCount}+`;
+					else return this.count;
+				}
+			},
+			// 是否显示组件
+			show() {
+				// 如果count的值为0,并且showZero设置为false,不显示组件
+				if(this.count == 0 && this.showZero == false) return false;
+				else return true;
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-badge {
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		line-height: 24rpx;
+		padding: 4rpx 8rpx;
+		border-radius: 100rpx;
+		z-index: 9;
+		
+		&--bg--primary {
+			background-color: $u-type-primary;
+		}
+		
+		&--bg--error {
+			background-color: $u-type-error;
+		}
+		
+		&--bg--success {
+			background-color: $u-type-success;
+		}
+		
+		&--bg--info {
+			background-color: $u-type-info;
+		}
+		
+		&--bg--warning {
+			background-color: $u-type-warning;
+		}
+	}
+	
+	.u-badge-dot {
+		height: 16rpx;
+		width: 16rpx;
+		border-radius: 100rpx;
+		line-height: 1;
+	}
+	
+	.u-badge-mini {
+		transform: scale(0.8);
+		transform-origin: center center;
+	}
+	
+	// .u-primary {
+	// 	background: $u-type-primary;
+	// 	color: #fff;
+	// }
+	
+	// .u-error {
+	// 	background: $u-type-error;
+	// 	color: #fff;
+	// }
+	
+	// .u-warning {
+	// 	background: $u-type-warning;
+	// 	color: #fff;
+	// }
+	
+	// .u-success {
+	// 	background: $u-type-success;
+	// 	color: #fff;
+	// }
+	
+	// .u-black {
+	// 	background: #585858;
+	// 	color: #fff;
+	// }
+	
+	.u-info {
+		background-color: $u-type-info;
+		color: #fff;
+	}
+</style>

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff