Browse Source

first commit

chubiao 1 year ago
commit
be3756702d
100 changed files with 64833 additions and 0 deletions
  1. 6 0
      .gitignore
  2. 31 0
      App.vue
  3. 0 0
      README.md
  4. 18 0
      api/dict.js
  5. 19 0
      api/kh.js
  6. 11 0
      api/login.js
  7. 55 0
      api/upload.js
  8. 44 0
      common/auth.js
  9. 366 0
      common/base64.js
  10. 160 0
      common/common.js
  11. 234 0
      common/crypto-js/aes.js
  12. 6059 0
      common/crypto-js/crypto-js.js
  13. 214 0
      common/idcard.js
  14. 35 0
      common/permission.js
  15. 75 0
      common/request.js
  16. 660 0
      common/sm4.js
  17. 43 0
      components/lzc-OCR/common/base64.js
  18. 110 0
      components/lzc-OCR/lzc-OCR.vue
  19. 17 0
      config.js
  20. 20 0
      index.html
  21. 29 0
      main.js
  22. 101 0
      manifest.json
  23. 6 0
      package.json
  24. 45 0
      pages.json
  25. 172 0
      pages/index/index.vue
  26. 179 0
      pages/login/index.vue
  27. 463 0
      pages/lr/base.vue
  28. 20 0
      pages/lr/eat.vue
  29. 8 0
      pages/lr/test.vue
  30. 253 0
      static/face/face.html
  31. 351 0
      static/face/js/alive_face.js
  32. 460 0
      static/face/js/ccv.js
  33. 8591 0
      static/face/js/clmtrackr.js
  34. 351 0
      static/face/js/face/alive_face.js
  35. 460 0
      static/face/js/face/ccv.js
  36. 8591 0
      static/face/js/face/clmtrackr.js
  37. 9472 0
      static/face/js/face/jquery-1.8.3.js
  38. 623 0
      static/face/js/face/model_pca_20_svm.js
  39. 4425 0
      static/face/js/face/numeric.js
  40. 44 0
      static/face/js/face/utils.js
  41. 9472 0
      static/face/js/jquery-1.8.3.js
  42. 623 0
      static/face/js/model_pca_20_svm.js
  43. 4425 0
      static/face/js/numeric.js
  44. 44 0
      static/face/js/utils.js
  45. BIN
      static/face/mp3/alive_eye.mp3
  46. BIN
      static/face/mp3/alive_head.mp3
  47. BIN
      static/face/mp3/alive_mouse.mp3
  48. BIN
      static/fonts/AlibabaPuHuiTi-3-55-Regular.ttf
  49. BIN
      static/fonts/AlibabaPuHuiTi-3-65-Medium.ttf
  50. BIN
      static/images/banner2.png
  51. BIN
      static/images/card.jpg
  52. BIN
      static/images/head.png
  53. BIN
      static/images/lr.png
  54. BIN
      static/images/sfsb.png
  55. BIN
      static/images/shqt.png
  56. 107 0
      static/index - 副本.html
  57. 107 0
      static/index.html
  58. BIN
      static/logo.png
  59. 10 0
      uni.promisify.adaptor.js
  60. 76 0
      uni.scss
  61. 26 0
      uni_modules/uni-card/changelog.md
  62. 270 0
      uni_modules/uni-card/components/uni-card/uni-card.vue
  63. 90 0
      uni_modules/uni-card/package.json
  64. 12 0
      uni_modules/uni-card/readme.md
  65. 47 0
      uni_modules/uni-data-checkbox/changelog.md
  66. 821 0
      uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue
  67. 84 0
      uni_modules/uni-data-checkbox/package.json
  68. 18 0
      uni_modules/uni-data-checkbox/readme.md
  69. 99 0
      uni_modules/uni-easyinput/changelog.md
  70. 54 0
      uni_modules/uni-easyinput/components/uni-easyinput/common.js
  71. 657 0
      uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue
  72. 87 0
      uni_modules/uni-easyinput/package.json
  73. 11 0
      uni_modules/uni-easyinput/readme.md
  74. 94 0
      uni_modules/uni-forms/changelog.md
  75. 627 0
      uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue
  76. 397 0
      uni_modules/uni-forms/components/uni-forms/uni-forms.vue
  77. 293 0
      uni_modules/uni-forms/components/uni-forms/utils.js
  78. 486 0
      uni_modules/uni-forms/components/uni-forms/validate.js
  79. 88 0
      uni_modules/uni-forms/package.json
  80. 23 0
      uni_modules/uni-forms/readme.md
  81. 40 0
      uni_modules/uni-icons/changelog.md
  82. 91 0
      uni_modules/uni-icons/components/uni-icons/uni-icons.uvue
  83. 110 0
      uni_modules/uni-icons/components/uni-icons/uni-icons.vue
  84. 664 0
      uni_modules/uni-icons/components/uni-icons/uniicons.css
  85. BIN
      uni_modules/uni-icons/components/uni-icons/uniicons.ttf
  86. 664 0
      uni_modules/uni-icons/components/uni-icons/uniicons_file.ts
  87. 649 0
      uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js
  88. 88 0
      uni_modules/uni-icons/package.json
  89. 8 0
      uni_modules/uni-icons/readme.md
  90. 19 0
      uni_modules/uni-load-more/changelog.md
  91. 5 0
      uni_modules/uni-load-more/components/uni-load-more/i18n/en.json
  92. 8 0
      uni_modules/uni-load-more/components/uni-load-more/i18n/index.js
  93. 5 0
      uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json
  94. 5 0
      uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json
  95. 399 0
      uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue
  96. 86 0
      uni_modules/uni-load-more/package.json
  97. 14 0
      uni_modules/uni-load-more/readme.md
  98. 8 0
      uni_modules/uni-scss/changelog.md
  99. 1 0
      uni_modules/uni-scss/index.scss
  100. 0 0
      uni_modules/uni-scss/package.json

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+node_modules/
+.project
+unpackage/
+.DS_Store
+.hbuilderx/
+package-lock.json

+ 31 - 0
App.vue

@@ -0,0 +1,31 @@
+<script>
+	// import routingIntercept  from '@/common/permission.js'
+	export default {
+		onLaunch: function() {
+			// routingIntercept()
+			console.log('App Launch')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style>
+	/*每个页面公共css */
+	@font-face {
+		font-family: 'puhui';
+		src: url('./static/fonts/AlibabaPuHuiTi-3-65-Medium.ttf');
+	}
+	.global-font {
+		font-family: 'puhui';
+		font-size: 16px;
+		color: #333;
+	}
+	uni-page-body, html, body {
+	  height: 100%;
+	}
+</style>

+ 0 - 0
README.md


+ 18 - 0
api/dict.js

@@ -0,0 +1,18 @@
+import request from '@/common/request.js'
+
+// 根据字典类型查询字典数据信息
+export const getDicts = (dictType) => {
+  return request({
+    url: '/api/system/dict/data/type/' + dictType,
+    method: 'get',
+  })
+}
+
+// 业务字典查询
+export const ObjdictList = (query) => {
+  return request({
+    url: '/api/system/dict/type/objdict',
+    method: 'get',
+    data: query
+  })
+}

+ 19 - 0
api/kh.js

@@ -0,0 +1,19 @@
+import request from '@/common/request.js'
+
+// 老人信息修改
+export function updateKhjbxx(data) {
+	return request({
+		url: '/api/business/lrjbxx',
+		method: 'put',
+		data: data
+	})
+}
+
+// 老人信息详情
+export function infoKhjbxx(id) {
+	return request({
+		url: '/api/business/lrjbxx/' + id,
+		method: 'get',
+		data: {}
+	})
+}

+ 11 - 0
api/login.js

@@ -0,0 +1,11 @@
+import request from '@/common/request.js'
+
+
+// 订单列表
+export function Login(data) {
+	return request({
+		url: '/api/auth/gzhlogin',
+		method: 'post',
+		data: data
+	})
+}

+ 55 - 0
api/upload.js

@@ -0,0 +1,55 @@
+import request from '@/common/request.js'
+import config from '../config.js'
+import {
+	getToken
+} from '../common/auth.js'
+
+/**
+ * 单一文件上传接口
+ * @file 文件地址
+ * @url 上传路径
+ * @formData 上传数据
+ */
+export function UploadOne(file, formData = {}) {
+	let header = {}
+	if (getToken()) {
+		header['Authorization'] = 'Bearer ' + getToken()
+	}
+	return new Promise((resolve, reject) => {
+		try {
+			uni.uploadFile({
+				url: '/api/file/upload',
+				filePath: file,
+				name: 'file',
+				formData: formData,
+				header: header,
+				success: (res) => {
+					console.log(res)
+					let r = JSON.parse(res.data)
+					resolve(r)
+				},
+				fail: (err) => {
+					console.log(err)
+					uni.showToast({
+						title: '服务器休息中,请稍后再试',
+						icon: "none",
+						position: "center",
+						duration: 3000
+					});
+				},
+				complete: (e) => {
+
+				}
+			})
+		} catch {
+			uni.showToast({
+				title: '网速不好哦!在试试',
+				icon: "none",
+				position: "center",
+				duration: 3000
+			})
+			uni.hideLoading()
+		}
+
+	})
+}

+ 44 - 0
common/auth.js

@@ -0,0 +1,44 @@
+const TokenKey = 'App-Token'
+const OpenidKey = 'App-Openid'
+const UserKey = 'App-User'
+
+export function getToken() {
+	return uni.getStorageSync(TokenKey)
+}
+
+export function setToken(token) {
+	return uni.setStorageSync(TokenKey, token)
+}
+
+export function removeToken() {
+	return uni.removeStorageSync(TokenKey)
+}
+
+
+
+export function getOpenid() {
+	return uni.getStorageSync(OpenidKey)
+}
+
+export function setOpenid(openid) {
+	return uni.setStorageSync(OpenidKey, openid)
+}
+
+export function removeOpenid() {
+	return uni.removeStorageSync(OpenidKey)
+}
+
+
+
+
+export function getUser() {
+	return uni.getStorageSync(UserKey)
+}
+
+export function setUser(user) {
+	return uni.setStorageSync(UserKey, user)
+}
+
+export function removeUser() {
+	return uni.removeStorageSync(UserKey)
+}

+ 366 - 0
common/base64.js

@@ -0,0 +1,366 @@
+if (Uint8Array)
+{
+  var _slice = Uint8Array.prototype.slice;
+
+  try {
+    // Can't be used with DOM elements in IE < 9
+    _slice.call(document.documentElement);
+  } catch (e) { // Fails in IE < 9
+
+    // This will work for genuine arrays, array-like objects,
+    // NamedNodeMap (attributes, entities, notations),
+    // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
+    // and will not fail on other DOM objects (as do DOM elements in IE < 9)
+    Uint8Array.prototype.slice = function(begin, end) {
+      // IE < 9 gets unhappy with an undefined end argument
+      end = (typeof end !== 'undefined') ? end : this.length;
+
+      // For native Array objects, we use the native slice function
+      if (Object.prototype.toString.call(this) === '[object Array]'){
+        return _slice.call(this, begin, end);
+      }
+
+      // For array like object we handle it ourselves.
+      var i, cloned = [],
+        size, len = this.length;
+
+      // Handle negative value for "begin"
+      var start = begin || 0;
+      start = (start >= 0) ? start : Math.max(0, len + start);
+
+      // Handle negative value for "end"
+      var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
+      if (end < 0) {
+        upTo = len + end;
+      }
+
+      // Actual expected size of the slice
+      size = upTo - start;
+
+      if (size > 0) {
+        cloned = new Array(size);
+        if (this.charAt) {
+          for (i = 0; i < size; i++) {
+            cloned[i] = this.charAt(start + i);
+          }
+        } else {
+          for (i = 0; i < size; i++) {
+            cloned[i] = this[start + i];
+          }
+        }
+      }
+
+      return cloned;
+    };
+  }
+if (!Uint8Array.from) {
+  Uint8Array.from = (function () {
+    var toStr = Object.prototype.toString;
+    var isCallable = function (fn) {
+      return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
+    };
+    var toInteger = function (value) {
+      var number = Number(value);
+      if (isNaN(number)) { return 0; }
+      if (number === 0 || !isFinite(number)) { return number; }
+      return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
+    };
+    var maxSafeInteger = Math.pow(2, 53) - 1;
+    var toLength = function (value) {
+      var len = toInteger(value);
+      return Math.min(Math.max(len, 0), maxSafeInteger);
+    };
+    var toItems = function (value) {
+      // support set
+      if (value.size > 0 && value.values) {
+        var values = value.values();
+        var it = values.next();
+        var o = [];
+        while (!it.done) {
+          o.push(it.value);
+          it = values.next();
+        }
+        return o;
+      }
+      return Object(value);
+    };
+    // The length property of the from method is 1.
+    return function from(arrayLike/*, mapFn, thisArg */) {
+      // 1. Let C be the this value.
+      var C = this;
+
+      // 2. Let items be ToObject(arrayLike).
+      var items = toItems(arrayLike);
+
+      // 3. ReturnIfAbrupt(items).
+      if (arrayLike == null) {
+        throw new TypeError("Array.from requires an array-like object - not null or undefined");
+      }
+
+      // 4. If mapfn is undefined, then let mapping be false.
+      var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
+      var T;
+      if (typeof mapFn !== 'undefined') {
+        // 5. else
+        // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
+        if (!isCallable(mapFn)) {
+          throw new TypeError('Array.from: when provided, the second argument must be a function');
+        }
+
+        // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
+        if (arguments.length > 2) {
+          T = arguments[2];
+        }
+      }
+
+      // 10. Let lenValue be Get(items, "length").
+      // 11. Let len be ToLength(lenValue).
+      var len = toLength(items.length);
+
+      // 13. If IsConstructor(C) is true, then
+      // 13. a. Let A be the result of calling the [[Construct]] internal method
+      // of C with an argument list containing the single item len.
+      // 14. a. Else, Let A be ArrayCreate(len).
+      var A = isCallable(C) ? Object(new C(len)) : new Array(len);
+
+      // 16. Let k be 0.
+      var k = 0;
+      // 17. Repeat, while k < len�� (also steps a - h)
+      var kValue;
+      while (k < len) {
+        kValue = items[k];
+        if (mapFn) {
+          A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
+        } else {
+          A[k] = kValue;
+        }
+        k += 1;
+      }
+      // 18. Let putStatus be Put(A, "length", len, true).
+      A.length = len;
+      // 20. Return A.
+      return A;
+    };
+  }());
+}
+}
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined'
+        ? module.exports = factory(global)
+        : typeof define === 'function' && define.amd
+        ? define(factory) : factory(global)
+}((
+    typeof self !== 'undefined' ? self
+        : typeof window !== 'undefined' ? window
+        : typeof global !== 'undefined' ? global
+: this
+), function(global) {
+    'use strict';
+    // existing version for noConflict()
+    global = global || {};
+    var _Base64 = global.Base64;
+    var version = "2.6.2";
+    // constants
+    var b64chars
+        = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+    var b64tab = function(bin) {
+        var t = {};
+        for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
+        return t;
+    }(b64chars);
+    var fromCharCode = String.fromCharCode;
+    // encoder stuff
+    var cb_utob = function(c) {
+        if (c.length < 2) {
+            var cc = c.charCodeAt(0);
+            return cc < 0x80 ? c
+                : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
+                                + fromCharCode(0x80 | (cc & 0x3f)))
+                : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
+                    + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
+                    + fromCharCode(0x80 | ( cc         & 0x3f)));
+        } else {
+            var cc = 0x10000
+                + (c.charCodeAt(0) - 0xD800) * 0x400
+                + (c.charCodeAt(1) - 0xDC00);
+            return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
+                    + fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
+                    + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
+                    + fromCharCode(0x80 | ( cc         & 0x3f)));
+        }
+    };
+    var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
+    var utob = function(u) {
+        return u.replace(re_utob, cb_utob);
+    };
+    var cb_encode = function(ccc) {
+        var padlen = [0, 2, 1][ccc.length % 3],
+        ord = ccc.charCodeAt(0) << 16
+            | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
+            | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
+        chars = [
+            b64chars.charAt( ord >>> 18),
+            b64chars.charAt((ord >>> 12) & 63),
+            padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
+            padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
+        ];
+        return chars.join('');
+    };
+    var btoa = global.btoa && typeof global.btoa == 'function'
+        ? function(b){ return global.btoa(b) } : function(b) {
+        if (b.match(/[^\x00-\xFF]/)) throw new RangeError(
+            'The string contains invalid characters.'
+        );
+        return b.replace(/[\s\S]{1,3}/g, cb_encode);
+    };
+    var _encode = function(u) {
+        return btoa(utob(String(u)));
+    };
+    var mkUriSafe = function (b64) {
+        return b64.replace(/[+\/]/g, function(m0) {
+            return m0 == '+' ? '-' : '_';
+        }).replace(/=/g, '');
+    };
+    var encode = function(u, urisafe) {
+        return urisafe ? mkUriSafe(_encode(u)) : _encode(u);
+    };
+    var encodeURI = function(u) { return encode(u, true) };
+    var fromUint8Array;
+    if (global.Uint8Array) fromUint8Array = function(a, urisafe) {
+        // return btoa(fromCharCode.apply(null, a));
+        var b64 = '';
+        for (var i = 0, l = a.length; i < l; i += 3) {
+            var a0 = a[i], a1 = a[i+1], a2 = a[i+2];
+            var ord = a0 << 16 | a1 << 8 | a2;
+            b64 +=    b64chars.charAt( ord >>> 18)
+                +     b64chars.charAt((ord >>> 12) & 63)
+                + ( typeof a1 != 'undefined'
+                    ? b64chars.charAt((ord >>>  6) & 63) : '=')
+                + ( typeof a2 != 'undefined'
+                    ? b64chars.charAt( ord         & 63) : '=');
+        }
+        return urisafe ? mkUriSafe(b64) : b64;
+    };
+    // decoder stuff
+    var re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g;
+    var cb_btou = function(cccc) {
+        switch(cccc.length) {
+        case 4:
+            var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
+                |    ((0x3f & cccc.charCodeAt(1)) << 12)
+                |    ((0x3f & cccc.charCodeAt(2)) <<  6)
+                |     (0x3f & cccc.charCodeAt(3)),
+            offset = cp - 0x10000;
+            return (fromCharCode((offset  >>> 10) + 0xD800)
+                    + fromCharCode((offset & 0x3FF) + 0xDC00));
+        case 3:
+            return fromCharCode(
+                ((0x0f & cccc.charCodeAt(0)) << 12)
+                    | ((0x3f & cccc.charCodeAt(1)) << 6)
+                    |  (0x3f & cccc.charCodeAt(2))
+            );
+        default:
+            return  fromCharCode(
+                ((0x1f & cccc.charCodeAt(0)) << 6)
+                    |  (0x3f & cccc.charCodeAt(1))
+            );
+        }
+    };
+    var btou = function(b) {
+        return b.replace(re_btou, cb_btou);
+    };
+    var cb_decode = function(cccc) {
+        var len = cccc.length,
+        padlen = len % 4,
+        n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
+            | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
+            | (len > 2 ? b64tab[cccc.charAt(2)] <<  6 : 0)
+            | (len > 3 ? b64tab[cccc.charAt(3)]       : 0),
+        chars = [
+            fromCharCode( n >>> 16),
+            fromCharCode((n >>>  8) & 0xff),
+            fromCharCode( n         & 0xff)
+        ];
+        chars.length -= [0, 0, 2, 1][padlen];
+        return chars.join('');
+    };
+    var _atob = global.atob && typeof global.atob == 'function'
+        ? function(a){ return global.atob(a) } : function(a){
+        return a.replace(/\S{1,4}/g, cb_decode);
+    };
+    var atob = function(a) {
+        return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
+    };
+    var _decode = function(a) { return btou(_atob(a)) };
+    var decode = function(a){
+        return _decode(
+            String(a).replace(/[-_]/g, function(m0) {
+                return m0 == '-' ? '+' : '/'
+            }).replace(/[^A-Za-z0-9\+\/]/g, '')
+        );
+    };
+    var toUint8Array;
+    if (global.Uint8Array) toUint8Array = function(a) {
+         return Uint8Array.from(atob(a), function(c) {
+            return c.charCodeAt(0);
+        });
+    };
+    var noConflict = function() {
+        var Base64 = global.Base64;
+        global.Base64 = _Base64;
+        return Base64;
+    };
+    // export Base64
+    global.Base64 = {
+        VERSION: version,
+        atob: atob,
+        btoa: btoa,
+        fromBase64: decode,
+        toBase64: encode,
+        utob: utob,
+        encode: encode,
+        encodeURI: encodeURI,
+        btou: btou,
+        decode: decode,
+        noConflict: noConflict,
+        fromUint8Array: fromUint8Array,
+        toUint8Array: toUint8Array
+    };
+    // if ES5 is available, make Base64.extendString() available
+    if (typeof Object.defineProperty === 'function') {
+        var noEnum = function(v){
+            return {value:v,enumerable:false,writable:true,configurable:true};
+        };
+        global.Base64.extendString = function () {
+            Object.defineProperty(
+                String.prototype, 'fromBase64', noEnum(function () {
+                    return decode(this)
+                }));
+            Object.defineProperty(
+                String.prototype, 'toBase64', noEnum(function (urisafe) {
+                    return encode(this, urisafe)
+                }));
+            Object.defineProperty(
+                String.prototype, 'toBase64URI', noEnum(function () {
+                    return encode(this, true)
+                }));
+        };
+    }
+    //
+    // export Base64 to the namespace
+    //
+    if (global['Meteor']) { // Meteor.js
+        Base64 = global.Base64;
+    }
+    // module.exports and AMD are mutually exclusive.
+    // module.exports has precedence.
+    if (typeof module !== 'undefined' && module.exports) {
+        module.exports.Base64 = global.Base64;
+    }
+    else if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define([], function(){ return global.Base64 });
+    }
+    // that's it!
+    return {Base64: global.Base64}
+}));

+ 160 - 0
common/common.js

@@ -0,0 +1,160 @@
+import {
+	getDicts
+} from '@/api/dict.js'
+
+/**
+ * 显示消息提示框
+ * @param content 提示的标题
+ */
+export function toast(content) {
+	uni.showToast({
+		icon: 'none',
+		title: content
+	})
+}
+
+/**
+ * 显示模态弹窗
+ * @param content 提示的标题
+ */
+export function showConfirm(content) {
+	return new Promise((resolve, reject) => {
+		uni.showModal({
+			title: '提示',
+			content: content,
+			cancelText: '取消',
+			confirmText: '确定',
+			success: function(res) {
+				resolve(res)
+			}
+		})
+	})
+}
+
+/**
+ * 参数处理
+ * @param params 参数
+ */
+export function tansParams(params) {
+	let result = ''
+	for (const propName of Object.keys(params)) {
+		const value = params[propName]
+		var part = encodeURIComponent(propName) + "="
+		if (value !== null && value !== "" && typeof(value) !== "undefined") {
+			if (typeof value === 'object') {
+				for (const key of Object.keys(value)) {
+					if (value[key] !== null && value[key] !== "" && typeof(value[key]) !== 'undefined') {
+						let params = propName + '[' + key + ']'
+						var subPart = encodeURIComponent(params) + "="
+						result += subPart + encodeURIComponent(value[key]) + "&"
+					}
+				}
+			} else {
+				result += part + encodeURIComponent(value) + "&"
+			}
+		}
+	}
+	return result
+}
+
+// 返回上一页
+export function back() {
+	uni.navigateBack({
+		delta: 1
+	})
+}
+
+// 页面跳转
+export function navigateTo(url) {
+	uni.navigateTo({
+		url: url
+	})
+}
+
+// 回显数据字典
+export function transDictLabel(datas, value) {
+	if (value === undefined) {
+		return "";
+	}
+	var actions = [];
+	Object.keys(datas).some((key) => {
+		if (datas[key].value == ('' + value)) {
+			actions.push(datas[key].text);
+			return true;
+		}
+	})
+	if (actions.length === 0) {
+		actions.push(value);
+	}
+	return actions.join('');
+}
+
+/**
+ * 批量获取字典数据
+ * dictTypeArr: 字典名称数组
+ * result: 接数据容器
+ */
+export function getDictList(dictTypeArr, result) {
+	dictTypeArr.forEach(dictType => {
+		getDicts(dictType).then(res => {
+			if (res.code !== 200) return
+			result[dictType] = changeDictAttrName(res.data)
+		})
+	})
+}
+
+// 更改字典数据的属性名
+export function changeDictAttrName(array) {
+	let result = JSON.stringify(array).replaceAll('dictLabel', 'text').replaceAll('dictValue', 'value')
+	return JSON.parse(result)
+}
+
+// 匹配字典值
+export function getDictInfo(array, value) {
+	return array.filter(e => e.text.includes(value))
+}
+
+
+/**
+ * @description 本地图片转base64方法(兼容APP、H5、小程序)
+ * @param {number} path 图片本地路径
+ * @returns Promise对象
+ */
+const toBase64 = (path) => {
+	return new Promise((resolve, reject) => {
+		// #ifdef APP-PLUS
+		plus.io.resolveLocalFileSystemURL(path, (entry) => {
+			entry.file((file) => {
+				let fileReader = new plus.io.FileReader()
+				fileReader.readAsDataURL(file)
+				fileReader.onloadend = (evt) => {
+					let base64 = evt.target.result.split(",")[1]
+					resolve(base64)
+				}
+			})
+		})
+		// #endif
+		// #ifdef H5
+		uni.request({
+			url: path,
+			responseType: 'arraybuffer',
+			success: (res) => {
+				resolve(uni.arrayBufferToBase64(res.data))
+			}
+		})
+		// #endif
+		// #ifdef MP-WEIXIN
+		uni.getFileSystemManager().readFile({
+			filePath: path,
+			encoding: 'base64',
+			success: (res) => {
+				resolve(res.data)
+			}
+		})
+		// #endif
+	})
+}
+
+export {
+	toBase64
+}

+ 234 - 0
common/crypto-js/aes.js

@@ -0,0 +1,234 @@
+;(function (root, factory, undef) {
+	if (typeof exports === "object") {
+		// CommonJS
+		module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core"));
+	}
+	else if (typeof define === "function" && define.amd) {
+		// AMD
+		define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory);
+	}
+	else {
+		// Global (browser)
+		factory(root.CryptoJS);
+	}
+}(this, function (CryptoJS) {
+
+	(function () {
+	    // Shortcuts
+	    var C = CryptoJS;
+	    var C_lib = C.lib;
+	    var BlockCipher = C_lib.BlockCipher;
+	    var C_algo = C.algo;
+
+	    // Lookup tables
+	    var SBOX = [];
+	    var INV_SBOX = [];
+	    var SUB_MIX_0 = [];
+	    var SUB_MIX_1 = [];
+	    var SUB_MIX_2 = [];
+	    var SUB_MIX_3 = [];
+	    var INV_SUB_MIX_0 = [];
+	    var INV_SUB_MIX_1 = [];
+	    var INV_SUB_MIX_2 = [];
+	    var INV_SUB_MIX_3 = [];
+
+	    // Compute lookup tables
+	    (function () {
+	        // Compute double table
+	        var d = [];
+	        for (var i = 0; i < 256; i++) {
+	            if (i < 128) {
+	                d[i] = i << 1;
+	            } else {
+	                d[i] = (i << 1) ^ 0x11b;
+	            }
+	        }
+
+	        // Walk GF(2^8)
+	        var x = 0;
+	        var xi = 0;
+	        for (var i = 0; i < 256; i++) {
+	            // Compute sbox
+	            var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4);
+	            sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63;
+	            SBOX[x] = sx;
+	            INV_SBOX[sx] = x;
+
+	            // Compute multiplication
+	            var x2 = d[x];
+	            var x4 = d[x2];
+	            var x8 = d[x4];
+
+	            // Compute sub bytes, mix columns tables
+	            var t = (d[sx] * 0x101) ^ (sx * 0x1010100);
+	            SUB_MIX_0[x] = (t << 24) | (t >>> 8);
+	            SUB_MIX_1[x] = (t << 16) | (t >>> 16);
+	            SUB_MIX_2[x] = (t << 8)  | (t >>> 24);
+	            SUB_MIX_3[x] = t;
+
+	            // Compute inv sub bytes, inv mix columns tables
+	            var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
+	            INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8);
+	            INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16);
+	            INV_SUB_MIX_2[sx] = (t << 8)  | (t >>> 24);
+	            INV_SUB_MIX_3[sx] = t;
+
+	            // Compute next counter
+	            if (!x) {
+	                x = xi = 1;
+	            } else {
+	                x = x2 ^ d[d[d[x8 ^ x2]]];
+	                xi ^= d[d[xi]];
+	            }
+	        }
+	    }());
+
+	    // Precomputed Rcon lookup
+	    var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
+
+	    /**
+	     * AES block cipher algorithm.
+	     */
+	    var AES = C_algo.AES = BlockCipher.extend({
+	        _doReset: function () {
+	            var t;
+
+	            // Skip reset of nRounds has been set before and key did not change
+	            if (this._nRounds && this._keyPriorReset === this._key) {
+	                return;
+	            }
+
+	            // Shortcuts
+	            var key = this._keyPriorReset = this._key;
+	            var keyWords = key.words;
+	            var keySize = key.sigBytes / 4;
+
+	            // Compute number of rounds
+	            var nRounds = this._nRounds = keySize + 6;
+
+	            // Compute number of key schedule rows
+	            var ksRows = (nRounds + 1) * 4;
+
+	            // Compute key schedule
+	            var keySchedule = this._keySchedule = [];
+	            for (var ksRow = 0; ksRow < ksRows; ksRow++) {
+	                if (ksRow < keySize) {
+	                    keySchedule[ksRow] = keyWords[ksRow];
+	                } else {
+	                    t = keySchedule[ksRow - 1];
+
+	                    if (!(ksRow % keySize)) {
+	                        // Rot word
+	                        t = (t << 8) | (t >>> 24);
+
+	                        // Sub word
+	                        t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
+
+	                        // Mix Rcon
+	                        t ^= RCON[(ksRow / keySize) | 0] << 24;
+	                    } else if (keySize > 6 && ksRow % keySize == 4) {
+	                        // Sub word
+	                        t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
+	                    }
+
+	                    keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t;
+	                }
+	            }
+
+	            // Compute inv key schedule
+	            var invKeySchedule = this._invKeySchedule = [];
+	            for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) {
+	                var ksRow = ksRows - invKsRow;
+
+	                if (invKsRow % 4) {
+	                    var t = keySchedule[ksRow];
+	                } else {
+	                    var t = keySchedule[ksRow - 4];
+	                }
+
+	                if (invKsRow < 4 || ksRow <= 4) {
+	                    invKeySchedule[invKsRow] = t;
+	                } else {
+	                    invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^
+	                                               INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]];
+	                }
+	            }
+	        },
+
+	        encryptBlock: function (M, offset) {
+	            this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX);
+	        },
+
+	        decryptBlock: function (M, offset) {
+	            // Swap 2nd and 4th rows
+	            var t = M[offset + 1];
+	            M[offset + 1] = M[offset + 3];
+	            M[offset + 3] = t;
+
+	            this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX);
+
+	            // Inv swap 2nd and 4th rows
+	            var t = M[offset + 1];
+	            M[offset + 1] = M[offset + 3];
+	            M[offset + 3] = t;
+	        },
+
+	        _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) {
+	            // Shortcut
+	            var nRounds = this._nRounds;
+
+	            // Get input, add round key
+	            var s0 = M[offset]     ^ keySchedule[0];
+	            var s1 = M[offset + 1] ^ keySchedule[1];
+	            var s2 = M[offset + 2] ^ keySchedule[2];
+	            var s3 = M[offset + 3] ^ keySchedule[3];
+
+	            // Key schedule row counter
+	            var ksRow = 4;
+
+	            // Rounds
+	            for (var round = 1; round < nRounds; round++) {
+	                // Shift rows, sub bytes, mix columns, add round key
+	                var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++];
+	                var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++];
+	                var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++];
+	                var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++];
+
+	                // Update state
+	                s0 = t0;
+	                s1 = t1;
+	                s2 = t2;
+	                s3 = t3;
+	            }
+
+	            // Shift rows, sub bytes, add round key
+	            var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++];
+	            var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++];
+	            var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++];
+	            var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++];
+
+	            // Set output
+	            M[offset]     = t0;
+	            M[offset + 1] = t1;
+	            M[offset + 2] = t2;
+	            M[offset + 3] = t3;
+	        },
+
+	        keySize: 256/32
+	    });
+
+	    /**
+	     * Shortcut functions to the cipher's object interface.
+	     *
+	     * @example
+	     *
+	     *     var ciphertext = CryptoJS.AES.encrypt(message, key, cfg);
+	     *     var plaintext  = CryptoJS.AES.decrypt(ciphertext, key, cfg);
+	     */
+	    C.AES = BlockCipher._createHelper(AES);
+	}());
+
+
+	return CryptoJS.AES;
+
+}));

File diff suppressed because it is too large
+ 6059 - 0
common/crypto-js/crypto-js.js


+ 214 - 0
common/idcard.js

@@ -0,0 +1,214 @@
+//身份证号合法性校验
+var idCardNoUtil = {
+	/*省,直辖市代码表*/
+	provinceAndCitys: {
+		11: "北京",
+		12: "天津",
+		13: "河北",
+		14: "山西",
+		15: "内蒙古",
+		21: "辽宁",
+		22: "吉林",
+		23: "黑龙江",
+		31: "上海",
+		32: "江苏",
+		33: "浙江",
+		34: "安徽",
+		35: "福建",
+		36: "江西",
+		37: "山东",
+		41: "河南",
+		42: "湖北",
+		43: "湖南",
+		44: "广东",
+		45: "广西",
+		46: "海南",
+		50: "重庆",
+		51: "四川",
+		52: "贵州",
+		53: "云南",
+		54: "西藏",
+		61: "陕西",
+		62: "甘肃",
+		63: "青海",
+		64: "宁夏",
+		65: "新疆",
+		71: "台湾",
+		81: "香港",
+		82: "澳门",
+		91: "国外"
+	},
+
+	/*每位加权因子*/
+	powers: ["7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2"],
+
+	/*第18位校检码*/
+	parityBit: ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"],
+
+	/*性别*/
+	genders: {
+		male: "男",
+		female: "女"
+	},
+
+	/*校验地址码*/
+	checkAddressCode: function(addressCode) {
+		var check = /^[1-9]\d{5}$/.test(addressCode);
+		if (!check) return false;
+		if (idCardNoUtil.provinceAndCitys[parseInt(addressCode.substring(0, 2))]) {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/*校验日期码*/
+	checkBirthDayCode: function(birDayCode) {
+		var check = /^[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))$/.test(birDayCode);
+		if (!check) return false;
+		var yyyy = parseInt(birDayCode.substring(0, 4), 10);
+		var mm = parseInt(birDayCode.substring(4, 6), 10);
+		var dd = parseInt(birDayCode.substring(6), 10);
+		var xdata = new Date(yyyy, mm - 1, dd);
+		if (xdata > new Date()) {
+			return false; //生日不能大于当前日期
+		} else if ((xdata.getFullYear() == yyyy) && (xdata.getMonth() == mm - 1) && (xdata.getDate() == dd)) {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/*计算校检码*/
+	getParityBit: function(idCardNo) {
+		var id17 = idCardNo.substring(0, 17);
+		/*加权 */
+		var power = 0;
+		for (var i = 0; i < 17; i++) {
+			power += parseInt(id17.charAt(i), 10) * parseInt(idCardNoUtil.powers[i]);
+		}
+		/*取模*/
+		var mod = power % 11;
+		return idCardNoUtil.parityBit[mod];
+	},
+
+	/*验证校检码*/
+	checkParityBit: function(idCardNo) {
+		var parityBit = idCardNo.charAt(17).toUpperCase();
+		if (idCardNoUtil.getParityBit(idCardNo) == parityBit) {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/*校验15位或18位的身份证号码*/
+	checkIdCardNo: function(idCardNo) {
+		//15位和18位身份证号码的基本校验
+		var check = /^\d{15}|(\d{17}(\d|x|X))$/.test(idCardNo);
+		if (!check) return false;
+		//判断长度为15位或18位
+		if (idCardNo.length == 15) {
+			return idCardNoUtil.check15IdCardNo(idCardNo);
+		} else if (idCardNo.length == 18) {
+			return idCardNoUtil.check18IdCardNo(idCardNo);
+		} else {
+			return false;
+		}
+	},
+
+	//校验15位的身份证号码
+	check15IdCardNo: function(idCardNo) {
+		//15位身份证号码的基本校验
+		var check = /^[1-9]\d{7}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}$/.test(idCardNo);
+		if (!check) return false;
+		//校验地址码
+		var addressCode = idCardNo.substring(0, 6);
+		check = idCardNoUtil.checkAddressCode(addressCode);
+		if (!check) return false;
+		var birDayCode = '19' + idCardNo.substring(6, 12);
+		//校验日期码
+		return idCardNoUtil.checkBirthDayCode(birDayCode);
+	},
+
+	//校验18位的身份证号码
+	check18IdCardNo: function(idCardNo) {
+		//18位身份证号码的基本格式校验
+		var check = /^[1-9]\d{5}[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}(\d|x|X)$/
+			.test(idCardNo);
+		if (!check) return false;
+		//校验地址码
+		var addressCode = idCardNo.substring(0, 6);
+		check = idCardNoUtil.checkAddressCode(addressCode);
+		if (!check) return false;
+		//校验日期码
+		var birDayCode = idCardNo.substring(6, 14);
+		check = idCardNoUtil.checkBirthDayCode(birDayCode);
+		if (!check) return false;
+		//验证校检码
+		return idCardNoUtil.checkParityBit(idCardNo);
+	},
+
+    // 日期格式化
+	formateDateCN: function(day) {
+		var yyyy = day.substring(0, 4);
+		var mm = day.substring(4, 6);
+		var dd = day.substring(6);
+		// return yyyy + '-' + mm + '-' + dd;
+		return yyyy+mm+dd;
+	},
+
+	//获取信息
+	getIdCardInfo: function(idCardNo) {
+		var idCardInfo = {
+			gender: "", //性别
+			birthday: "" // 出生日期(yyyy-mm-dd)
+		};
+		if (idCardNo.length == 15) {
+			var aday = '19' + idCardNo.substring(6, 12);
+			idCardInfo.birthday = idCardNoUtil.formateDateCN(aday);
+			if (parseInt(idCardNo.charAt(14)) % 2 == 0) {
+				idCardInfo.gender = idCardNoUtil.genders.female;
+			} else {
+				idCardInfo.gender = idCardNoUtil.genders.male;
+			}
+		} else if (idCardNo.length == 18) {
+			var aday = idCardNo.substring(6, 14);
+			idCardInfo.birthday = idCardNoUtil.formateDateCN(aday);
+			if (parseInt(idCardNo.charAt(16)) % 2 == 0) {
+				idCardInfo.gender = idCardNoUtil.genders.female;
+			} else {
+				idCardInfo.gender = idCardNoUtil.genders.male;
+			}
+
+		}
+		return idCardInfo;
+	},
+
+	/*18位转15位*/
+	getId15: function(idCardNo) {
+		if (idCardNo.length == 15) {
+			return idCardNo;
+		} else if (idCardNo.length == 18) {
+			return idCardNo.substring(0, 6) + idCardNo.substring(8, 17);
+		} else {
+			return null;
+		}
+	},
+
+	/*15位转18位*/
+	getId18: function(idCardNo) {
+		if (idCardNo.length == 15) {
+			var id17 = idCardNo.substring(0, 6) + '19' + idCardNo.substring(6);
+			var parityBit = idCardNoUtil.getParityBit(id17);
+			return id17 + parityBit;
+		} else if (idCardNo.length == 18) {
+			return idCardNo;
+		} else {
+			return null;
+		}
+	}
+};
+
+
+export default idCardNoUtil;

+ 35 - 0
common/permission.js

@@ -0,0 +1,35 @@
+// 白名单
+const whiteList = ['pages/login/index']
+ 
+export default async function() {
+	const list = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab']
+	// 给uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab这4个路由方法添加拦截器
+	list.forEach(item => {
+		uni.addInterceptor(item, {
+			invoke(e) {
+			    // 获取要跳转的页面路径
+				const url = e.url.split('?')[0]
+				// 判断当前窗口是白名单,如果是则不重定向路由
+				var pass = false
+				if (whiteList) {
+					pass = whiteList.indexOf(url) != -1
+				}
+				// 不是白名单并且没有token
+				if (!pass && !uni.getStorageSync('token')) {
+					uni.showToast({
+						title: '请先登录',
+						icon: 'none'
+					})
+					uni.navigateTo({
+						url: "/pages/login/index"
+					})
+					return false
+				}
+				return e
+			},
+			fail(err) { // 失败回调拦截
+				console.log(err)
+			}
+		})
+	})
+}

+ 75 - 0
common/request.js

@@ -0,0 +1,75 @@
+import { getToken, setToken } from './auth.js'
+import { toast } from './common.js'
+import config from '../config.js'
+
+// const BASE_URL = config.service
+const BASE_URL = ''
+
+const request = config => {
+  config.header = config.header || {}
+  let isRequest = true
+  if (getToken()) {
+    config.header['Authorization'] = 'Bearer ' + getToken()
+    // isRequest = true
+  } 
+  console.log("入参:", config,BASE_URL + config.url)
+  if (isRequest) {
+    return new Promise((resolve, reject) => {
+      uni.request({
+        header: config.header,
+        method: config.method.toUpperCase() || 'GET',
+        dataType: 'json',
+        timeout: config.timeout || 10000,
+        url: BASE_URL + config.url,
+        data: config.data,
+      }).then((res) => {
+        console.log("请求返回:", res)
+        if (res.statusCode === 200) {
+          const { code, msg } = res.data
+
+          if (code === 401) {
+            uni.showModal({
+              title: '系统提示',
+              content: '登录状态已过期,请重新登录',
+              showCancel: false,
+              success: function(res) {
+                if (res.confirm) {
+                  uni.reLaunch({
+                    url: '/pages/login/index'
+                  })
+                }
+              }
+            });
+          } else if (code !== 200) {
+            toast(msg)
+            reject(code)
+          }
+
+          resolve(res.data)
+        } else {
+          toast('未知错误,请反馈给管理员')
+        }
+      }).catch(error => {
+        console.log("error", error)
+        let {
+          message
+        } = error
+        if (message === 'Network Error') {
+          message = '后端接口连接异常'
+        } else if (message.includes('timeout')) {
+          message = '系统接口请求超时'
+        } else if (message.includes('Request failed with status code')) {
+          message = '系统接口' + message.substr(message.length - 3) + '异常'
+        }
+
+        toast(message)
+        reject(error)
+      })
+    })
+
+  }
+
+
+}
+
+export default request

+ 660 - 0
common/sm4.js

@@ -0,0 +1,660 @@
+import CryptoJS from './crypto-js/crypto-js.js'
+// import CryptoJS from './crypto-js/crypto-js.js'
+var Base64 = Base64 || require('@/common/base64.js').Base64;
+import {
+	getToken
+} from './auth.js'
+/**
+ * 国密SM4加密算法
+ */
+function SM4_Context() {
+	this.mode = 1;
+	this.isPadding = true;
+	this.sk = new Array(32);
+}
+
+function SM4() {
+	this.SM4_ENCRYPT = 1;
+	this.SM4_DECRYPT = 0;
+
+	var SboxTable = [0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
+		0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
+		0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
+		0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
+		0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
+		0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
+		0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
+		0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
+		0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
+		0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
+		0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
+		0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
+		0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
+		0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
+		0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
+		0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48
+	];
+
+	var FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc];
+
+	var CK = [0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
+		0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
+		0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
+		0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
+		0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
+		0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
+		0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
+		0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
+	];
+
+	this.GET_ULONG_BE = function(b, i) {
+		return (b[i] & 0xff) << 24 | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | (b[i + 3] & 0xff) &
+			0xffffffff;
+	}
+
+	this.PUT_ULONG_BE = function(n, b, i) {
+		var t1 = (0xFF & (n >> 24));
+		var t2 = (0xFF & (n >> 16));
+		var t3 = (0xFF & (n >> 8));
+		var t4 = (0xFF & (n));
+		b[i] = t1 > 128 ? t1 - 256 : t1;
+		b[i + 1] = t2 > 128 ? t2 - 256 : t2;
+		b[i + 2] = t3 > 128 ? t3 - 256 : t3;
+		b[i + 3] = t4 > 128 ? t4 - 256 : t4;
+	}
+
+	this.SHL = function(x, n) {
+		return (x & 0xFFFFFFFF) << n;
+	}
+
+	this.ROTL = function(x, n) {
+		var s = this.SHL(x, n);
+		var ss = x >> (32 - n);
+		return this.SHL(x, n) | x >> (32 - n);
+	}
+
+	this.sm4Lt = function(ka) {
+		var bb = 0;
+		var c = 0;
+		var a = new Array(4);
+		var b = new Array(4);
+		this.PUT_ULONG_BE(ka, a, 0);
+		b[0] = this.sm4Sbox(a[0]);
+		b[1] = this.sm4Sbox(a[1]);
+		b[2] = this.sm4Sbox(a[2]);
+		b[3] = this.sm4Sbox(a[3]);
+		bb = this.GET_ULONG_BE(b, 0);
+		c = bb ^ this.ROTL(bb, 2) ^ this.ROTL(bb, 10) ^ this.ROTL(bb, 18) ^ this.ROTL(bb, 24);
+		return c;
+	}
+
+	this.sm4F = function(x0, x1, x2, x3, rk) {
+		return x0 ^ this.sm4Lt(x1 ^ x2 ^ x3 ^ rk);
+	}
+
+	this.sm4CalciRK = function(ka) {
+		var bb = 0;
+		var rk = 0;
+		var a = new Array(4);
+		var b = new Array(4);
+		this.PUT_ULONG_BE(ka, a, 0);
+		b[0] = this.sm4Sbox(a[0]);
+		b[1] = this.sm4Sbox(a[1]);
+		b[2] = this.sm4Sbox(a[2]);
+		b[3] = this.sm4Sbox(a[3]);
+		bb = this.GET_ULONG_BE(b, 0);
+		rk = bb ^ this.ROTL(bb, 13) ^ this.ROTL(bb, 23);
+		return rk;
+	}
+
+	this.sm4Sbox = function(inch) {
+		var i = inch & 0xFF;
+		var retVal = SboxTable[i];
+		return retVal > 128 ? retVal - 256 : retVal;
+	}
+
+	this.sm4_setkey_enc = function(ctx, key) {
+		if (ctx == null) {
+			alert("ctx is null!");
+			return false;
+		}
+		if (key == null || key.length != 16) {
+			alert("key error!");
+			return false;
+		}
+		ctx.mode = this.SM4_ENCRYPT;
+		this.sm4_setkey(ctx.sk, key);
+	};
+	//生成解密密钥
+	this.sm4_setkey_dec = function(ctx, key) {
+		if (ctx == null) {
+			Error("ctx is null!");
+		}
+
+		if (key == null || key.length != 16) {
+			Error("key error!");
+		}
+
+		var i = 0;
+		ctx.mode = 0;
+		this.sm4_setkey(ctx.sk, key);
+		ctx.sk = ctx.sk.reverse();
+	}
+
+
+	this.sm4_setkey = function(SK, key) {
+		var MK = new Array(4);
+		var k = new Array(36);
+		var i = 0;
+		MK[0] = this.GET_ULONG_BE(key, 0);
+		MK[1] = this.GET_ULONG_BE(key, 4);
+		MK[2] = this.GET_ULONG_BE(key, 8);
+		MK[3] = this.GET_ULONG_BE(key, 12);
+		k[0] = MK[0] ^ FK[0];
+		k[1] = MK[1] ^ FK[1];
+		k[2] = MK[2] ^ FK[2];
+		k[3] = MK[3] ^ FK[3];
+		for (var i = 0; i < 32; i++) {
+			k[(i + 4)] = (k[i] ^ this.sm4CalciRK(k[(i + 1)] ^ k[(i + 2)] ^ k[(i + 3)] ^ CK[i]));
+			SK[i] = k[(i + 4)];
+		}
+
+	}
+	this.padding = function(input, mode) {
+		if (input == null) {
+			return null;
+		}
+		var ret = null;
+		if (mode == this.SM4_ENCRYPT) {
+			var p = parseInt(16 - input.length % 16);
+			ret = input.slice(0);
+			for (var i = 0; i < p; i++) {
+				ret[input.length + i] = p;
+			}
+		} else {
+			var p = input[input.length - 1];
+			ret = input.slice(0, input.length - p);
+		}
+		return ret;
+	}
+	this.sm4_one_round = function(sk, input, output) {
+		var i = 0;
+		var ulbuf = new Array(36);
+		ulbuf[0] = this.GET_ULONG_BE(input, 0);
+		ulbuf[1] = this.GET_ULONG_BE(input, 4);
+		ulbuf[2] = this.GET_ULONG_BE(input, 8);
+		ulbuf[3] = this.GET_ULONG_BE(input, 12);
+		while (i < 32) {
+			ulbuf[(i + 4)] = this.sm4F(ulbuf[i], ulbuf[(i + 1)], ulbuf[(i + 2)], ulbuf[(i + 3)], sk[i]);
+			i++;
+		}
+		this.PUT_ULONG_BE(ulbuf[35], output, 0);
+		this.PUT_ULONG_BE(ulbuf[34], output, 4);
+		this.PUT_ULONG_BE(ulbuf[33], output, 8);
+		this.PUT_ULONG_BE(ulbuf[32], output, 12);
+
+	}
+
+	this.sm4_crypt_ecb = function(ctx, input) {
+
+		if (input == null) {
+			alert("input is null!");
+		}
+
+		if ((ctx.isPadding) && (ctx.mode == this.SM4_ENCRYPT)) {
+			input = this.padding(input, this.SM4_ENCRYPT);
+		}
+		var i = 0;
+		var length = input.length;
+		var bous = new Array();
+		for (; length > 0; length -= 16) {
+			var out = new Array(16);
+			var ins = input.slice(i * 16, (16 * (i + 1)));
+			this.sm4_one_round(ctx.sk, ins, out)
+			bous = bous.concat(out);
+			i++;
+		}
+		var output = bous;
+		if (ctx.isPadding && ctx.mode == this.SM4_DECRYPT) {
+			output = this.padding(output, this.SM4_DECRYPT);
+		}
+		for (var i = 0; i < output.length; i++) {
+			if (output[i] < 0) {
+				output[i] = output[i] + 256;
+			}
+		}
+		return output;
+	}
+
+	this.sm4_crypt_cbc = function(ctx, iv, input) {
+		if (iv == null || iv.length != 16) {
+			alert("iv error!");
+		}
+
+		if (input == null) {
+			alert("input is null!");
+		}
+
+		if (ctx.isPadding && ctx.mode == this.SM4_ENCRYPT) {
+			input = this.padding(input, this.SM4_ENCRYPT);
+		}
+
+		var i = 0;
+		var length = input.length;
+		var bous = new Array();
+		if (ctx.mode == this.SM4_ENCRYPT) {
+			var k = 0;
+			for (; length > 0; length -= 16) {
+				var out = new Array(16);
+				var out1 = new Array(16);
+				var ins = input.slice(k * 16, (16 * (k + 1)));
+
+				for (i = 0; i < 16; i++) {
+					out[i] = (ins[i] ^ iv[i]);
+				}
+				this.sm4_one_round(ctx.sk, out, out1);
+				iv = out1.slice(0, 16);
+				bous = bous.concat(out1);
+				k++;
+			}
+		} else {
+			var temp = [];
+			var k = 0;
+			for (; length > 0; length -= 16) {
+				var out = new Array(16);
+				var out1 = new Array(16);
+				var ins = input.slice(k * 16, (16 * (k + 1)));
+				temp = ins.slice(0, 16);
+				this.sm4_one_round(ctx.sk, ins, out);
+				for (i = 0; i < 16; i++) {
+					out1[i] = (out[i] ^ iv[i]);
+				}
+				iv = temp.slice(0, 16);
+				bous = bous.concat(out1);
+				k++;
+			}
+		}
+
+		var output = bous;
+		if (ctx.isPadding && ctx.mode == this.SM4_DECRYPT) {
+			output = this.padding(output, this.SM4_DECRYPT);
+		}
+
+		for (var i = 0; i < output.length; i++) {
+			if (output[i] < 0) {
+				output[i] = output[i] + 256;
+			}
+		}
+		return output;
+	}
+}
+
+function SM4Util() {
+	this.secretKey = "";
+	this.iv = "";
+	this.hexString = false;
+	//加密_ECB
+	this.encryptData_ECB = function(plainText) {
+		try {
+			var sm4 = new SM4();
+			var ctx = new SM4_Context();
+			ctx.isPadding = true;
+			ctx.mode = sm4.SM4_ENCRYPT;
+			var keyBytes = stringToByte(this.secretKey);
+			sm4.sm4_setkey_enc(ctx, keyBytes);
+			var encrypted = sm4.sm4_crypt_ecb(ctx, stringToByte(plainText));
+			var cipherText = Base64.fromUint8Array(encrypted);
+			if (cipherText != null && cipherText.trim().length > 0) {
+				cipherText.replace(/(\s*|\t|\r|\n)/g, "");
+			}
+			return cipherText;
+		} catch (e) {
+			console.error(e);
+			return null;
+		}
+
+	}
+	//解密_ECB
+	this.decryptData_ECB = function(cipherText) {
+		if (cipherText) {
+			try {
+				var sm4 = new SM4();
+				var ctx = new SM4_Context();
+				ctx.isPadding = true;
+				ctx.mode = sm4.SM4_ENCRYPT;
+				var keyBytes = stringToByte(this.secretKey);
+				sm4.sm4_setkey_dec(ctx, keyBytes);
+				if (typeof cipherText === 'string') {
+					var decrypted = sm4.sm4_crypt_ecb(ctx, Base64.toUint8Array(cipherText));
+					return byteToString(decrypted);
+				} else if (cipherText instanceof Array) {
+					let decrypted = []
+					cipherText.forEach((item) => {
+						let str = sm4.sm4_crypt_ecb(ctx, Base64.toUint8Array(item))
+						decrypted.push(byteToString(str))
+					})
+					return decrypted;
+				}
+
+			} catch (e) {
+				console.error(e);
+				return null;
+			}
+		}
+	}
+
+	this.encryptData_CBC = function(plainText) {
+		try {
+			var sm4 = new SM4();
+			var ctx = new SM4_Context();
+			ctx.isPadding = true;
+			ctx.mode = sm4.SM4_ENCRYPT;
+
+			var keyBytes = stringToByte(this.secretKey);
+			var ivBytes = stringToByte(this.iv);
+
+			sm4.sm4_setkey_enc(ctx, keyBytes);
+			var encrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, stringToByte(plainText));
+			var cipherText = Base64.fromUint8Array(encrypted);
+			if (cipherText != null && cipherText.trim().length > 0) {
+				cipherText.replace(/(\s*|\t|\r|\n)/g, "");
+			}
+			return cipherText;
+		} catch (e) {
+			console.error(e);
+			return null;
+		}
+	}
+	//解密_CBC
+	this.decryptData_CBC = function(cipherText) {
+		try {
+			var sm4 = new SM4();
+			var ctx = new SM4_Context();
+			ctx.isPadding = true;
+			ctx.mode = sm4.SM4_ENCRYPT;
+			var keyBytes = stringToByte(this.secretKey);
+			var ivBytes = stringToByte(this.iv);
+			sm4.sm4_setkey_dec(ctx, keyBytes);
+			var decrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, Base64.toUint8Array(cipherText));
+			return byteToString(decrypted);
+		} catch (e) {
+			console.error(e);
+			return null;
+		}
+	}
+
+	function stringToByte(str) {
+		var bytes = new Array();
+		var len, c;
+		len = str.length;
+		for (var i = 0; i < len; i++) {
+			c = str.charCodeAt(i);
+			if (c >= 0x010000 && c <= 0x10FFFF) {
+				bytes.push(((c >> 18) & 0x07) | 0xF0);
+				bytes.push(((c >> 12) & 0x3F) | 0x80);
+				bytes.push(((c >> 6) & 0x3F) | 0x80);
+				bytes.push((c & 0x3F) | 0x80);
+			} else if (c >= 0x000800 && c <= 0x00FFFF) {
+				bytes.push(((c >> 12) & 0x0F) | 0xE0);
+				bytes.push(((c >> 6) & 0x3F) | 0x80);
+				bytes.push((c & 0x3F) | 0x80);
+			} else if (c >= 0x000080 && c <= 0x0007FF) {
+				bytes.push(((c >> 6) & 0x1F) | 0xC0);
+				bytes.push((c & 0x3F) | 0x80);
+			} else {
+				bytes.push(c & 0xFF);
+			}
+		}
+		return bytes;
+	}
+
+	function byteToString(arr) {
+		if (typeof arr === 'string') {
+			return arr;
+		}
+		var str = '',
+			_arr = arr;
+		for (var i = 0; i < _arr.length; i++) {
+			var one = _arr[i].toString(2),
+				v = one.match(/^1+?(?=0)/);
+			if (v && one.length == 8) {
+				var bytesLength = v[0].length;
+				var store = _arr[i].toString(2).slice(7 - bytesLength);
+				for (var st = 1; st < bytesLength; st++) {
+					if (_arr[st + i]) {
+						store += _arr[st + i].toString(2).slice(2);
+					}
+				}
+				str += String.fromCharCode(parseInt(store, 2));
+				i += bytesLength - 1;
+			} else {
+				str += String.fromCharCode(_arr[i]);
+			}
+		}
+		return str;
+	}
+};
+
+function getsm4Key() {
+	var token = getToken()
+	if (token == null || token.length == 0) token = '1234567887654321'
+	return CryptoJS.MD5(token).toString().toUpperCase().substr(16, 16)
+}
+
+function getsm4Iv() {
+	var token = getToken()
+	if (token == null || token.length == 0) token = '1234567887654321'
+	return CryptoJS.MD5(CryptoJS.MD5(token).toString()).toString().toUpperCase().substr(16, 16)
+}
+
+function getsm4Keydef() {
+	var token = '1234567887654321'
+	return token
+}
+
+function getsm4Ivdef() {
+	var token = '1234567887654321'
+	return CryptoJS.MD5(CryptoJS.MD5(token).toString()).toString().toUpperCase().substr(16, 16)
+}
+
+function maskA(str, cd) {
+	let cdc = str.length - 2 * cd;
+	if (cdc > 0) {
+		var reptext = ''
+		for (let k = 0; k < cdc; k++) {
+			reptext = reptext + '✱'
+		}
+		return str.substr(0, cd) + reptext + str.substr(str.length - cd);
+	} else
+		return str;
+}
+
+function mask(str, type) {
+	return str;
+	let strcd = str != null ? str.length : 0;
+	if (strcd > 0) {
+		if (type == 1) //姓名
+		{
+			if (strcd > 1) {
+				let cdc = strcd - 1;
+				var reptext = ''
+				for (let k = 0; k < cdc; k++) {
+					reptext = reptext + '✱'
+				}
+				return reptext + str.substr(cdc)
+			} else {
+				return str
+			}
+		} else if (type == 2) //身份证号
+		{
+			if (strcd == 18) {
+				return str.substr(0, 3) + '✱✱✱✱✱' + str.substr(8, 1) + '✱✱✱✱✱✱✱' + str.substr(16, 1) + '✱';
+			} else {
+				return maskA(str, 1);
+			}
+		} else if (type == 3) //手机号
+		{
+			if (strcd == 11) {
+				return str.substr(0, 2) + '✱✱✱✱✱✱✱✱' + str.substr(10, 1);
+			} else {
+				return maskA(str, 2);
+			}
+		} else if (type == 4) //住址
+		{
+			return maskA(str, 2);
+		} else if (type == 5) //邮件地址
+		{
+			return maskA(str, 2);
+		} else if (type == 6) {
+			return maskA(str, 1);
+		} else //其他
+		{
+			return str;
+		}
+	} else {
+		return str
+	}
+}
+
+function maskArr(arr, type) {
+	return arr;
+	let res = []
+	arr.forEach((str) => {
+		let strcd = str != null ? str.length : 0;
+		if (strcd > 0) {
+			if (type == 1) //姓名
+			{
+				if (strcd > 1) {
+					let cdc = strcd - 1;
+					var reptext = ''
+					for (let k = 0; k < cdc; k++) {
+						reptext = reptext + '✱'
+					}
+					res.push(reptext + str.substr(cdc))
+				} else {
+					res.push(str)
+				}
+			} else if (type == 2) //身份证号
+			{
+				if (strcd == 18) {
+					res.push(str.substr(0, 3) + '✱✱✱✱✱' + str.substr(8, 1) + '✱✱✱✱✱✱✱' + str.substr(16, 1) +
+						'✱')
+				} else {
+					res.push(maskA(str, 1))
+				}
+			} else if (type == 3) //手机号
+			{
+				if (strcd == 11) {
+					res.push(str.substr(0, 2) + '✱✱✱✱✱✱✱✱' + str.substr(10, 1))
+				} else {
+					res.push(maskA(str, 2))
+				}
+			} else if (type == 4) //住址
+			{
+				res.push(maskA(str, 2))
+			} else if (type == 5) //邮件地址
+			{
+				res.push(maskA(str, 2))
+			} else if (type == 6) //邮件地址
+			{
+				res.push(maskA(str, 1))
+			} else {
+				res.push(str)
+			}
+		} else {
+			res.push(str)
+		}
+	})
+	return res
+}
+
+export function encrypt_ECB(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Key();
+	return s4.encryptData_ECB(data);
+}
+
+export function encrypt_ECBdef(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Keydef();
+	return s4.encryptData_ECB(data);
+}
+export function encrypt_ECBA(data, key) {
+	var s4 = new SM4Util();
+	s4.secretKey = key;
+	return s4.encryptData_ECB(data);
+}
+export function decrypt_ECB(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Key();
+	return s4.decryptData_ECB(data);
+}
+
+export function decrypt_ECBdef(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Keydef();
+	return s4.decryptData_ECB(data);
+}
+
+
+export function masks(str, type) {
+	return mask(str, type);
+}
+
+export function encrypt_CBC(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Key();
+	s4.iv = getsm4Iv();
+	return s4.encryptData_CBC(data);
+}
+export function encrypt_CBCdef(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Keydef();
+	s4.iv = getsm4Ivdef();
+	return s4.encryptData_CBC(data);
+}
+export function decrypt_CBC(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Key();
+	s4.iv = getsm4Iv();
+	return s4.decryptData_CBC(data);
+}
+export function decrypt_CBCdef(data) {
+	var s4 = new SM4Util();
+	s4.secretKey = getsm4Keydef();
+	s4.iv = getsm4Ivdef();
+	return s4.decryptData_CBC(data);
+}
+
+
+export function decryptData_ECB(rowdata, fields, hides) {
+	if (rowdata) {
+		var s4 = new SM4Util();
+		s4.secretKey = getsm4Key();
+		for (let j = 0; j < fields.length; j++) {
+			if (rowdata[fields[j]] != null) {
+				let zfc = s4.decryptData_ECB(rowdata[fields[j]]);
+				zfc instanceof Array ? rowdata[fields[j]] = maskArr(zfc, hides[j]) : rowdata[fields[j]] = mask(zfc,
+					hides[j])
+			}
+		}
+	}
+	return rowdata
+}
+
+export function decryptRowData_ECB(rowdata, fields, hides) {
+	if (rowdata && rowdata.length > 0) {
+		var s4 = new SM4Util();
+		s4.secretKey = getsm4Key();
+		for (let i = 0; i < rowdata.length; i++) {
+			for (let j = 0; j < fields.length; j++) {
+				if (rowdata[i][fields[j]] != null) {
+					let srcs = rowdata[i][fields[j]]
+					let zfc = s4.decryptData_ECB(srcs);
+					rowdata[i][fields[j]] = mask(zfc, hides[j]);
+				}
+			}
+		}
+	}
+	return rowdata
+}

+ 43 - 0
components/lzc-OCR/common/base64.js

@@ -0,0 +1,43 @@
+/**
+ * @description 本地图片转base64方法(兼容APP、H5、小程序)
+ * @param {number} path 图片本地路径
+ * @returns Promise对象
+ */
+const toBase64 = (path) => {
+	return new Promise((resolve, reject) => {
+		// #ifdef APP-PLUS
+		plus.io.resolveLocalFileSystemURL(path, (entry) => {
+			entry.file((file) => {
+				let fileReader = new plus.io.FileReader()
+				fileReader.readAsDataURL(file)
+				fileReader.onloadend = (evt) => {
+					let base64 = evt.target.result.split(",")[1]
+					resolve(base64)
+				}
+			})
+		})
+		// #endif
+		// #ifdef H5
+		uni.request({
+			url: path,
+			responseType: 'arraybuffer',
+			success: (res) => {
+				resolve(uni.arrayBufferToBase64(res.data))
+			}
+		})
+		// #endif
+		// #ifdef MP-WEIXIN
+		uni.getFileSystemManager().readFile({
+			filePath: path,
+			encoding: 'base64',
+			success: (res) => {
+				resolve(res.data)
+			}
+		})
+		// #endif
+	})
+}
+
+export {
+	toBase64
+}

+ 110 - 0
components/lzc-OCR/lzc-OCR.vue

@@ -0,0 +1,110 @@
+<template>
+	<view class="box" @click="chooseImage">
+		<image class="sfsb" :src="bSrc"></image>
+	</view>
+</template>
+
+<script>
+	let convert = require('./common/base64.js')
+	export default {
+		data() {
+			return {
+				preSrc: '',
+				bSrc: this.src
+			};
+		},
+		props: {
+			src: {
+				type: String,
+				default: '/static/images/sfsb.png'
+			},
+			disabled: {
+				type: Array,
+				default: () => []
+			}
+		},
+		mounted() {},
+		methods: {
+			// 选择本地图片
+			chooseImage() {
+				if (this.disabled.length > 0) return;
+				uni.chooseImage({
+					count: 1,
+					mediaType: ['image'],
+					sizeType: ['original', 'compressed'],
+					sourceType: ['camera'],
+					camera: 'back',
+					success: (res) => {
+						uni.showLoading({
+							title: '正在识别中...'
+						})
+						this.$emit('start', {
+							animal: true,
+							img: res.tempFilePaths[0]
+						})
+						this.preSrc = res.tempFilePaths[0]
+						// 下面将图片本地路径转base64
+						convert.toBase64(res.tempFilePaths[0]).then((res) => {
+							this.getAccessToken(res)
+						})
+					}
+				})
+			},
+			// 获取AccessToken
+			getAccessToken(path) {
+				uni.request({
+					url: '/baiduApi/oauth/2.0/token',
+					data: {
+						grant_type: 'client_credentials',
+						client_id: 'NgkKVL2KIyGTOqAs4FBKl8tW',
+						client_secret: 'v4C8G72bREjtdWGScQAWLKlHhpoNb3u6'
+					},
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/x-www-form-urlencoded'
+					},
+					success: (res) => {
+						this.bSrc = this.preSrc
+						this.uploadImage(path, res.data.access_token)
+					}
+				})
+			},
+			// 身份证识别
+			uploadImage(path, token) {
+				uni.request({
+					url: '/baiduApi/rest/2.0/ocr/v1/idcard',
+					data: {
+						image: path,
+						access_token: token,
+						id_card_side: 'front',
+						detect_photo: true,
+						detect_risk: true,
+						detect_card: true,
+					},
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/x-www-form-urlencoded'
+					},
+					success: (res) => {
+						uni.hideLoading()
+						console.log(res)
+						this.$emit('end', res.data, this.bSrc)
+					}
+				})
+			},
+		}
+	}
+</script>
+
+<style>
+	.box {
+		width: 100%;
+		height: 100%;
+	}
+
+	.sfsb {
+		width: 100%;
+		height: 150px;
+		display: block;
+	}
+</style>

+ 17 - 0
config.js

@@ -0,0 +1,17 @@
+const build = {
+	"service": "http://121.36.73.159:807/prod-api",
+	// "service" : "http://10.16.9.114:8080",
+	// "service" : "http://10.16.4.5/dev-api",
+	"baseUrl": "http://121.36.73.159:807",
+	"head": "/uploadPath/2024/01/16/profile.png",
+	"appid": "wxeb15ff6b40432045",
+	// 百度云OCR
+	"ocr_client_id": "",
+	"ocr_client_secret": "",
+	// 百度云人脸识别
+	"face_client_id": "G2wsdrRdvQDtfgxwrm9kHKQK",
+	"face_client_secret": "B2LSgNkdTE7APUSZ9Z7VHYb41D8y6D3W",
+}
+
+
+export default build

+ 20 - 0
index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 29 - 0
main.js

@@ -0,0 +1,29 @@
+import App from './App'
+
+import { toast, transDictLabel, changeDictAttrName, getDictList } from './common/common.js'
+
+Vue.prototype.toast = toast
+Vue.prototype.transDictLabel = transDictLabel
+Vue.prototype.changeDictAttrName = changeDictAttrName
+Vue.prototype.getDictList = getDictList
+
+// #ifndef VUE3
+import Vue from 'vue'
+import './uni.promisify.adaptor'
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+  ...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  return {
+    app
+  }
+}
+// #endif

+ 101 - 0
manifest.json

@@ -0,0 +1,101 @@
+{
+    "name" : "yl-gzh",
+    "appid" : "__UNI__80B8CA1",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2",
+    "h5" : {
+        "devServer" : {
+			"https": false,
+            // "port" : 8081,
+            "disableHostCheck" : true,
+            "proxy" : {
+                "/baiduApi" : {
+                    "target" : "https://aip.baidubce.com", // 需要跨域的域名
+                    "changeOrigin" : true,
+                    "secure" : false,
+                    "pathRewrite" : {
+                        "^/baiduApi" : ""
+                    }
+                },
+				"/api" : {
+				    "target" : "http://121.36.73.159:807/prod-api", // 需要跨域的域名
+				    "changeOrigin" : true,
+				    "secure" : false,
+				    "pathRewrite" : {
+				        "^/api" : ""
+				    }
+				}
+            },
+            "allowedHosts" : [ "www.ccsckj.com" ]
+        },
+        "router" : {
+            "base" : "./"
+        }
+    }
+}

+ 6 - 0
package.json

@@ -0,0 +1,6 @@
+{
+	"dependencies": {
+		"@dcloudio/uni-ui": "^1.5.0",
+		"crypto-js": "^4.2.0"
+	}
+}

+ 45 - 0
pages.json

@@ -0,0 +1,45 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "首页"
+			}
+		},
+		{
+			"path": "pages/login/index",
+			"style": {
+				"navigationBarTitleText": "登录"
+			}
+		},
+		{
+			"path": "pages/lr/base",
+			"style": {
+				"navigationBarTitleText": "基本信息"
+			}
+		},
+		{
+			"path": "pages/lr/eat",
+			"style": {
+				"navigationBarTitleText": "食堂点餐"
+			}
+		},
+		{
+			"path": "pages/lr/test",
+			"style": {
+				"navigationBarTitleText": "测试"
+			}
+		}
+	],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8",
+		"h5": {
+			"titleNView": false
+		}
+	},
+	"uniIdRouter": {}
+}

+ 172 - 0
pages/index/index.vue

@@ -0,0 +1,172 @@
+<template>
+	<view class="container">
+		<view>
+			<text class="phoneText global-font">Hello,张三</text>
+		</view>
+
+		<view class="bannerBox">
+			<swiper class="swiper-box" :interval="interval" autoplay indicator-dots indicator-color="#C7C7C7"
+				indicator-active-color="#1aad19">
+				<swiper-item v-for="(item, index) in bannerList" :key="index">
+					<image src="../../static/images/banner2.png" class="imgItem" @click="bannerItemClick(item)"></image>
+				</swiper-item>
+			</swiper>
+		</view>
+
+		<view class="btnBox">
+			<image class="btnImg" src="../../static/images/lr.png" @click="btnClick(0)" />
+			<image class="btnImg img2" src="../../static/images/shqt.png" @click="btnClick(1)" />
+		</view>
+
+	</view>
+</template>
+
+<script>
+	import {
+		getToken
+	} from '@/common/auth.js'
+	import {
+		getUser
+	} from '@/common/auth.js'
+	import {
+		decryptRowData_ECB,
+		decryptData_ECB
+	} from '@/common/sm4.js'
+	import {
+		updateKhjbxx,
+		infoKhjbxx
+	} from '@/api/kh.js'
+	export default {
+		data() {
+			return {
+				interval: 3000,
+				bannerList: [{
+					image: '../../static/image/banner2.png'
+				}],
+				lrInfo: {},
+				// 人员信息
+				userInfo: {},
+			}
+		},
+		mounted() {
+			// 老人07、员工04、监管06
+			if (!getToken()) {
+				uni.navigateTo({
+					url: '/pages/login/index'
+				})
+			}
+			this.userInfo = getUser()
+			if (this.userInfo.userType == '07') {
+				this.info(this.userInfo.userId)
+			}
+		},
+		methods: {
+			info(id) {
+				infoKhjbxx(id).then(res => {
+					if (res.code == 200) {
+						this.lrInfo = decryptData_ECB(res.data, ["lrZjhm", "lrXm", "lrHjbcxx", "lrXjdzBcxx",
+							"lrCydh", "lrPoxm", "lrPoZjmh", "jhrXm", "jhrSjhm", "zlrXm", "zlrDh", "cjzh",
+							"yhzh", "khmc"
+						], [2, 1, 4, 4, 3, 1, 2, 1, 2, 1, 3, 5, 5, 5]);
+					}
+				})
+			},
+			btnClick(index) {
+				let baseUrl = this.userInfo.userType == '07' ? '/pages/lr/base?info=' + JSON.stringify(this.lrInfo) : '/pages/lr/base'
+				let url = index == 0 ? baseUrl : '/pages/lr/eat'
+				// let url = index == 0 ? '/pages/lr/base' : '/pages/lr/test'
+				uni.navigateTo({
+					url: url
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+	/* 轮播图 */
+	.bannerBox {
+		position: absolute;
+		margin-top: 14vh;
+		width: 100vw;
+	}
+
+	.swiper-box {
+		width: 90%;
+		height: 300rpx;
+		margin: 50rpx 5vw;
+		margin-top: -15%;
+		margin-bottom: 20rpx;
+	}
+
+	.swiper-adsense-box {
+		width: 90%;
+		height: 150rpx;
+		margin: 30rpx auto;
+	}
+
+	.imgItem {
+		width: 100%;
+		height: 100%;
+	}
+
+	.swiper-box swiper-item {
+		height: 280rpx !important;
+		border-radius: 15px;
+		overflow: hidden;
+	}
+
+	.wx-swiper-dots .wx-swiper-dot {
+		width: 45rpx;
+		height: 8rpx;
+		border-radius: 5rpx;
+	}
+
+	.wx-swiper-dots .wx-swiper-dot:nth-of-type(n+2) {
+		margin-left: 30rpx;
+	}
+
+	.wx-swiper-dots.wx-swiper-dots-horizontal {
+		position: absolute;
+		bottom: 0rpx;
+	}
+
+	.phoneText {
+		position: absolute;
+		margin-top: 2vh;
+		margin-left: 5vw;
+		width: 80vw;
+		height: 2vh;
+		font-size: 14px;
+		color: white;
+		font-weight: 700;
+		line-height: 2vh;
+		text-align: left;
+		vertical-align: top;
+	}
+
+	.container {
+		height: 100vh;
+		width: 100vw;
+		background: linear-gradient(to bottom, #07bf61 0%, #fff 30%, #F8FCFF 100%);
+	}
+
+	/* 按钮 */
+	.btnBox {
+		position: absolute;
+		width: 90%;
+		display: flex;
+		margin: 30vh 5vw;
+		margin-bottom: 10rem;
+	}
+
+	.btnImg {
+		width: 45vw;
+		height: 33vw;
+		display: block;
+	}
+
+	.img2 {
+		margin-left: 5vw;
+	}
+</style>

+ 179 - 0
pages/login/index.vue

@@ -0,0 +1,179 @@
+<template>
+	<view style="width: 100%;height: 100%;">
+		<image src="../../static/images/card.jpg" style="width: 100%;height: 100%;" mode="aspectFill">
+			<uni-forms ref="baseForm" :modelValue="formData" :rules="rules">
+				<view class="cardBody">
+					<view class="card ">
+						<view class="phoneText global-font">
+							账号
+						</view>
+						<view>
+							<uni-forms-item name="username">
+								<input class="inputClass" v-model="formData.username" />
+							</uni-forms-item>
+						</view>
+
+						<view class="phoneText global-font">
+							密码
+						</view>
+						<view>
+							<uni-forms-item name="password">
+								<input class="inputClass" v-model="formData.password" type="password" />
+							</uni-forms-item>
+						</view>
+						<view>
+							<button cursor-spacing="22px" class="buttonClass" @click="to">登录</button>
+						</view>
+					</view>
+				</view>
+			</uni-forms>
+		</image>
+
+	</view>
+
+
+</template>
+
+<script>
+	import config from '@/config.js'
+	import {
+		setToken,
+		setOpenid,
+		setUser
+	} from '@/common/auth.js'
+	import {
+		Login
+	} from '@/api/login.js'
+	export default {
+		data() {
+			return {
+				formData: {
+					username: '220723194104200237',
+					password: 'sckj@2022',
+					code: "061lgSkl26q2Pc4xvwol2pUvGJ3lgSk5"
+				},
+				rules: {
+					username: {
+						rules: [{
+							required: true,
+							errorMessage: '账号不能为空'
+						}]
+					},
+					password: {
+						rules: [{
+							required: true,
+							errorMessage: '密码不能为空'
+						}]
+					},
+				},
+			}
+		},
+		mounted() {
+			// this.getCode(config.appId)
+		},
+		methods: {
+			getCode(appid) { // 非静默授权,第一次有弹框
+				let local = window.location.href; // 获取页面url
+				console.log("local:", local)
+				let code = this.getUrlCode().code; // 截取code
+				if (code && code !== '') { // 如果有code
+					// 拿到code去请求openId并存在本地 ?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
+					this.formData.code = code
+				} else {
+					// window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${encodeURIComponent(local)}&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect`
+					window.location.href =
+						`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${encodeURIComponent(local)}&response_type=code&scope=snsapi_base&state=123#wechat_redirect`
+				}
+			},
+			getUrlCode() { // 截取url中的code方法
+				let url = location.search;
+				console.log("utl:", url)
+				let theRequest = {};
+				if (url.indexOf("?") !== -1) {
+					console.log("utl1:", url)
+					let str = url.substr(1);
+					let strs = str.split("&");
+					for (let i = 0; i < strs.length; i++) {
+						theRequest[strs[i].split("=")[0]] = (strs[i].split("=")[1])
+					}
+				}
+				console.log("theRequest:", theRequest)
+				return theRequest
+			},
+			to() {
+				this.$refs['baseForm'].validate().then(res => {
+					Login(this.formData).then(e => {
+						console.log(e)
+						if (e.code == 200) {
+							setOpenid(e.data.openId)
+							setUser(e.data.sysUser)
+							setToken(e.data.token.access_token)
+							uni.navigateTo({
+								url: '/pages/index/index'
+							})
+						}
+					})
+
+				}).catch(err => {
+					console.log(err);
+				})
+
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.cardBody {
+		display: flex;
+		align-items: center;
+		flex-direction: column;
+
+		position: absolute;
+		top: 20vh;
+		left: 10vw;
+	}
+
+	.card {
+		margin-top: 20vh;
+		width: 76vw;
+		height: 32vh;
+		border-radius: 16px;
+		background: rgba(255, 255, 255, 1);
+		box-shadow: 5px 6px 18px rgba(0, 0, 0, 0.08);
+
+	}
+
+	.phoneText {
+		margin-top: 2vh;
+		margin-left: 4vw;
+		width: 14vw;
+		height: 2vh;
+		font-size: 14px;
+		line-height: 2vh;
+		color: rgba(116, 127, 158, 1);
+		text-align: left;
+		vertical-align: top;
+
+	}
+
+	.inputClass {
+		margin-top: 1vh;
+		margin-left: 4vw;
+		width: 68vw;
+		height: 4vh;
+		border-radius: 16px;
+		border: 1px solid rgba(176, 179, 199, 1);
+		text-indent: 4vw;
+	}
+
+	.buttonClass {
+		margin-top: 4vh;
+		margin-left: 4vw;
+		width: 68vw;
+		height: 5vh;
+		border-radius: 16px;
+		border: 1px solid rgba(176, 179, 199, 1);
+		background: #7948EA;
+	}
+</style>

+ 463 - 0
pages/lr/base.vue

@@ -0,0 +1,463 @@
+<template>
+	<view>
+		<uni-card>
+			<uni-section title="基本信息" type="line">
+				<lzcOCR class="lzcOCR" :src="zjcaijianSrc" @end="idcardEnd"></lzcOCR>
+
+				<uni-forms ref="baseForm" :modelValue="baseFormData" :rules="rules"
+					style="margin-top: 3vh;margin-left: 3vw;">
+					<uni-forms-item label="姓名" required name="lrXm">
+						<uni-easyinput ref="xm" type="text" v-model="baseFormData.lrXm" placeholder="请输入姓名"
+							:inputBorder="false"></uni-easyinput>
+					</uni-forms-item>
+					<uni-forms-item label="手机号" required name="lrCydh">
+						<uni-easyinput type="text" v-model="baseFormData.lrCydh" placeholder="请输入手机号"
+							:inputBorder="false"></uni-easyinput>
+					</uni-forms-item>
+					<uni-forms-item label="住址" required name="lrHjbcxx">
+						<uni-easyinput type="text" v-model="baseFormData.lrHjbcxx" placeholder="请输入住址"
+							:inputBorder="false"></uni-easyinput>
+					</uni-forms-item>
+					<uni-forms-item label="人像" required>
+						<image :src="src" @click="takePhoto" style="width: 25vw;height: 10vh;"></image>
+					</uni-forms-item>
+					<view v-if="show">
+						<uni-forms-item label="直接上传">
+							<uni-data-checkbox v-model="radio" :localdata="radioData"></uni-data-checkbox>
+						</uni-forms-item>
+						<text>注意:由于人脸比对不成功可以直接上传人工审核!</text>
+					</view>
+
+					<view>
+						<button cursor-spacing="22px" class="buttonClass" @click="save()">保存</button>
+					</view>
+				</uni-forms>
+			</uni-section>
+		</uni-card>
+
+	</view>
+</template>
+
+<script>
+	import lzcOCR from '@/components/lzc-OCR/lzc-OCR.vue';
+	import {
+		decryptRowData_ECB,
+		decryptData_ECB
+	} from '@/common/sm4.js'
+	import {
+		showConfirm,
+		getDictInfo,
+		toast,
+		toBase64
+	} from '@/common/common.js'
+	import {
+		UploadOne
+	} from '@/api/upload.js'
+	import {
+		updateKhjbxx,
+		infoKhjbxx
+	} from '@/api/kh.js'
+	import {
+		getUser
+	} from '@/common/auth.js'
+	import CryptoJS from 'crypto-js';
+	import config from '@/config.js';
+	import idCardNoUtil from '@/common/idcard.js'
+	export default {
+		components: {
+			lzcOCR
+		},
+		data() {
+			return {
+				show: false,
+				radio: 0,
+				radioData: [{
+					text: '否',
+					value: 0
+				}, {
+					text: '是',
+					value: 1
+				}],
+				// 字典
+				dicts: {
+					lr_info_check: [],
+					// 民族
+					C0009: [],
+					// 性别
+					C0007: [],
+				},
+				// 百度云access_token
+				token: '',
+				client_id: config.face_client_id,
+				client_secret: config.face_client_secret,
+				// 人像地址
+				src: "/static/images/head.png",
+				// 身份拍摄地址
+				zjSrc: "",
+				// 身份证裁剪地址
+				zjcaijianSrc: "",
+				showSrc: "",
+				// 人员信息
+				userInfo: {},
+				// 组
+				groupId: '',
+				// 身份证号加密
+				idcardMD5: '',
+				// 人像base64
+				face: '',
+				// 身份证base64
+				idcardFace: '',
+				// 对比份数
+				score: 0,
+				baseFormData: {
+					lrXm: '',
+					lrZjhm: '',
+					lrHjbcxx: '',
+					lrCydh: '',
+				},
+				rules: {
+					lrHjbcxx: {
+						rules: [{
+							required: true,
+							errorMessage: '住址不能为空'
+						}]
+					},
+					lrXm: {
+						rules: [{
+							required: true,
+							errorMessage: '姓名不能为空'
+						}]
+					},
+					photo: {
+						rules: [{
+							required: true,
+							errorMessage: '请上传人像'
+						}]
+					},
+					lrCydh: {
+						rules: [{
+								required: true,
+								errorMessage: '手机号不能为空'
+							},
+							{
+								format: 'number',
+								errorMessage: '请输入正确的手机号',
+							},
+							{
+								pattern: '^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1})|(17[0-9]{1}))+\\d{8})$',
+								errorMessage: '请输入正确的手机号',
+							}
+						]
+					}
+				},
+			}
+		},
+		created() {},
+		onLoad(o) {
+			this.getDictList(Object.keys(this.dicts), this.dicts)
+			this.userInfo = getUser()
+			this.groupId = this.userInfo.dept.locationCode.substring(0, 6)
+			let info = JSON.parse(o.info)
+			this.baseFormData = info
+			this.zjcaijianSrc = config.baseUrl + info.lrZjz
+			this.src = config.baseUrl + info.lrTx
+		},
+		methods: {
+			getAccessToken() {
+				uni.request({
+					url: '/baiduApi/oauth/2.0/token',
+					data: {
+						grant_type: 'client_credentials',
+						client_id: this.client_id,
+						client_secret: this.client_secret
+					},
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/x-www-form-urlencoded'
+					},
+					success: (res) => {
+						if (res.statusCode == 200) {
+							this.token = res.data.access_token
+							this.match()
+						}
+					},
+					error: (err) => {
+						uni.hideLoading()
+					}
+				})
+			},
+
+			match() {
+				let face = this.face
+				let idcardFace = this.idcardFace
+				let data = [{
+					image: face,
+					image_type: 'BASE64',
+					liveness_control: 'NORMAL',
+				}, {
+					image: idcardFace,
+					image_type: 'BASE64'
+				}]
+				console.log(data)
+				uni.request({
+					url: '/baiduApi/rest/2.0/face/v3/match?access_token=' + this.token,
+					data: data,
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/json'
+					},
+					success: (res) => {
+						console.log('对比', res);
+						if (res.data.error_msg == 'SUCCESS') {
+							this.score = res.data.result.score;
+							if (this.score >= 80) {
+								this.baseFormData.lzzt = 1
+								this.faceSearch()
+							} else {
+								// 低于80选项是否人工审核,是的话进记录表
+								// 身份证头像保存后端
+								// 人脸库注册人脸、身份证md5
+								// 修改的时候去人脸库搜索,搜索不到不允许修改
+								// 修改成功替换原始人脸库照片
+								// 首次修改搜索身份证
+								// 后端没入库,人脸库相应删除
+								// ocr失败身份证原版入库,ocr成功人脸入库
+								// showConfirm('人像与身份证不符,请重新上传')
+								this.baseFormData.lzzt = 0
+								this.show = true
+								uni.hideLoading()
+							}
+
+						} else {
+							showConfirm('请正确上传人像')
+						}
+
+					},
+					error: (err) => {
+						uni.hideLoading()
+					}
+				})
+			},
+			createGroup() {
+				uni.request({
+					url: '/baiduApi/rest/2.0/face/v3/faceset/group/add?access_token=' + this.token,
+					data: {
+						group_id: this.groupId,
+					},
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/x-www-form-urlencoded'
+					},
+					success: (res) => {
+						if (res.statusCode == 200) {
+							this.faceAdd()
+						}
+					},
+					error: (err) => {
+						uni.hideLoading()
+					}
+				})
+			},
+			// 人脸注册
+			faceAdd() {
+				// https://cloud.baidu.com/doc/FACE/s/Gk37c1uzc#%E4%BA%BA%E8%84%B8%E6%B3%A8%E5%86%8C
+				let face = this.face
+				let data = {
+					image: face,
+					image_type: 'BASE64',
+					group_id: this.groupId,
+					user_id: this.idcardMD5,
+					action_type: 'REPLACE', // 操作方式 APPEND: 当user_id在库中已经存在时,对此user_id重复注册时,新注册的图片默认会追加到该user_id下 REPLACE : 当对此user_id重复注册时,则会用新图替换库中该user_id下所有图片 默认使用APPEND
+				}
+				console.log("人脸注册:", data)
+				uni.request({
+					url: '/baiduApi/rest/2.0/face/v3/faceset/user/add?access_token=' + this.token,
+					data: data,
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/json'
+					},
+					success: (res) => {
+						console.log("人脸注册:", res);
+						uni.hideLoading()
+						if (res.data.error_msg == 'SUCCESS') {
+
+						}
+					},
+					error: (err) => {
+						uni.hideLoading()
+					}
+				})
+			},
+			// 人脸搜索
+			faceSearch() {
+				let face = this.face
+				let data = {
+					image: face,
+					image_type: 'BASE64',
+					group_id_list: this.groupId
+				}
+				console.log("人脸搜索:", data)
+				uni.request({
+					url: '/baiduApi/rest/2.0/face/v3/search?access_token=' + this.token,
+					data: data,
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/json'
+					},
+					success: (res) => {
+						if (res.data.error_msg == 'SUCCESS') {
+							if (res.data.result.user_list.length < 1) {
+								console.log("人脸不存在");
+								this.createGroup()
+							} else {
+								this.faceAdd()
+							}
+						}
+					},
+					error: (err) => {
+						uni.hideLoading()
+					}
+				})
+			},
+			save() {
+				if (this.src == '/static/images/head.png') {
+					showConfirm('请上传人像')
+				} else {
+					if ((this.score >= 80) || (this.score < 80 && this.radio == '1')) {
+						uni.showLoading({
+							title: '正在保存中...'
+						})
+						this.$refs['baseForm'].validate().then(res => {
+							UploadOne(this.zjcaijianSrc, {}).then(re => {
+								console.log("re", re)
+								this.baseFormData.lrZjz = re.data.url
+								UploadOne(this.src, {}).then(re => {
+									console.log("re", re)
+									this.baseFormData.lrTx = re.data.url
+									this.baseFormData.id = this.userInfo.userId
+									updateKhjbxx(this.baseFormData).then(r => {
+										console.log("r:", r)
+										uni.hideLoading()
+										if (r.code == 200) {
+											toast('保存成功')
+											setTimeout(function() {
+												uni.reLaunch({
+													url: '/pages/index/index'
+												})
+											}, 1000)
+										}
+									})
+								})
+							})
+						}).catch(err => {
+							uni.hideLoading()
+							console.log(err);
+						})
+					} else {
+						showConfirm('请重新上传人像')
+					}
+				}
+
+			},
+			takePhoto() {
+				if (!this.zjSrc) {
+					showConfirm('请先上传身份证人像面')
+				}
+				uni.chooseImage({
+					count: 1,
+					mediaType: ['image'],
+					sizeType: ['original', 'compressed'],
+					sourceType: ['camera'],
+					camera: 'front',
+					success: (res) => {
+						uni.showLoading({
+							title: '正在上传中...'
+						})
+						this.src = res.tempFilePaths[0]
+						toBase64(res.tempFilePaths[0]).then((res) => {
+							this.face = res
+						})
+						this.getAccessToken()
+					}
+				})
+			},
+			// 身份证识别
+			idcardEnd(words, src) {
+				// 身份证号校验  性别  出生日期
+				// 修改之后的姓名和ocr返回校验,重新ocr
+				let id = words.words_result['公民身份号码'].words
+				let csrq = words.words_result['出生'].words
+				let sex = words.words_result['性别'].words
+				let info = idCardNoUtil.getIdCardInfo(id)
+				if (!idCardNoUtil.checkIdCardNo(id)) {
+					showConfirm('身份证号识别有误,请重新上传')
+					return
+				}
+				if (info.birthday != csrq) {
+					showConfirm('身份证出生日期识别有误,请重新上传')
+					return
+				}
+				if (info.gender != sex) {
+					showConfirm('身份证性别识别有误,请重新上传')
+					return
+				}
+				if (words.image_status == "reversed_side") {
+					showConfirm('请上传身份证人像面')
+					return
+				}
+				if (words.risk_type != "normal") {
+					// normal-正常身份证;copy-复印件;temporary-临时身份证;screen-翻拍;unknown-其他未知情况
+					showConfirm('请上传正确的身份证人像面')
+					return
+				}
+				if (words.image_status == "other_type_card") {
+					console.log('请上传正确的身份证人像面')
+					return
+				}
+				this.baseFormData.lrXb = getDictInfo(this.dicts.C0007, words.words_result['性别'].words)[0].value
+				this.baseFormData.lrMz = getDictInfo(this.dicts.C0009, words.words_result['民族'].words)[0].value
+				// // 民族
+				// this.C0009
+				// // 性别
+				// this.C0007
+				// base64转本地url
+				// Base64字符串
+				const base64String = words.card_image;
+				const arrayBuffer = uni.base64ToArrayBuffer(base64String);
+				// 创建Blob对象
+				const blob = new Blob([arrayBuffer], {
+					type: 'image/jpg'
+				});
+				// 创建URL对象并指向blob
+				const url = URL.createObjectURL(blob);
+				// 然后可以将url用作图片的src,或者上传到服务器或保存到本地
+				this.idcardFace = words.photo
+				this.baseFormData.lrZjhm = id
+				this.baseFormData.lrCsrq = csrq
+				this.idcardMD5 = CryptoJS.MD5(this.baseFormData.lrZjhm).toString()
+				this.baseFormData.lrXm = words.words_result['姓名'].words
+				this.zjSrc = src
+				this.zjcaijianSrc = url
+			},
+		}
+	}
+</script>
+
+<style>
+	.lzcOCR {
+		width: 90%;
+		margin: 0 auto;
+		display: block;
+	}
+
+	.buttonClass {
+		margin-top: 4vh;
+		margin-left: 4vw;
+		width: 68vw;
+		height: 5vh;
+		border-radius: 16px;
+		border: 1px solid rgba(176, 179, 199, 1);
+		background: #7948EA;
+	}
+</style>

+ 20 - 0
pages/lr/eat.vue

@@ -0,0 +1,20 @@
+<template>
+	<view style="width: 100%;height: 100%;">
+		<web-view src="../../static/index.html"></web-view>
+	</view>
+</template>
+</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {}
+		},
+		mounted() {},
+		onReady() {},
+		methods: {}
+	}
+</script>
+
+<style>
+</style>

+ 8 - 0
pages/lr/test.vue

@@ -0,0 +1,8 @@
+<template>
+</template>
+
+<script>
+</script>
+
+<style>
+</style>

+ 253 - 0
static/face/face.html

@@ -0,0 +1,253 @@
+
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+<style>
+    #container {
+        position: relative;
+    }
+
+    #canvas {
+        position: absolute;
+        left: 0;
+        top: 0;
+    }
+</style>
+<script src="js/utils.js"></script>
+<script src="js/clmtrackr.js"></script>
+<script src="js/model_pca_20_svm.js"></script>
+<script src="js/numeric.js"></script>
+<script src="js/ccv.js"></script>
+<script src="js/jquery-1.8.3.js" type="text/javascript" charset="utf-8"></script>
+<div id="container" style="text-align: center;">
+    <video id="video" width="500" height="400" style="transform: rotateY(180deg)" autoplay>
+        您的浏览器不支持video标签
+    </video>
+    <canvas id="canvas" width="500" height="400"></canvas>
+</div>
+
+<button id="mouse">张嘴验证</button>
+<button id="head">摇头验证</button>
+<button id="eye">眨眼验证</button>
+<button id="capturen" onclick="cap()" style="color: blue;">拍照</button>
+<button id="downlaod" onclick="down()" style="color: blue;">下载</button>
+<button id="rxzc" onclick="rxzc()" style="color: blue;">注册</button>
+<button id="rxbd" onclick="rxbd()" style="color: blue;">比对</button>
+
+<div id="tip" style="display: '';">
+</div>
+<input type="hidden" id="result" />
+<div id="msg" style="display: '';">
+</div>
+
+<div id="positions">
+</div>
+<img id="img" alt="" src="">
+<script src="js/alive_face.js" type="text/javascript" charset="utf-8"></script>
+<script type="text/javascript">
+    var video = document.getElementById("video");
+    function flip_index (kPel, width, height) {
+        var i     = Math.floor (kPel / width) ;
+        var j     = kPel % width ;
+        var jFlip = width - j - 1 ;
+        var kFlip = i * width + jFlip ;
+        return kFlip ;
+    }
+    function cap()
+    {
+        var scale=1;
+        var canvas = document.createElement("canvas");
+        let context = canvas.getContext("2d");
+        canvas.width = video.videoWidth * scale;
+        canvas.height = video.videoHeight * scale;
+        context.drawImage(video, 0, 0, canvas.width, canvas.height);
+        var imageData = context.getImageData (0, 0, canvas.width, canvas.height) ;
+        var imageFlip = new ImageData (canvas.width, canvas.height) ;
+        var Npel      = imageData.data.length / 4 ;
+        for ( var kPel = 0 ; kPel < Npel ; kPel++ ) {
+            var kFlip      = flip_index (kPel, canvas.width, canvas.height) ;
+            var offset     = 4 * kPel ;
+            var offsetFlip = 4 * kFlip ;
+            imageFlip.data[offsetFlip + 0] = imageData.data[offset + 0] ;
+            imageFlip.data[offsetFlip + 1] = imageData.data[offset + 1] ;
+            imageFlip.data[offsetFlip + 2] = imageData.data[offset + 2] ;
+            imageFlip.data[offsetFlip + 3] = imageData.data[offset + 3] ;
+        }
+
+        var canvasFlip = document.createElement('canvas') ;
+        canvasFlip.setAttribute('width', canvas.width) ;
+        canvasFlip.setAttribute('height',canvas.height) ;
+        canvasFlip.getContext('2d').putImageData(imageFlip, 0, 0) ;
+        img.src =canvasFlip.toDataURL('image/jpeg');
+
+
+
+
+
+
+        //var link = document.createElement('a');
+        //link.setAttribute('download', 'img_.jpg');
+        //	link.setAttribute('href', canvasFlip.toDataURL("image/jpeg").replace("image/jpeg", "image/octet-stream"));
+        //link.click();
+
+        var data = canvasFlip.toDataURL('image/jpeg',0.5);
+        var img_base64 = data.substring(23);
+      //  alert(data);
+       /// alert(img_base64);
+    }
+
+    function down()
+    {
+        var scale=1;
+        var canvas = document.createElement("canvas");
+        let context = canvas.getContext("2d");
+        canvas.width = video.videoWidth * scale;
+        canvas.height = video.videoHeight * scale;
+        context.drawImage(video, 0, 0, canvas.width, canvas.height);
+        var imageData = context.getImageData (0, 0, canvas.width, canvas.height) ;
+        var imageFlip = new ImageData (canvas.width, canvas.height) ;
+        var Npel      = imageData.data.length / 4 ;
+        for ( var kPel = 0 ; kPel < Npel ; kPel++ ) {
+            var kFlip      = flip_index (kPel, canvas.width, canvas.height) ;
+            var offset     = 4 * kPel ;
+            var offsetFlip = 4 * kFlip ;
+            imageFlip.data[offsetFlip + 0] = imageData.data[offset + 0] ;
+            imageFlip.data[offsetFlip + 1] = imageData.data[offset + 1] ;
+            imageFlip.data[offsetFlip + 2] = imageData.data[offset + 2] ;
+            imageFlip.data[offsetFlip + 3] = imageData.data[offset + 3] ;
+        }
+        var canvasFlip = document.createElement('canvas') ;
+        canvasFlip.setAttribute('width', canvas.width) ;
+        canvasFlip.setAttribute('height',canvas.height) ;
+        canvasFlip.getContext('2d').putImageData(imageFlip, 0, 0) ;
+        img.src =canvasFlip.toDataURL('image/jpeg');
+
+
+
+
+
+
+        var link = document.createElement('a');
+        link.setAttribute('download', 'img_.jpg');
+        link.setAttribute('href', canvasFlip.toDataURL("image/jpeg").replace("image/jpeg", "image/octet-stream"));
+        link.click();
+
+    }
+
+    function rxbd()
+    {
+        var scale=1;
+        var canvas = document.createElement("canvas");
+        let context = canvas.getContext("2d");
+        canvas.width = video.videoWidth * scale;
+        canvas.height = video.videoHeight * scale;
+        context.drawImage(video, 0, 0, canvas.width, canvas.height);
+        var imageData = context.getImageData (0, 0, canvas.width, canvas.height) ;
+        var imageFlip = new ImageData (canvas.width, canvas.height) ;
+        var Npel      = imageData.data.length / 4 ;
+        for ( var kPel = 0 ; kPel < Npel ; kPel++ ) {
+            var kFlip      = flip_index (kPel, canvas.width, canvas.height) ;
+            var offset     = 4 * kPel ;
+            var offsetFlip = 4 * kFlip ;
+            imageFlip.data[offsetFlip + 0] = imageData.data[offset + 0] ;
+            imageFlip.data[offsetFlip + 1] = imageData.data[offset + 1] ;
+            imageFlip.data[offsetFlip + 2] = imageData.data[offset + 2] ;
+            imageFlip.data[offsetFlip + 3] = imageData.data[offset + 3] ;
+        }
+        var canvasFlip = document.createElement('canvas') ;
+        canvasFlip.setAttribute('width', canvas.width) ;
+        canvasFlip.setAttribute('height',canvas.height) ;
+        canvasFlip.getContext('2d').putImageData(imageFlip, 0, 0) ;
+        img.src =canvasFlip.toDataURL('image/jpeg');
+
+
+
+
+
+
+        var data = canvasFlip.toDataURL('image/jpeg',0.5);
+        var img_base64 = data.substring(23);
+
+        var formData= new FormData();
+        formData.append("zp", img_base64);
+        var url = '/rxbd/bd_1vn';
+        var xhr = new XMLHttpRequest();
+        xhr.open('post', url, true);
+        xhr.onload = function (e) {
+            querydata=JSON.parse(xhr.responseText).data;
+            alert(JSON.stringify(querydata));
+
+        }
+        xhr.onerror = function (e) {
+            console.log(e)
+
+        };
+        xhr.send(formData);
+
+
+    }
+
+    function rxzc()
+    {
+        var scale=1;
+        var canvas = document.createElement("canvas");
+        let context = canvas.getContext("2d");
+        canvas.width = video.videoWidth * scale;
+        canvas.height = video.videoHeight * scale;
+        context.drawImage(video, 0, 0, canvas.width, canvas.height);
+        var imageData = context.getImageData (0, 0, canvas.width, canvas.height) ;
+        var imageFlip = new ImageData (canvas.width, canvas.height) ;
+        var Npel      = imageData.data.length / 4 ;
+        for ( var kPel = 0 ; kPel < Npel ; kPel++ ) {
+            var kFlip      = flip_index (kPel, canvas.width, canvas.height) ;
+            var offset     = 4 * kPel ;
+            var offsetFlip = 4 * kFlip ;
+            imageFlip.data[offsetFlip + 0] = imageData.data[offset + 0] ;
+            imageFlip.data[offsetFlip + 1] = imageData.data[offset + 1] ;
+            imageFlip.data[offsetFlip + 2] = imageData.data[offset + 2] ;
+            imageFlip.data[offsetFlip + 3] = imageData.data[offset + 3] ;
+        }
+        var canvasFlip = document.createElement('canvas') ;
+        canvasFlip.setAttribute('width', canvas.width) ;
+        canvasFlip.setAttribute('height',canvas.height) ;
+        canvasFlip.getContext('2d').putImageData(imageFlip, 0, 0) ;
+        img.src =canvasFlip.toDataURL('image/jpeg');
+
+
+
+
+
+
+        var data = canvasFlip.toDataURL('image/jpeg',0.5);
+        var img_base64 = data.substring(23);
+
+        var userInput = prompt("请输入人像唯一键值:", ""); // 第二个参数为默认值,这里设置为空字符串表示没有默认值
+        if (userInput != null) {
+            var formData= new FormData();
+            formData.append("zp", img_base64);
+            formData.append("szfh", userInput);
+            var url = '/rxbd/add_zp';
+            var xhr = new XMLHttpRequest();
+            xhr.open('post', url, true);
+            xhr.onload = function (e) {
+                querydata=JSON.parse(xhr.responseText).data;
+                alert(JSON.stringify(querydata));
+
+            }
+            xhr.onerror = function (e) {
+                console.log(e)
+
+            };
+            xhr.send(formData);
+        } else {
+            alert("未输入人像唯一键值");
+        }
+
+
+    }
+
+
+</script>
+
+</html>

+ 351 - 0
static/face/js/alive_face.js

@@ -0,0 +1,351 @@
+		$(function(){
+			//执行开始的方法
+			//showpos = true;
+			startTrack();
+			
+		})
+		var showpos = false;
+		// Put event listeners into place
+		//window.addEventListener("DOMContentLoaded", function() {
+		// Grab elements, create settings, etc.
+		var canvas = document.getElementById("canvas"),
+			context = canvas.getContext("2d"),
+			video = document.getElementById("video"),
+			videoObj = {
+				"video": true
+			},
+			errBack = function(error) {
+				if(error.PERMISSION_DENIED) {
+					jAlert('用户拒绝了浏览器请求媒体的权限', '提示');
+				} else if(error.NOT_SUPPORTED_ERROR) {
+					jAlert('对不起,您的浏览器不支持拍照功能,请使用其他浏览器', '提示');
+				} else if(error.MANDATORY_UNSATISFIED_ERROR) {
+					jAlert('指定的媒体类型未接收到媒体流', '提示');
+				} else {
+					jAlert('系统未能获取到摄像头,请确保摄像头已正确安装。或尝试刷新页面,重试', '提示');
+				}
+			};
+		// Put video listeners into place
+		if(navigator.getUserMedia) { // Standard
+			navigator.getUserMedia(videoObj, function(stream) {
+
+				//video.src = stream;  //old
+				video.srcObject = stream;  //liang change
+				video.play();
+			}, errBack);
+		} else if(navigator.webkitGetUserMedia) { // WebKit-prefixed
+			try {
+				navigator.webkitGetUserMedia(videoObj, function(stream) {
+					video.src = window.webkitURL.createObjectURL(stream);
+					video.play();
+				}, errBack);
+
+			} catch(error) {
+				alert(error);
+			}
+		} else if(navigator.mozGetUserMedia) { // Firefox-prefixed
+			navigator.mozGetUserMedia(videoObj, function(stream) {
+
+				video.src = window.URL.createObjectURL(stream);
+				video.play();
+			}, errBack);
+		}
+		//张嘴
+		document.getElementById("mouse").addEventListener("click", function() {
+			var audio = document.createElement('audio');
+			var source = document.createElement('source');
+			source.type = "audio/mp3";
+			source.src = "mp3/alive_mouse.mp3";
+			source.autoplay = "autoplay";
+			source.controls = "controls";
+			audio.appendChild(source);
+			audio.play();
+			alive_mouse();
+		});
+		//摇头
+		document.getElementById("head").addEventListener("click", function() {
+			alive_head();
+		});
+		//眨眨眼
+		// document.getElementById("eye").addEventListener("click", function() {
+		// 	var audio = document.createElement('audio');
+		// 	var source = document.createElement('source');
+		// 	source.type = "audio/mp3";
+		// 	source.src = "mp3/alive_eye.mp3";
+		// 	source.autoplay = "autoplay";
+		// 	source.controls = "controls";
+		// 	audio.appendChild(source);
+		// 	audio.play();
+		// 	alive_eye();
+		// });
+
+		//}, false);
+		//////////////////////////////////////////////////////////////////////////////
+		//活体
+		var last_time = 0; //时间因素
+		var last_nose_left = 0;
+		var last_nose_top = 0;
+
+		//张嘴动作
+		var is_mouse_ok = false;
+		var is_alive_mouse = false;
+		var last_dis_eye_norse = 0;
+		var last_dis_mouse = 0;
+
+		//检测张嘴动作
+		function alive_mouse() {
+			document.getElementById("tip").innerHTML = "请张合嘴巴";
+			document.getElementById('result').value = "";
+			is_mouse_ok = false;
+			last_dis_mouse = 0;
+			last_time = 0;
+			last_dis_eye_norse = 100000000;
+			is_alive_head = false;
+			is_alive_mouse = true;
+			is_alive_eye = false;
+		}
+		//摇头动作
+		var is_head_ok = false;
+		var is_alive_head = false;
+		var last_dis_left_right = 100000000;
+		function alive_head() {
+			document.getElementById("tip").innerHTML = "请在水平方向左右摇头";
+			document.getElementById('result').value = "";
+			is_head_ok = false;
+			last_dis_left_right = 100000000;
+			last_time = 0;
+			is_alive_head = true;
+			is_alive_mouse = false;
+			is_alive_eye = false;
+		}
+		//眨眼动作
+		var is_alive_eye = false;
+		var is_eye_ok = false;
+		// function alive_eye() {
+		// 	document.getElementById("tip").innerHTML = "请眨眼";
+		// 	document.getElementById('result').value = "";
+		// 	is_eye_ok = false;
+		// 	last_dis_eye_norse = 100000000;
+        //
+		// 	last_nose_left = 0;
+		// 	last_nose_top = 0;
+        //
+		// 	last_time = 0;
+        //
+		// 	is_alive_head = false;
+		// 	is_alive_mouse = false;
+		// 	is_alive_eye = true;
+		// }
+
+		function startTrack() {
+			var videoInput = document.getElementById('video');
+			var ctracker = new clm.tracker();
+			ctracker.init(pModel);
+			ctracker.start(videoInput);
+			var canvasInput = document.getElementById('canvas');
+			//获取二维画布对象
+			var cc = canvasInput.getContext('2d');
+			cc.lineWidth = 3;
+			function drawLoop() {
+				//requestAnimationFrame(drawLoop);
+				cc.clearRect(0, 0, canvasInput.width, canvasInput.height);
+				//ctracker.draw(canvasInput );
+				var positions = ctracker.getCurrentPosition();
+				if(showpos && positions) {
+					for(var p = 0; p < positions.length; p++) {
+						positionString += "featurepoint " + p + " : [" + positions[p][0].toFixed(2) + "," + positions[p][1].toFixed(2) + "]<br/>";
+					}
+					document.getElementById('positions').innerHTML = positionString;
+				}
+				if(positions) {
+					for(var p = 0; p < 71; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = '#00FF00';
+						cc.fill();
+					}
+					//cc.strokeStyle = 'red';
+					//0-14 轮廓
+					//7 下吧,最下
+
+					//2 最左边
+					//12 最右边
+
+					//15-22 眉毛
+
+					//23-27 左眼睛五个点
+					//27 左眼中间
+					//63-66 左眼四个点
+
+					//28-32 右眼睛五个点
+					//67-70 右眼四个点
+
+					//33-43 鼻子
+					//62 鼻中间
+
+					//44-61 嘴巴
+					//47 嘴巴上
+					//53 嘴巴下
+					///////////////////////////////////////////////////////////////////////////////////////////////
+					//左眼中间
+					for(var p = 27; p <= 27; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//鼻子中间
+					for(var p = 62; p <= 62; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//嘴巴上
+					for(var p = 57; p <= 57; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//嘴巴下
+					for(var p = 60; p <= 60; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//////////////////////////////////////
+					//head
+					if(is_alive_head == true) {
+						if(last_time == 0 || (new Date().getTime() - last_time > 500 && new Date().getTime() - last_time < 10000)) {
+							var xdiff_left = positions[62][0] - positions[2][0];
+							var ydiff_left = positions[62][1] - positions[2][1];
+							var dis_left = Math.pow((xdiff_left * xdiff_left + ydiff_left * ydiff_left), 0.5);
+
+							var xdiff_right = positions[12][0] - positions[62][0];
+							var ydiff_right = positions[12][1] - positions[62][1];
+							var dis_right = Math.pow((xdiff_right * xdiff_right + ydiff_right * ydiff_right), 0.5);
+
+							var xdiff_side = positions[12][0] - positions[2][0];
+							var ydiff_side = positions[12][1] - positions[2][1];
+							var dis_side = Math.pow((xdiff_side * xdiff_side + ydiff_side * ydiff_side), 0.5);
+							var dis_left_right = dis_left - dis_right;
+							document.getElementById('result').value = dis_left_right;
+							if(last_dis_left_right > 0 && dis_left_right > dis_side / 3) {
+
+								document.getElementById('result').value = "通过";
+								var msg_m =document.getElementById('result'); 
+								if(msg_m.value=='通过'){
+									//执行其他方法
+									is_head_ok = true;
+									is_alive_head = false;
+									alert("活体验证通过!");
+									//location.href="http://muxiongxiong.top"
+								}
+							}
+							last_dis_left_right = dis_left_right;
+							last_time = new Date().getTime();
+						}
+					}
+					/////////////////////////////////////
+					//mouse 
+					if(is_alive_mouse == true) {
+						if(last_time == 0 || (new Date().getTime() - last_time > 500 && new Date().getTime() - last_time < 10000)) {
+							//研究和鼻子距离
+							var xdiff = positions[62][0] - positions[27][0];
+							var ydiff = positions[62][1] - positions[27][1];
+							var dis_eye_norse = Math.pow((xdiff * xdiff + ydiff * ydiff), 0.5);
+							//上嘴唇 和下嘴唇距离
+							var xdiff_mouse = positions[53][0] - positions[47][0];
+							var ydiff_mouse = positions[53][1] - positions[47][1];
+							var dis_mouse = Math.pow((xdiff_mouse * xdiff_mouse + ydiff_mouse * ydiff_mouse), 0.5);
+							//上次的眼鼻距离和这次的眼鼻距离差
+							var dn = Math.abs(dis_eye_norse - last_dis_eye_norse);
+							//上次的嘴距离和本次的嘴距离差
+							var dm = Math.abs(dis_mouse - last_dis_mouse);
+							//鼻子的位置确保变化不大
+							if(last_nose_left > 0 && last_nose_top > 0 &&
+								Math.abs(positions[62][0] - last_nose_left) < 5 &&
+								Math.abs(positions[62][1] - last_nose_top) < 5
+							) {
+								document.getElementById('msg').innerHTML = dn;
+								if(last_dis_eye_norse > 0 && dn < dis_eye_norse * 1 / 50) {
+									if(last_dis_mouse > 0 && dm > dis_mouse / 10) {
+										document.getElementById('result').value = "通过";
+										var msg_mouse = document.getElementById('result');
+										if(msg_mouse.value=='通过'){
+											//验证摇头
+											is_alive_mouse = false;
+											is_mouse_ok = true;
+											var audio = document.createElement('audio');
+											var source = document.createElement('source');
+											source.type = "audio/mp3";
+											source.src = "mp3/alive_head.mp3";
+											source.autoplay = "autoplay";
+											source.controls = "controls";
+											audio.appendChild(source);
+											audio.play();
+											alive_head();
+										}
+									}
+								}
+							}
+							last_dis_mouse = dis_mouse;
+							last_dis_eye_norse = dis_eye_norse;
+							last_time = new Date().getTime();
+
+							last_nose_left = positions[62][0];
+							last_nose_top = positions[62][1];
+
+						}
+					}
+					/////////////////////////////////////
+					//eye 
+					// if(is_alive_eye == true) {
+					// 	if(last_time == 0 || (new Date().getTime() - last_time > 10)) {
+                    //
+					// 		var xdiff1 = positions[62][0] - positions[27][0];
+					// 		var ydiff1 = positions[62][1] - positions[27][1];
+					// 		var dis_eye_norse1 = Math.pow((xdiff1 * xdiff1 + ydiff1 * ydiff1), 0.5);
+                    //
+					// 		var xdiff2 = positions[62][0] - positions[32][0];
+					// 		var ydiff2 = positions[62][1] - positions[32][1];
+					// 		var dis_eye_norse2 = Math.pow((xdiff2 * xdiff2 + ydiff2 * ydiff2), 0.5);
+                    //
+					// 		var dis_eye_norse = (dis_eye_norse1 + dis_eye_norse2);
+                    //
+					// 		if(last_nose_left > 0 && last_nose_top > 0 &&
+					// 			Math.abs(positions[62][0] - last_nose_left) < 0.5 &&
+					// 			Math.abs(positions[62][1] - last_nose_top) < 0.5
+					// 		) {
+					// 			document.getElementById('msg').innerHTML = Math.abs(dis_eye_norse - last_dis_eye_norse) - dis_eye_norse * 1 / 20;
+                    //
+					// 			if(last_dis_eye_norse > 0 && (Math.abs(dis_eye_norse - last_dis_eye_norse) > dis_eye_norse * 1 / 20)) {
+                    //
+					// 				document.getElementById('result').value = "通过";
+                    //
+					// 				is_alive_eye = false;
+					// 				is_eye_ok = true;
+                    //
+					// 			}
+					// 		}
+                    //
+					// 		last_nose_left = positions[62][0];
+					// 		last_nose_top = positions[62][1];
+                    //
+					// 		last_dis_eye_norse = dis_eye_norse;
+					// 		last_time = new Date().getTime();
+                    //
+					// 	}
+					// }
+				}
+				requestAnimationFrame(drawLoop);
+			}
+			drawLoop();
+		}

+ 460 - 0
static/face/js/ccv.js

@@ -0,0 +1,460 @@
+if (parallable === undefined) {
+	var parallable = function (file, funct) {
+		parallable.core[funct.toString()] = funct().core;
+		return function () {
+			var i;
+			var async, worker_num, params;
+			if (arguments.length > 1) {
+				async = arguments[arguments.length - 2];
+				worker_num = arguments[arguments.length - 1];
+				params = new Array(arguments.length - 2);
+				for (i = 0; i < arguments.length - 2; i++)
+					params[i] = arguments[i];
+			} else {
+				async = arguments[0].async;
+				worker_num = arguments[0].worker;
+				params = arguments[0];
+				delete params["async"];
+				delete params["worker"];
+				params = [params];
+			}
+			var scope = { "shared" : {} };
+			var ctrl = funct.apply(scope, params);
+			if (async) {
+				return function (complete, error) {
+					var executed = 0;
+					var outputs = new Array(worker_num);
+					var inputs = ctrl.pre.apply(scope, [worker_num]);
+					/* sanitize scope shared because for Chrome/WebKit, worker only support JSONable data */
+					for (i in scope.shared)
+						/* delete function, if any */
+						if (typeof scope.shared[i] == "function")
+							delete scope.shared[i];
+						/* delete DOM object, if any */
+						else if (scope.shared[i].tagName !== undefined)
+							delete scope.shared[i];
+					for (i = 0; i < worker_num; i++) {
+						var worker = new Worker(file);
+						worker.onmessage = (function (i) {
+							return function (event) {
+								outputs[i] = (typeof event.data == "string") ? JSON.parse(event.data) : event.data;
+								executed++;
+								if (executed == worker_num)
+									complete(ctrl.post.apply(scope, [outputs]));
+							}
+						})(i);
+						var msg = { "input" : inputs[i],
+									"name" : funct.toString(),
+									"shared" : scope.shared,
+									"id" : i,
+									"worker" : params.worker_num };
+						try {
+							worker.postMessage(msg);
+						} catch (e) {
+							worker.postMessage(JSON.stringify(msg));
+						}
+					}
+				}
+			} else {
+				return ctrl.post.apply(scope, [[ctrl.core.apply(scope, [ctrl.pre.apply(scope, [1])[0], 0, 1])]]);
+			}
+		}
+	};
+	parallable.core = {};
+}
+
+function get_named_arguments(params, names) {
+	if (params.length > 1) {
+		var new_params = {};
+		for (var i = 0; i < names.length; i++)
+			new_params[names[i]] = params[i];
+		return new_params;
+	} else if (params.length == 1) {
+		return params[0];
+	} else {
+		return {};
+	}
+}
+
+var ccv = {
+	pre : function (image) {
+		if (image.tagName.toLowerCase() == "img") {
+			var canvas = document.createElement("canvas");
+			document.body.appendChild(image);
+			canvas.width = image.offsetWidth;
+			canvas.style.width = image.offsetWidth.toString() + "px";
+			canvas.height = image.offsetHeight;
+			canvas.style.height = image.offsetHeight.toString() + "px";
+			document.body.removeChild(image);
+			var ctx = canvas.getContext("2d");
+			ctx.drawImage(image, 0, 0);
+			return canvas;
+		}
+		return image;
+	},
+
+	grayscale : function (canvas) {
+		var ctx = canvas.getContext("2d");
+		var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+		var data = imageData.data;
+		var pix1, pix2, pix = canvas.width * canvas.height * 4;
+		while (pix > 0)
+			data[pix -= 4] = data[pix1 = pix + 1] = data[pix2 = pix + 2] = (data[pix] * 0.3 + data[pix1] * 0.59 + data[pix2] * 0.11);
+		ctx.putImageData(imageData, 0, 0);
+		return canvas;
+	},
+
+	array_group : function (seq, gfunc) {
+		var i, j;
+		var node = new Array(seq.length);
+		for (i = 0; i < seq.length; i++)
+			node[i] = {"parent" : -1,
+					   "element" : seq[i],
+					   "rank" : 0};
+		for (i = 0; i < seq.length; i++) {
+			if (!node[i].element)
+				continue;
+			var root = i;
+			while (node[root].parent != -1)
+				root = node[root].parent;
+			for (j = 0; j < seq.length; j++) {
+				if( i != j && node[j].element && gfunc(node[i].element, node[j].element)) {
+					var root2 = j;
+
+					while (node[root2].parent != -1)
+						root2 = node[root2].parent;
+
+					if(root2 != root) {
+						if(node[root].rank > node[root2].rank)
+							node[root2].parent = root;
+						else {
+							node[root].parent = root2;
+							if (node[root].rank == node[root2].rank)
+							node[root2].rank++;
+							root = root2;
+						}
+
+						/* compress path from node2 to the root: */
+						var temp, node2 = j;
+						while (node[node2].parent != -1) {
+							temp = node2;
+							node2 = node[node2].parent;
+							node[temp].parent = root;
+						}
+
+						/* compress path from node to the root: */
+						node2 = i;
+						while (node[node2].parent != -1) {
+							temp = node2;
+							node2 = node[node2].parent;
+							node[temp].parent = root;
+						}
+					}
+				}
+			}
+		}
+		var idx = new Array(seq.length);
+		var class_idx = 0;
+		for(i = 0; i < seq.length; i++) {
+			j = -1;
+			var node1 = i;
+			if(node[node1].element) {
+				while (node[node1].parent != -1)
+					node1 = node[node1].parent;
+				if(node[node1].rank >= 0)
+					node[node1].rank = ~class_idx++;
+				j = ~node[node1].rank;
+			}
+			idx[i] = j;
+		}
+		return {"index" : idx, "cat" : class_idx};
+	},
+
+	detect_objects : parallable("ccv.js", function (canvas, cascade, interval, min_neighbors) {
+		if (this.shared !== undefined) {
+			var params = get_named_arguments(arguments, ["canvas", "cascade", "interval", "min_neighbors"]);
+			this.shared.canvas = params.canvas;
+			this.shared.interval = params.interval;
+			this.shared.min_neighbors = params.min_neighbors;
+			this.shared.cascade = params.cascade;
+			this.shared.scale = Math.pow(2, 1 / (params.interval + 1));
+			this.shared.next = params.interval + 1;
+			this.shared.scale_upto = Math.floor(Math.log(Math.min(params.canvas.width / params.cascade.width, params.canvas.height / params.cascade.height)) / Math.log(this.shared.scale));
+			var i;
+			for (i = 0; i < this.shared.cascade.stage_classifier.length; i++)
+				this.shared.cascade.stage_classifier[i].orig_feature = this.shared.cascade.stage_classifier[i].feature;
+		}
+		function pre(worker_num) {
+			var canvas = this.shared.canvas;
+			var interval = this.shared.interval;
+			var scale = this.shared.scale;
+			var next = this.shared.next;
+			var scale_upto = this.shared.scale_upto;
+			var pyr = new Array((scale_upto + next * 2) * 4);
+			var ret = new Array((scale_upto + next * 2) * 4);
+			pyr[0] = canvas;
+			ret[0] = { "width" : pyr[0].width,
+					   "height" : pyr[0].height,
+					   "data" : pyr[0].getContext("2d").getImageData(0, 0, pyr[0].width, pyr[0].height).data };
+			var i;
+			for (i = 1; i <= interval; i++) {
+				pyr[i * 4] = document.createElement("canvas");
+				pyr[i * 4].width = Math.floor(pyr[0].width / Math.pow(scale, i));
+				pyr[i * 4].height = Math.floor(pyr[0].height / Math.pow(scale, i));
+				pyr[i * 4].getContext("2d").drawImage(pyr[0], 0, 0, pyr[0].width, pyr[0].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height);
+				ret[i * 4] = { "width" : pyr[i * 4].width,
+							   "height" : pyr[i * 4].height,
+							   "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data };
+			}
+			for (i = next; i < scale_upto + next * 2; i++) {
+				pyr[i * 4] = document.createElement("canvas");
+				pyr[i * 4].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 0, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height);
+				ret[i * 4] = { "width" : pyr[i * 4].width,
+							   "height" : pyr[i * 4].height,
+							   "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data };
+			}
+			for (i = next * 2; i < scale_upto + next * 2; i++) {
+				pyr[i * 4 + 1] = document.createElement("canvas");
+				pyr[i * 4 + 1].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4 + 1].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4 + 1].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 0, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4 + 1].width - 2, pyr[i * 4 + 1].height);
+				ret[i * 4 + 1] = { "width" : pyr[i * 4 + 1].width,
+								   "height" : pyr[i * 4 + 1].height,
+								   "data" : pyr[i * 4 + 1].getContext("2d").getImageData(0, 0, pyr[i * 4 + 1].width, pyr[i * 4 + 1].height).data };
+				pyr[i * 4 + 2] = document.createElement("canvas");
+				pyr[i * 4 + 2].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4 + 2].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4 + 2].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 1, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height - 2);
+				ret[i * 4 + 2] = { "width" : pyr[i * 4 + 2].width,
+								   "height" : pyr[i * 4 + 2].height,
+								   "data" : pyr[i * 4 + 2].getContext("2d").getImageData(0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height).data };
+				pyr[i * 4 + 3] = document.createElement("canvas");
+				pyr[i * 4 + 3].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4 + 3].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4 + 3].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 1, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 3].width - 2, pyr[i * 4 + 3].height - 2);
+				ret[i * 4 + 3] = { "width" : pyr[i * 4 + 3].width,
+								   "height" : pyr[i * 4 + 3].height,
+								   "data" : pyr[i * 4 + 3].getContext("2d").getImageData(0, 0, pyr[i * 4 + 3].width, pyr[i * 4 + 3].height).data };
+			}
+			return [ret];
+		};
+
+		function core(pyr, id, worker_num) {
+			var cascade = this.shared.cascade;
+			var interval = this.shared.interval;
+			var scale = this.shared.scale;
+			var next = this.shared.next;
+			var scale_upto = this.shared.scale_upto;
+			var i, j, k, x, y, q;
+			var scale_x = 1, scale_y = 1;
+			var dx = [0, 1, 0, 1];
+			var dy = [0, 0, 1, 1];
+			var seq = [];
+			for (i = 0; i < scale_upto; i++) {
+				var qw = pyr[i * 4 + next * 8].width - Math.floor(cascade.width / 4);
+				var qh = pyr[i * 4 + next * 8].height - Math.floor(cascade.height / 4);
+				var step = [pyr[i * 4].width * 4, pyr[i * 4 + next * 4].width * 4, pyr[i * 4 + next * 8].width * 4];
+				var paddings = [pyr[i * 4].width * 16 - qw * 16,
+								pyr[i * 4 + next * 4].width * 8 - qw * 8,
+								pyr[i * 4 + next * 8].width * 4 - qw * 4];
+				for (j = 0; j < cascade.stage_classifier.length; j++) {
+					var orig_feature = cascade.stage_classifier[j].orig_feature;
+					var feature = cascade.stage_classifier[j].feature = new Array(cascade.stage_classifier[j].count);
+					for (k = 0; k < cascade.stage_classifier[j].count; k++) {
+						feature[k] = {"size" : orig_feature[k].size,
+									  "px" : new Array(orig_feature[k].size),
+									  "pz" : new Array(orig_feature[k].size),
+									  "nx" : new Array(orig_feature[k].size),
+									  "nz" : new Array(orig_feature[k].size)};
+						for (q = 0; q < orig_feature[k].size; q++) {
+							feature[k].px[q] = orig_feature[k].px[q] * 4 + orig_feature[k].py[q] * step[orig_feature[k].pz[q]];
+							feature[k].pz[q] = orig_feature[k].pz[q];
+							feature[k].nx[q] = orig_feature[k].nx[q] * 4 + orig_feature[k].ny[q] * step[orig_feature[k].nz[q]];
+							feature[k].nz[q] = orig_feature[k].nz[q];
+						}
+					}
+				}
+				for (q = 0; q < 4; q++) {
+					var u8 = [pyr[i * 4].data, pyr[i * 4 + next * 4].data, pyr[i * 4 + next * 8 + q].data];
+					var u8o = [dx[q] * 8 + dy[q] * pyr[i * 4].width * 8, dx[q] * 4 + dy[q] * pyr[i * 4 + next * 4].width * 4, 0];
+					for (y = 0; y < qh; y++) {
+						for (x = 0; x < qw; x++) {
+							var sum = 0;
+							var flag = true;
+							for (j = 0; j < cascade.stage_classifier.length; j++) {
+								sum = 0;
+								var alpha = cascade.stage_classifier[j].alpha;
+								var feature = cascade.stage_classifier[j].feature;
+								for (k = 0; k < cascade.stage_classifier[j].count; k++) {
+									var feature_k = feature[k];
+									var p, pmin = u8[feature_k.pz[0]][u8o[feature_k.pz[0]] + feature_k.px[0]];
+									var n, nmax = u8[feature_k.nz[0]][u8o[feature_k.nz[0]] + feature_k.nx[0]];
+									if (pmin <= nmax) {
+										sum += alpha[k * 2];
+									} else {
+										var f, shortcut = true;
+										for (f = 0; f < feature_k.size; f++) {
+											if (feature_k.pz[f] >= 0) {
+												p = u8[feature_k.pz[f]][u8o[feature_k.pz[f]] + feature_k.px[f]];
+												if (p < pmin) {
+													if (p <= nmax) {
+														shortcut = false;
+														break;
+													}
+													pmin = p;
+												}
+											}
+											if (feature_k.nz[f] >= 0) {
+												n = u8[feature_k.nz[f]][u8o[feature_k.nz[f]] + feature_k.nx[f]];
+												if (n > nmax) {
+													if (pmin <= n) {
+														shortcut = false;
+														break;
+													}
+													nmax = n;
+												}
+											}
+										}
+										sum += (shortcut) ? alpha[k * 2 + 1] : alpha[k * 2];
+									}
+								}
+								if (sum < cascade.stage_classifier[j].threshold) {
+									flag = false;
+									break;
+								}
+							}
+							if (flag) {
+								seq.push({"x" : (x * 4 + dx[q] * 2) * scale_x,
+										  "y" : (y * 4 + dy[q] * 2) * scale_y,
+										  "width" : cascade.width * scale_x,
+										  "height" : cascade.height * scale_y,
+										  "neighbor" : 1,
+										  "confidence" : sum});
+							}
+							u8o[0] += 16;
+							u8o[1] += 8;
+							u8o[2] += 4;
+						}
+						u8o[0] += paddings[0];
+						u8o[1] += paddings[1];
+						u8o[2] += paddings[2];
+					}
+				}
+				scale_x *= scale;
+				scale_y *= scale;
+			}
+			return seq;
+		};
+
+		function post(seq) {
+			var min_neighbors = this.shared.min_neighbors;
+			var cascade = this.shared.cascade;
+			var interval = this.shared.interval;
+			var scale = this.shared.scale;
+			var next = this.shared.next;
+			var scale_upto = this.shared.scale_upto;
+			var i, j;
+			for (i = 0; i < cascade.stage_classifier.length; i++)
+				cascade.stage_classifier[i].feature = cascade.stage_classifier[i].orig_feature;
+			seq = seq[0];
+			if (!(min_neighbors > 0))
+				return seq;
+			else {
+				var result = ccv.array_group(seq, function (r1, r2) {
+					var distance = Math.floor(r1.width * 0.25 + 0.5);
+
+					return r2.x <= r1.x + distance &&
+						   r2.x >= r1.x - distance &&
+						   r2.y <= r1.y + distance &&
+						   r2.y >= r1.y - distance &&
+						   r2.width <= Math.floor(r1.width * 1.5 + 0.5) &&
+						   Math.floor(r2.width * 1.5 + 0.5) >= r1.width;
+				});
+				var ncomp = result.cat;
+				var idx_seq = result.index;
+				var comps = new Array(ncomp + 1);
+				for (i = 0; i < comps.length; i++)
+					comps[i] = {"neighbors" : 0,
+								"x" : 0,
+								"y" : 0,
+								"width" : 0,
+								"height" : 0,
+								"confidence" : 0};
+
+				// count number of neighbors
+				for(i = 0; i < seq.length; i++)
+				{
+					var r1 = seq[i];
+					var idx = idx_seq[i];
+
+					if (comps[idx].neighbors == 0)
+						comps[idx].confidence = r1.confidence;
+
+					++comps[idx].neighbors;
+
+					comps[idx].x += r1.x;
+					comps[idx].y += r1.y;
+					comps[idx].width += r1.width;
+					comps[idx].height += r1.height;
+					comps[idx].confidence = Math.max(comps[idx].confidence, r1.confidence);
+				}
+
+				var seq2 = [];
+				// calculate average bounding box
+				for(i = 0; i < ncomp; i++)
+				{
+					var n = comps[i].neighbors;
+					if (n >= min_neighbors)
+						seq2.push({"x" : (comps[i].x * 2 + n) / (2 * n),
+								   "y" : (comps[i].y * 2 + n) / (2 * n),
+								   "width" : (comps[i].width * 2 + n) / (2 * n),
+								   "height" : (comps[i].height * 2 + n) / (2 * n),
+								   "neighbors" : comps[i].neighbors,
+								   "confidence" : comps[i].confidence});
+				}
+
+				var result_seq = [];
+				// filter out small face rectangles inside large face rectangles
+				for(i = 0; i < seq2.length; i++)
+				{
+					var r1 = seq2[i];
+					var flag = true;
+					for(j = 0; j < seq2.length; j++)
+					{
+						var r2 = seq2[j];
+						var distance = Math.floor(r2.width * 0.25 + 0.5);
+
+						if(i != j &&
+						   r1.x >= r2.x - distance &&
+						   r1.y >= r2.y - distance &&
+						   r1.x + r1.width <= r2.x + r2.width + distance &&
+						   r1.y + r1.height <= r2.y + r2.height + distance &&
+						   (r2.neighbors > Math.max(3, r1.neighbors) || r1.neighbors < 3))
+						{
+							flag = false;
+							break;
+						}
+					}
+
+					if(flag)
+						result_seq.push(r1);
+				}
+				return result_seq;
+			}
+		};
+		return { "pre" : pre, "core" : core, "post" : post };
+	})
+}
+
+onmessage = function (event) {
+	var data = (typeof event.data == "string") ? JSON.parse(event.data) : event.data;
+	var scope = { "shared" : data.shared };
+	var result = parallable.core[data.name].apply(scope, [data.input, data.id, data.worker]);
+	try {
+		postMessage(result);
+	} catch (e) {
+		postMessage(JSON.stringify(result));
+	}
+}

File diff suppressed because it is too large
+ 8591 - 0
static/face/js/clmtrackr.js


+ 351 - 0
static/face/js/face/alive_face.js

@@ -0,0 +1,351 @@
+		$(function(){
+			//执行开始的方法
+			//showpos = true;
+			startTrack();
+			
+		})
+		var showpos = false;
+		// Put event listeners into place
+		//window.addEventListener("DOMContentLoaded", function() {
+		// Grab elements, create settings, etc.
+		var canvas = document.getElementById("canvas"),
+			context = canvas.getContext("2d"),
+			video = document.getElementById("video"),
+			videoObj = {
+				"video": true
+			},
+			errBack = function(error) {
+				if(error.PERMISSION_DENIED) {
+					jAlert('用户拒绝了浏览器请求媒体的权限', '提示');
+				} else if(error.NOT_SUPPORTED_ERROR) {
+					jAlert('对不起,您的浏览器不支持拍照功能,请使用其他浏览器', '提示');
+				} else if(error.MANDATORY_UNSATISFIED_ERROR) {
+					jAlert('指定的媒体类型未接收到媒体流', '提示');
+				} else {
+					jAlert('系统未能获取到摄像头,请确保摄像头已正确安装。或尝试刷新页面,重试', '提示');
+				}
+			};
+		// Put video listeners into place
+		if(navigator.getUserMedia) { // Standard
+			navigator.getUserMedia(videoObj, function(stream) {
+
+				//video.src = stream;  //old
+				video.srcObject = stream;  //liang change
+				video.play();
+			}, errBack);
+		} else if(navigator.webkitGetUserMedia) { // WebKit-prefixed
+			try {
+				navigator.webkitGetUserMedia(videoObj, function(stream) {
+					video.src = window.webkitURL.createObjectURL(stream);
+					video.play();
+				}, errBack);
+
+			} catch(error) {
+				alert(error);
+			}
+		} else if(navigator.mozGetUserMedia) { // Firefox-prefixed
+			navigator.mozGetUserMedia(videoObj, function(stream) {
+
+				video.src = window.URL.createObjectURL(stream);
+				video.play();
+			}, errBack);
+		}
+		//张嘴
+		document.getElementById("mouse").addEventListener("click", function() {
+			var audio = document.createElement('audio');
+			var source = document.createElement('source');
+			source.type = "audio/mp3";
+			source.src = "mp3/alive_mouse.mp3";
+			source.autoplay = "autoplay";
+			source.controls = "controls";
+			audio.appendChild(source);
+			audio.play();
+			alive_mouse();
+		});
+		//摇头
+		document.getElementById("head").addEventListener("click", function() {
+			alive_head();
+		});
+		//眨眨眼
+		// document.getElementById("eye").addEventListener("click", function() {
+		// 	var audio = document.createElement('audio');
+		// 	var source = document.createElement('source');
+		// 	source.type = "audio/mp3";
+		// 	source.src = "mp3/alive_eye.mp3";
+		// 	source.autoplay = "autoplay";
+		// 	source.controls = "controls";
+		// 	audio.appendChild(source);
+		// 	audio.play();
+		// 	alive_eye();
+		// });
+
+		//}, false);
+		//////////////////////////////////////////////////////////////////////////////
+		//活体
+		var last_time = 0; //时间因素
+		var last_nose_left = 0;
+		var last_nose_top = 0;
+
+		//张嘴动作
+		var is_mouse_ok = false;
+		var is_alive_mouse = false;
+		var last_dis_eye_norse = 0;
+		var last_dis_mouse = 0;
+
+		//检测张嘴动作
+		function alive_mouse() {
+			document.getElementById("tip").innerHTML = "请张合嘴巴";
+			document.getElementById('result').value = "";
+			is_mouse_ok = false;
+			last_dis_mouse = 0;
+			last_time = 0;
+			last_dis_eye_norse = 100000000;
+			is_alive_head = false;
+			is_alive_mouse = true;
+			is_alive_eye = false;
+		}
+		//摇头动作
+		var is_head_ok = false;
+		var is_alive_head = false;
+		var last_dis_left_right = 100000000;
+		function alive_head() {
+			document.getElementById("tip").innerHTML = "请在水平方向左右摇头";
+			document.getElementById('result').value = "";
+			is_head_ok = false;
+			last_dis_left_right = 100000000;
+			last_time = 0;
+			is_alive_head = true;
+			is_alive_mouse = false;
+			is_alive_eye = false;
+		}
+		//眨眼动作
+		var is_alive_eye = false;
+		var is_eye_ok = false;
+		// function alive_eye() {
+		// 	document.getElementById("tip").innerHTML = "请眨眼";
+		// 	document.getElementById('result').value = "";
+		// 	is_eye_ok = false;
+		// 	last_dis_eye_norse = 100000000;
+        //
+		// 	last_nose_left = 0;
+		// 	last_nose_top = 0;
+        //
+		// 	last_time = 0;
+        //
+		// 	is_alive_head = false;
+		// 	is_alive_mouse = false;
+		// 	is_alive_eye = true;
+		// }
+
+		function startTrack() {
+			var videoInput = document.getElementById('video');
+			var ctracker = new clm.tracker();
+			ctracker.init(pModel);
+			ctracker.start(videoInput);
+			var canvasInput = document.getElementById('canvas');
+			//获取二维画布对象
+			var cc = canvasInput.getContext('2d');
+			cc.lineWidth = 3;
+			function drawLoop() {
+				//requestAnimationFrame(drawLoop);
+				cc.clearRect(0, 0, canvasInput.width, canvasInput.height);
+				//ctracker.draw(canvasInput );
+				var positions = ctracker.getCurrentPosition();
+				if(showpos && positions) {
+					for(var p = 0; p < positions.length; p++) {
+						positionString += "featurepoint " + p + " : [" + positions[p][0].toFixed(2) + "," + positions[p][1].toFixed(2) + "]<br/>";
+					}
+					document.getElementById('positions').innerHTML = positionString;
+				}
+				if(positions) {
+					for(var p = 0; p < 71; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = '#00FF00';
+						cc.fill();
+					}
+					//cc.strokeStyle = 'red';
+					//0-14 轮廓
+					//7 下吧,最下
+
+					//2 最左边
+					//12 最右边
+
+					//15-22 眉毛
+
+					//23-27 左眼睛五个点
+					//27 左眼中间
+					//63-66 左眼四个点
+
+					//28-32 右眼睛五个点
+					//67-70 右眼四个点
+
+					//33-43 鼻子
+					//62 鼻中间
+
+					//44-61 嘴巴
+					//47 嘴巴上
+					//53 嘴巴下
+					///////////////////////////////////////////////////////////////////////////////////////////////
+					//左眼中间
+					for(var p = 27; p <= 27; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//鼻子中间
+					for(var p = 62; p <= 62; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//嘴巴上
+					for(var p = 57; p <= 57; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//嘴巴下
+					for(var p = 60; p <= 60; p++) {
+						cc.beginPath();
+						cc.arc(positions[p][0].toFixed(2), positions[p][1].toFixed(2), 2, 0, Math.PI * 2, true);
+						cc.closePath();
+						cc.fillStyle = 'red';
+						cc.fill();
+					}
+					//////////////////////////////////////
+					//head
+					if(is_alive_head == true) {
+						if(last_time == 0 || (new Date().getTime() - last_time > 500 && new Date().getTime() - last_time < 10000)) {
+							var xdiff_left = positions[62][0] - positions[2][0];
+							var ydiff_left = positions[62][1] - positions[2][1];
+							var dis_left = Math.pow((xdiff_left * xdiff_left + ydiff_left * ydiff_left), 0.5);
+
+							var xdiff_right = positions[12][0] - positions[62][0];
+							var ydiff_right = positions[12][1] - positions[62][1];
+							var dis_right = Math.pow((xdiff_right * xdiff_right + ydiff_right * ydiff_right), 0.5);
+
+							var xdiff_side = positions[12][0] - positions[2][0];
+							var ydiff_side = positions[12][1] - positions[2][1];
+							var dis_side = Math.pow((xdiff_side * xdiff_side + ydiff_side * ydiff_side), 0.5);
+							var dis_left_right = dis_left - dis_right;
+							document.getElementById('result').value = dis_left_right;
+							if(last_dis_left_right > 0 && dis_left_right > dis_side / 3) {
+
+								document.getElementById('result').value = "通过";
+								var msg_m =document.getElementById('result'); 
+								if(msg_m.value=='通过'){
+									//执行其他方法
+									is_head_ok = true;
+									is_alive_head = false;
+									alert("活体验证通过!");
+									//location.href="http://muxiongxiong.top"
+								}
+							}
+							last_dis_left_right = dis_left_right;
+							last_time = new Date().getTime();
+						}
+					}
+					/////////////////////////////////////
+					//mouse 
+					if(is_alive_mouse == true) {
+						if(last_time == 0 || (new Date().getTime() - last_time > 500 && new Date().getTime() - last_time < 10000)) {
+							//研究和鼻子距离
+							var xdiff = positions[62][0] - positions[27][0];
+							var ydiff = positions[62][1] - positions[27][1];
+							var dis_eye_norse = Math.pow((xdiff * xdiff + ydiff * ydiff), 0.5);
+							//上嘴唇 和下嘴唇距离
+							var xdiff_mouse = positions[53][0] - positions[47][0];
+							var ydiff_mouse = positions[53][1] - positions[47][1];
+							var dis_mouse = Math.pow((xdiff_mouse * xdiff_mouse + ydiff_mouse * ydiff_mouse), 0.5);
+							//上次的眼鼻距离和这次的眼鼻距离差
+							var dn = Math.abs(dis_eye_norse - last_dis_eye_norse);
+							//上次的嘴距离和本次的嘴距离差
+							var dm = Math.abs(dis_mouse - last_dis_mouse);
+							//鼻子的位置确保变化不大
+							if(last_nose_left > 0 && last_nose_top > 0 &&
+								Math.abs(positions[62][0] - last_nose_left) < 5 &&
+								Math.abs(positions[62][1] - last_nose_top) < 5
+							) {
+								document.getElementById('msg').innerHTML = dn;
+								if(last_dis_eye_norse > 0 && dn < dis_eye_norse * 1 / 50) {
+									if(last_dis_mouse > 0 && dm > dis_mouse / 10) {
+										document.getElementById('result').value = "通过";
+										var msg_mouse = document.getElementById('result');
+										if(msg_mouse.value=='通过'){
+											//验证摇头
+											is_alive_mouse = false;
+											is_mouse_ok = true;
+											var audio = document.createElement('audio');
+											var source = document.createElement('source');
+											source.type = "audio/mp3";
+											source.src = "mp3/alive_head.mp3";
+											source.autoplay = "autoplay";
+											source.controls = "controls";
+											audio.appendChild(source);
+											audio.play();
+											alive_head();
+										}
+									}
+								}
+							}
+							last_dis_mouse = dis_mouse;
+							last_dis_eye_norse = dis_eye_norse;
+							last_time = new Date().getTime();
+
+							last_nose_left = positions[62][0];
+							last_nose_top = positions[62][1];
+
+						}
+					}
+					/////////////////////////////////////
+					//eye 
+					// if(is_alive_eye == true) {
+					// 	if(last_time == 0 || (new Date().getTime() - last_time > 10)) {
+                    //
+					// 		var xdiff1 = positions[62][0] - positions[27][0];
+					// 		var ydiff1 = positions[62][1] - positions[27][1];
+					// 		var dis_eye_norse1 = Math.pow((xdiff1 * xdiff1 + ydiff1 * ydiff1), 0.5);
+                    //
+					// 		var xdiff2 = positions[62][0] - positions[32][0];
+					// 		var ydiff2 = positions[62][1] - positions[32][1];
+					// 		var dis_eye_norse2 = Math.pow((xdiff2 * xdiff2 + ydiff2 * ydiff2), 0.5);
+                    //
+					// 		var dis_eye_norse = (dis_eye_norse1 + dis_eye_norse2);
+                    //
+					// 		if(last_nose_left > 0 && last_nose_top > 0 &&
+					// 			Math.abs(positions[62][0] - last_nose_left) < 0.5 &&
+					// 			Math.abs(positions[62][1] - last_nose_top) < 0.5
+					// 		) {
+					// 			document.getElementById('msg').innerHTML = Math.abs(dis_eye_norse - last_dis_eye_norse) - dis_eye_norse * 1 / 20;
+                    //
+					// 			if(last_dis_eye_norse > 0 && (Math.abs(dis_eye_norse - last_dis_eye_norse) > dis_eye_norse * 1 / 20)) {
+                    //
+					// 				document.getElementById('result').value = "通过";
+                    //
+					// 				is_alive_eye = false;
+					// 				is_eye_ok = true;
+                    //
+					// 			}
+					// 		}
+                    //
+					// 		last_nose_left = positions[62][0];
+					// 		last_nose_top = positions[62][1];
+                    //
+					// 		last_dis_eye_norse = dis_eye_norse;
+					// 		last_time = new Date().getTime();
+                    //
+					// 	}
+					// }
+				}
+				requestAnimationFrame(drawLoop);
+			}
+			drawLoop();
+		}

+ 460 - 0
static/face/js/face/ccv.js

@@ -0,0 +1,460 @@
+if (parallable === undefined) {
+	var parallable = function (file, funct) {
+		parallable.core[funct.toString()] = funct().core;
+		return function () {
+			var i;
+			var async, worker_num, params;
+			if (arguments.length > 1) {
+				async = arguments[arguments.length - 2];
+				worker_num = arguments[arguments.length - 1];
+				params = new Array(arguments.length - 2);
+				for (i = 0; i < arguments.length - 2; i++)
+					params[i] = arguments[i];
+			} else {
+				async = arguments[0].async;
+				worker_num = arguments[0].worker;
+				params = arguments[0];
+				delete params["async"];
+				delete params["worker"];
+				params = [params];
+			}
+			var scope = { "shared" : {} };
+			var ctrl = funct.apply(scope, params);
+			if (async) {
+				return function (complete, error) {
+					var executed = 0;
+					var outputs = new Array(worker_num);
+					var inputs = ctrl.pre.apply(scope, [worker_num]);
+					/* sanitize scope shared because for Chrome/WebKit, worker only support JSONable data */
+					for (i in scope.shared)
+						/* delete function, if any */
+						if (typeof scope.shared[i] == "function")
+							delete scope.shared[i];
+						/* delete DOM object, if any */
+						else if (scope.shared[i].tagName !== undefined)
+							delete scope.shared[i];
+					for (i = 0; i < worker_num; i++) {
+						var worker = new Worker(file);
+						worker.onmessage = (function (i) {
+							return function (event) {
+								outputs[i] = (typeof event.data == "string") ? JSON.parse(event.data) : event.data;
+								executed++;
+								if (executed == worker_num)
+									complete(ctrl.post.apply(scope, [outputs]));
+							}
+						})(i);
+						var msg = { "input" : inputs[i],
+									"name" : funct.toString(),
+									"shared" : scope.shared,
+									"id" : i,
+									"worker" : params.worker_num };
+						try {
+							worker.postMessage(msg);
+						} catch (e) {
+							worker.postMessage(JSON.stringify(msg));
+						}
+					}
+				}
+			} else {
+				return ctrl.post.apply(scope, [[ctrl.core.apply(scope, [ctrl.pre.apply(scope, [1])[0], 0, 1])]]);
+			}
+		}
+	};
+	parallable.core = {};
+}
+
+function get_named_arguments(params, names) {
+	if (params.length > 1) {
+		var new_params = {};
+		for (var i = 0; i < names.length; i++)
+			new_params[names[i]] = params[i];
+		return new_params;
+	} else if (params.length == 1) {
+		return params[0];
+	} else {
+		return {};
+	}
+}
+
+var ccv = {
+	pre : function (image) {
+		if (image.tagName.toLowerCase() == "img") {
+			var canvas = document.createElement("canvas");
+			document.body.appendChild(image);
+			canvas.width = image.offsetWidth;
+			canvas.style.width = image.offsetWidth.toString() + "px";
+			canvas.height = image.offsetHeight;
+			canvas.style.height = image.offsetHeight.toString() + "px";
+			document.body.removeChild(image);
+			var ctx = canvas.getContext("2d");
+			ctx.drawImage(image, 0, 0);
+			return canvas;
+		}
+		return image;
+	},
+
+	grayscale : function (canvas) {
+		var ctx = canvas.getContext("2d");
+		var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+		var data = imageData.data;
+		var pix1, pix2, pix = canvas.width * canvas.height * 4;
+		while (pix > 0)
+			data[pix -= 4] = data[pix1 = pix + 1] = data[pix2 = pix + 2] = (data[pix] * 0.3 + data[pix1] * 0.59 + data[pix2] * 0.11);
+		ctx.putImageData(imageData, 0, 0);
+		return canvas;
+	},
+
+	array_group : function (seq, gfunc) {
+		var i, j;
+		var node = new Array(seq.length);
+		for (i = 0; i < seq.length; i++)
+			node[i] = {"parent" : -1,
+					   "element" : seq[i],
+					   "rank" : 0};
+		for (i = 0; i < seq.length; i++) {
+			if (!node[i].element)
+				continue;
+			var root = i;
+			while (node[root].parent != -1)
+				root = node[root].parent;
+			for (j = 0; j < seq.length; j++) {
+				if( i != j && node[j].element && gfunc(node[i].element, node[j].element)) {
+					var root2 = j;
+
+					while (node[root2].parent != -1)
+						root2 = node[root2].parent;
+
+					if(root2 != root) {
+						if(node[root].rank > node[root2].rank)
+							node[root2].parent = root;
+						else {
+							node[root].parent = root2;
+							if (node[root].rank == node[root2].rank)
+							node[root2].rank++;
+							root = root2;
+						}
+
+						/* compress path from node2 to the root: */
+						var temp, node2 = j;
+						while (node[node2].parent != -1) {
+							temp = node2;
+							node2 = node[node2].parent;
+							node[temp].parent = root;
+						}
+
+						/* compress path from node to the root: */
+						node2 = i;
+						while (node[node2].parent != -1) {
+							temp = node2;
+							node2 = node[node2].parent;
+							node[temp].parent = root;
+						}
+					}
+				}
+			}
+		}
+		var idx = new Array(seq.length);
+		var class_idx = 0;
+		for(i = 0; i < seq.length; i++) {
+			j = -1;
+			var node1 = i;
+			if(node[node1].element) {
+				while (node[node1].parent != -1)
+					node1 = node[node1].parent;
+				if(node[node1].rank >= 0)
+					node[node1].rank = ~class_idx++;
+				j = ~node[node1].rank;
+			}
+			idx[i] = j;
+		}
+		return {"index" : idx, "cat" : class_idx};
+	},
+
+	detect_objects : parallable("ccv.js", function (canvas, cascade, interval, min_neighbors) {
+		if (this.shared !== undefined) {
+			var params = get_named_arguments(arguments, ["canvas", "cascade", "interval", "min_neighbors"]);
+			this.shared.canvas = params.canvas;
+			this.shared.interval = params.interval;
+			this.shared.min_neighbors = params.min_neighbors;
+			this.shared.cascade = params.cascade;
+			this.shared.scale = Math.pow(2, 1 / (params.interval + 1));
+			this.shared.next = params.interval + 1;
+			this.shared.scale_upto = Math.floor(Math.log(Math.min(params.canvas.width / params.cascade.width, params.canvas.height / params.cascade.height)) / Math.log(this.shared.scale));
+			var i;
+			for (i = 0; i < this.shared.cascade.stage_classifier.length; i++)
+				this.shared.cascade.stage_classifier[i].orig_feature = this.shared.cascade.stage_classifier[i].feature;
+		}
+		function pre(worker_num) {
+			var canvas = this.shared.canvas;
+			var interval = this.shared.interval;
+			var scale = this.shared.scale;
+			var next = this.shared.next;
+			var scale_upto = this.shared.scale_upto;
+			var pyr = new Array((scale_upto + next * 2) * 4);
+			var ret = new Array((scale_upto + next * 2) * 4);
+			pyr[0] = canvas;
+			ret[0] = { "width" : pyr[0].width,
+					   "height" : pyr[0].height,
+					   "data" : pyr[0].getContext("2d").getImageData(0, 0, pyr[0].width, pyr[0].height).data };
+			var i;
+			for (i = 1; i <= interval; i++) {
+				pyr[i * 4] = document.createElement("canvas");
+				pyr[i * 4].width = Math.floor(pyr[0].width / Math.pow(scale, i));
+				pyr[i * 4].height = Math.floor(pyr[0].height / Math.pow(scale, i));
+				pyr[i * 4].getContext("2d").drawImage(pyr[0], 0, 0, pyr[0].width, pyr[0].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height);
+				ret[i * 4] = { "width" : pyr[i * 4].width,
+							   "height" : pyr[i * 4].height,
+							   "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data };
+			}
+			for (i = next; i < scale_upto + next * 2; i++) {
+				pyr[i * 4] = document.createElement("canvas");
+				pyr[i * 4].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 0, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height);
+				ret[i * 4] = { "width" : pyr[i * 4].width,
+							   "height" : pyr[i * 4].height,
+							   "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data };
+			}
+			for (i = next * 2; i < scale_upto + next * 2; i++) {
+				pyr[i * 4 + 1] = document.createElement("canvas");
+				pyr[i * 4 + 1].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4 + 1].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4 + 1].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 0, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4 + 1].width - 2, pyr[i * 4 + 1].height);
+				ret[i * 4 + 1] = { "width" : pyr[i * 4 + 1].width,
+								   "height" : pyr[i * 4 + 1].height,
+								   "data" : pyr[i * 4 + 1].getContext("2d").getImageData(0, 0, pyr[i * 4 + 1].width, pyr[i * 4 + 1].height).data };
+				pyr[i * 4 + 2] = document.createElement("canvas");
+				pyr[i * 4 + 2].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4 + 2].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4 + 2].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 1, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height - 2);
+				ret[i * 4 + 2] = { "width" : pyr[i * 4 + 2].width,
+								   "height" : pyr[i * 4 + 2].height,
+								   "data" : pyr[i * 4 + 2].getContext("2d").getImageData(0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height).data };
+				pyr[i * 4 + 3] = document.createElement("canvas");
+				pyr[i * 4 + 3].width = Math.floor(pyr[i * 4 - next * 4].width / 2);
+				pyr[i * 4 + 3].height = Math.floor(pyr[i * 4 - next * 4].height / 2);
+				pyr[i * 4 + 3].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 1, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 3].width - 2, pyr[i * 4 + 3].height - 2);
+				ret[i * 4 + 3] = { "width" : pyr[i * 4 + 3].width,
+								   "height" : pyr[i * 4 + 3].height,
+								   "data" : pyr[i * 4 + 3].getContext("2d").getImageData(0, 0, pyr[i * 4 + 3].width, pyr[i * 4 + 3].height).data };
+			}
+			return [ret];
+		};
+
+		function core(pyr, id, worker_num) {
+			var cascade = this.shared.cascade;
+			var interval = this.shared.interval;
+			var scale = this.shared.scale;
+			var next = this.shared.next;
+			var scale_upto = this.shared.scale_upto;
+			var i, j, k, x, y, q;
+			var scale_x = 1, scale_y = 1;
+			var dx = [0, 1, 0, 1];
+			var dy = [0, 0, 1, 1];
+			var seq = [];
+			for (i = 0; i < scale_upto; i++) {
+				var qw = pyr[i * 4 + next * 8].width - Math.floor(cascade.width / 4);
+				var qh = pyr[i * 4 + next * 8].height - Math.floor(cascade.height / 4);
+				var step = [pyr[i * 4].width * 4, pyr[i * 4 + next * 4].width * 4, pyr[i * 4 + next * 8].width * 4];
+				var paddings = [pyr[i * 4].width * 16 - qw * 16,
+								pyr[i * 4 + next * 4].width * 8 - qw * 8,
+								pyr[i * 4 + next * 8].width * 4 - qw * 4];
+				for (j = 0; j < cascade.stage_classifier.length; j++) {
+					var orig_feature = cascade.stage_classifier[j].orig_feature;
+					var feature = cascade.stage_classifier[j].feature = new Array(cascade.stage_classifier[j].count);
+					for (k = 0; k < cascade.stage_classifier[j].count; k++) {
+						feature[k] = {"size" : orig_feature[k].size,
+									  "px" : new Array(orig_feature[k].size),
+									  "pz" : new Array(orig_feature[k].size),
+									  "nx" : new Array(orig_feature[k].size),
+									  "nz" : new Array(orig_feature[k].size)};
+						for (q = 0; q < orig_feature[k].size; q++) {
+							feature[k].px[q] = orig_feature[k].px[q] * 4 + orig_feature[k].py[q] * step[orig_feature[k].pz[q]];
+							feature[k].pz[q] = orig_feature[k].pz[q];
+							feature[k].nx[q] = orig_feature[k].nx[q] * 4 + orig_feature[k].ny[q] * step[orig_feature[k].nz[q]];
+							feature[k].nz[q] = orig_feature[k].nz[q];
+						}
+					}
+				}
+				for (q = 0; q < 4; q++) {
+					var u8 = [pyr[i * 4].data, pyr[i * 4 + next * 4].data, pyr[i * 4 + next * 8 + q].data];
+					var u8o = [dx[q] * 8 + dy[q] * pyr[i * 4].width * 8, dx[q] * 4 + dy[q] * pyr[i * 4 + next * 4].width * 4, 0];
+					for (y = 0; y < qh; y++) {
+						for (x = 0; x < qw; x++) {
+							var sum = 0;
+							var flag = true;
+							for (j = 0; j < cascade.stage_classifier.length; j++) {
+								sum = 0;
+								var alpha = cascade.stage_classifier[j].alpha;
+								var feature = cascade.stage_classifier[j].feature;
+								for (k = 0; k < cascade.stage_classifier[j].count; k++) {
+									var feature_k = feature[k];
+									var p, pmin = u8[feature_k.pz[0]][u8o[feature_k.pz[0]] + feature_k.px[0]];
+									var n, nmax = u8[feature_k.nz[0]][u8o[feature_k.nz[0]] + feature_k.nx[0]];
+									if (pmin <= nmax) {
+										sum += alpha[k * 2];
+									} else {
+										var f, shortcut = true;
+										for (f = 0; f < feature_k.size; f++) {
+											if (feature_k.pz[f] >= 0) {
+												p = u8[feature_k.pz[f]][u8o[feature_k.pz[f]] + feature_k.px[f]];
+												if (p < pmin) {
+													if (p <= nmax) {
+														shortcut = false;
+														break;
+													}
+													pmin = p;
+												}
+											}
+											if (feature_k.nz[f] >= 0) {
+												n = u8[feature_k.nz[f]][u8o[feature_k.nz[f]] + feature_k.nx[f]];
+												if (n > nmax) {
+													if (pmin <= n) {
+														shortcut = false;
+														break;
+													}
+													nmax = n;
+												}
+											}
+										}
+										sum += (shortcut) ? alpha[k * 2 + 1] : alpha[k * 2];
+									}
+								}
+								if (sum < cascade.stage_classifier[j].threshold) {
+									flag = false;
+									break;
+								}
+							}
+							if (flag) {
+								seq.push({"x" : (x * 4 + dx[q] * 2) * scale_x,
+										  "y" : (y * 4 + dy[q] * 2) * scale_y,
+										  "width" : cascade.width * scale_x,
+										  "height" : cascade.height * scale_y,
+										  "neighbor" : 1,
+										  "confidence" : sum});
+							}
+							u8o[0] += 16;
+							u8o[1] += 8;
+							u8o[2] += 4;
+						}
+						u8o[0] += paddings[0];
+						u8o[1] += paddings[1];
+						u8o[2] += paddings[2];
+					}
+				}
+				scale_x *= scale;
+				scale_y *= scale;
+			}
+			return seq;
+		};
+
+		function post(seq) {
+			var min_neighbors = this.shared.min_neighbors;
+			var cascade = this.shared.cascade;
+			var interval = this.shared.interval;
+			var scale = this.shared.scale;
+			var next = this.shared.next;
+			var scale_upto = this.shared.scale_upto;
+			var i, j;
+			for (i = 0; i < cascade.stage_classifier.length; i++)
+				cascade.stage_classifier[i].feature = cascade.stage_classifier[i].orig_feature;
+			seq = seq[0];
+			if (!(min_neighbors > 0))
+				return seq;
+			else {
+				var result = ccv.array_group(seq, function (r1, r2) {
+					var distance = Math.floor(r1.width * 0.25 + 0.5);
+
+					return r2.x <= r1.x + distance &&
+						   r2.x >= r1.x - distance &&
+						   r2.y <= r1.y + distance &&
+						   r2.y >= r1.y - distance &&
+						   r2.width <= Math.floor(r1.width * 1.5 + 0.5) &&
+						   Math.floor(r2.width * 1.5 + 0.5) >= r1.width;
+				});
+				var ncomp = result.cat;
+				var idx_seq = result.index;
+				var comps = new Array(ncomp + 1);
+				for (i = 0; i < comps.length; i++)
+					comps[i] = {"neighbors" : 0,
+								"x" : 0,
+								"y" : 0,
+								"width" : 0,
+								"height" : 0,
+								"confidence" : 0};
+
+				// count number of neighbors
+				for(i = 0; i < seq.length; i++)
+				{
+					var r1 = seq[i];
+					var idx = idx_seq[i];
+
+					if (comps[idx].neighbors == 0)
+						comps[idx].confidence = r1.confidence;
+
+					++comps[idx].neighbors;
+
+					comps[idx].x += r1.x;
+					comps[idx].y += r1.y;
+					comps[idx].width += r1.width;
+					comps[idx].height += r1.height;
+					comps[idx].confidence = Math.max(comps[idx].confidence, r1.confidence);
+				}
+
+				var seq2 = [];
+				// calculate average bounding box
+				for(i = 0; i < ncomp; i++)
+				{
+					var n = comps[i].neighbors;
+					if (n >= min_neighbors)
+						seq2.push({"x" : (comps[i].x * 2 + n) / (2 * n),
+								   "y" : (comps[i].y * 2 + n) / (2 * n),
+								   "width" : (comps[i].width * 2 + n) / (2 * n),
+								   "height" : (comps[i].height * 2 + n) / (2 * n),
+								   "neighbors" : comps[i].neighbors,
+								   "confidence" : comps[i].confidence});
+				}
+
+				var result_seq = [];
+				// filter out small face rectangles inside large face rectangles
+				for(i = 0; i < seq2.length; i++)
+				{
+					var r1 = seq2[i];
+					var flag = true;
+					for(j = 0; j < seq2.length; j++)
+					{
+						var r2 = seq2[j];
+						var distance = Math.floor(r2.width * 0.25 + 0.5);
+
+						if(i != j &&
+						   r1.x >= r2.x - distance &&
+						   r1.y >= r2.y - distance &&
+						   r1.x + r1.width <= r2.x + r2.width + distance &&
+						   r1.y + r1.height <= r2.y + r2.height + distance &&
+						   (r2.neighbors > Math.max(3, r1.neighbors) || r1.neighbors < 3))
+						{
+							flag = false;
+							break;
+						}
+					}
+
+					if(flag)
+						result_seq.push(r1);
+				}
+				return result_seq;
+			}
+		};
+		return { "pre" : pre, "core" : core, "post" : post };
+	})
+}
+
+onmessage = function (event) {
+	var data = (typeof event.data == "string") ? JSON.parse(event.data) : event.data;
+	var scope = { "shared" : data.shared };
+	var result = parallable.core[data.name].apply(scope, [data.input, data.id, data.worker]);
+	try {
+		postMessage(result);
+	} catch (e) {
+		postMessage(JSON.stringify(result));
+	}
+}

File diff suppressed because it is too large
+ 8591 - 0
static/face/js/face/clmtrackr.js


File diff suppressed because it is too large
+ 9472 - 0
static/face/js/face/jquery-1.8.3.js


File diff suppressed because it is too large
+ 623 - 0
static/face/js/face/model_pca_20_svm.js


File diff suppressed because it is too large
+ 4425 - 0
static/face/js/face/numeric.js


+ 44 - 0
static/face/js/face/utils.js

@@ -0,0 +1,44 @@
+// helper functions
+
+/**
+ * Provides requestAnimationFrame in a cross browser way.
+ */
+window.requestAnimFrame = (function() {
+  return window.requestAnimationFrame ||
+         window.webkitRequestAnimationFrame ||
+         window.mozRequestAnimationFrame ||
+         window.oRequestAnimationFrame ||
+         window.msRequestAnimationFrame ||
+         function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
+           return window.setTimeout(callback, 1000/60);
+         };
+})();
+
+/**
+ * Provides cancelRequestAnimationFrame in a cross browser way.
+ */
+window.cancelRequestAnimFrame = (function() {
+  return window.cancelAnimationFrame ||
+         window.webkitCancelRequestAnimationFrame ||
+         window.mozCancelRequestAnimationFrame ||
+         window.oCancelRequestAnimationFrame ||
+         window.msCancelRequestAnimationFrame ||
+         window.clearTimeout;
+})();
+
+// video support utility functions
+function supports_video() {
+  return !!document.createElement('video').canPlayType;
+}
+
+function supports_h264_baseline_video() {
+  if (!supports_video()) { return false; }
+  var v = document.createElement("video");
+  return v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
+}
+
+function supports_ogg_theora_video() {
+  if (!supports_video()) { return false; }
+  var v = document.createElement("video");
+  return v.canPlayType('video/ogg; codecs="theora, vorbis"');
+}

File diff suppressed because it is too large
+ 9472 - 0
static/face/js/jquery-1.8.3.js


File diff suppressed because it is too large
+ 623 - 0
static/face/js/model_pca_20_svm.js


File diff suppressed because it is too large
+ 4425 - 0
static/face/js/numeric.js


+ 44 - 0
static/face/js/utils.js

@@ -0,0 +1,44 @@
+// helper functions
+
+/**
+ * Provides requestAnimationFrame in a cross browser way.
+ */
+window.requestAnimFrame = (function() {
+  return window.requestAnimationFrame ||
+         window.webkitRequestAnimationFrame ||
+         window.mozRequestAnimationFrame ||
+         window.oRequestAnimationFrame ||
+         window.msRequestAnimationFrame ||
+         function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
+           return window.setTimeout(callback, 1000/60);
+         };
+})();
+
+/**
+ * Provides cancelRequestAnimationFrame in a cross browser way.
+ */
+window.cancelRequestAnimFrame = (function() {
+  return window.cancelAnimationFrame ||
+         window.webkitCancelRequestAnimationFrame ||
+         window.mozCancelRequestAnimationFrame ||
+         window.oCancelRequestAnimationFrame ||
+         window.msCancelRequestAnimationFrame ||
+         window.clearTimeout;
+})();
+
+// video support utility functions
+function supports_video() {
+  return !!document.createElement('video').canPlayType;
+}
+
+function supports_h264_baseline_video() {
+  if (!supports_video()) { return false; }
+  var v = document.createElement("video");
+  return v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
+}
+
+function supports_ogg_theora_video() {
+  if (!supports_video()) { return false; }
+  var v = document.createElement("video");
+  return v.canPlayType('video/ogg; codecs="theora, vorbis"');
+}

BIN
static/face/mp3/alive_eye.mp3


BIN
static/face/mp3/alive_head.mp3


BIN
static/face/mp3/alive_mouse.mp3


BIN
static/fonts/AlibabaPuHuiTi-3-55-Regular.ttf


BIN
static/fonts/AlibabaPuHuiTi-3-65-Medium.ttf


BIN
static/images/banner2.png


BIN
static/images/card.jpg


BIN
static/images/head.png


BIN
static/images/lr.png


BIN
static/images/sfsb.png


BIN
static/images/shqt.png


+ 107 - 0
static/index - 副本.html

@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="UTF-8">
+	<title>H5摄像头(新版浏览器https)(兼容老版浏览器)</title>
+</head>
+<body>
+	<!-- 说明:将网页更改为https访问才行 否者报错:
+	NotSupportedError Only secure origins are allowed (see: https://goo.gl/Y0ZkNV). -->
+	
+	<!-- video用于显示媒体设备的视频流,自动播放;属性:https://zhuanlan.zhihu.com/p/535917105 -->
+	<video id="video" autoplay webkit-playsinline="true" playsinline="true"
+		style="width: 800px;height: 400px;transform: rotateY(180deg);display:none;"></video>
+		<!-- transform: rotateY(180deg); 镜像解决 -->
+	<!-- 可以通过画布canvas渲染,获取使用默认的video也行 -->
+	<canvas id="canvas" width="500" height="400" style="transform: rotateY(180deg);"></canvas>
+	<!-- 拍照按钮 -->
+	<div><button id="capture" style="color: blue;">拍照</button></div>
+	<!-- 描绘video截图 -->
+	<img id="img" alt="" src="">
+	
+	<script type="text/javascript">
+		var video = document.getElementById("video");
+		var img = document.getElementById("img");
+		var canvas = document.getElementById("canvas");
+		var context = canvas.getContext("2d");
+		var v_t_w = 500, v_t_h = 400;
+		
+		// 访问用户媒体设备的兼容方法
+		function getUserMedia(constrains, successFun, errorFun){
+			//like12 modified,20210628,bug,navigator.mediaDevices为空会导致后面卡死
+			if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia){
+				//最新标准API(新版浏览器https)
+				navigator.mediaDevices.getUserMedia(constrains).then(successFun).catch(errorFun);
+			} else if (navigator.webkitGetUserMedia){
+				//like12 modified,20210628,不是这种调用方法 应该为后者
+				//webkit内核浏览器(老版浏览器)
+				//navigator.webkitGetUserMedia(constrains).then(successFun).catch(errorFun);
+				navigator.webkitGetUserMedia({ "video": true }, successFun, errorFun);
+			} else if (navigator.mozGetUserMedia){
+				//Firefox浏览器
+				navagator.mozGetUserMedia(constrains).then(successFun).catch(errorFun);
+			} else if (navigator.getUserMedia){
+				//旧版API
+				navigator.getUserMedia(constrains).then(successFun).catch(errorFun);
+			}
+		}
+		
+		// 成功的回调函数
+		function successFun(stream){
+			//like12 modifed,20210618,Chrome升级后,新版本不再支持该用法
+			//摄像头视频流显示报错Failed to execute 'createObjectURL' on 'URL'
+			//研究即时通信的过程中需要调用摄像头,发现报错,原来是谷歌弃用了这个方法,根据官方提示修改即可
+			//所以原先的代码:video.src = URL.createObjectURL(stream);
+			//需要被修改为:video.srcObject = stream;
+			//(新版浏览器https)
+			if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia){
+				video.srcObject = stream;
+			}
+			//(老版浏览器)
+			else{
+				//兼容webkit内核浏览器
+				var CompatibleURL = window.URL || window.webkitURL;
+				//将视频流设置为video元素的源
+				//此处的代码将会报错  解决的办法是将video的srcObject属性指向stream即可
+				video.src = CompatibleURL.createObjectURL(stream);
+			}
+			// 播放视频
+			video.play();
+			// 可以通过画布canvas渲染,获取使用默认的video也行
+			setInterval(function(){
+				canvas.width = v_t_w;
+		        canvas.height = v_t_h;
+				context.drawImage(video, 0, 0, v_t_w, v_t_h);
+			}, 10);
+		}
+		
+		// 异常的回调函数
+		function errorFun(error){
+			console.log("访问用户媒体设备失败:", error.name, error.message);
+			alert("访问用户媒体设备失败:" + error.name + " " + error.message);
+		}
+		
+		// 注册拍照按钮的单击事件-截图
+		document.getElementById("capture").addEventListener("click",function(){
+			var base64Img = canvas.toDataURL('image/jpg');
+			//var base64 = canvas.toDataURL('image/jpeg',0.5);// 图片质量0.5
+			img.src = base64Img;
+		});
+		
+		// 开始调用摄像头
+		//like12 modified,20210628,bug,navigator.mediaDevices为空会导致后面卡死
+		if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia
+				|| navigator.getUserMedia
+				|| navigator.webkitGetUserMedia
+				|| navigator.mozGetUserMedia){
+			// 调用用户媒体设备,访问摄像头
+			getUserMedia({
+				video:{width:v_t_w, height:v_t_h}
+			}, successFun, errorFun);
+		} else {
+			alert("你的浏览器不支持访问用户媒体设备");
+		}
+		
+	</script>
+</body>
+</html>

+ 107 - 0
static/index.html

@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="UTF-8">
+	<title>H5摄像头(新版浏览器https)(兼容老版浏览器)</title>
+</head>
+<body>
+	<!-- 说明:将网页更改为https访问才行 否者报错:
+	NotSupportedError Only secure origins are allowed (see: https://goo.gl/Y0ZkNV). -->
+	
+	<!-- video用于显示媒体设备的视频流,自动播放;属性:https://zhuanlan.zhihu.com/p/535917105 -->
+	<video id="video" autoplay webkit-playsinline="true" playsinline="true"
+		style="width: 800px;height: 400px;transform: rotateY(180deg);display:none;"></video>
+		<!-- transform: rotateY(180deg); 镜像解决 -->
+	<!-- 可以通过画布canvas渲染,获取使用默认的video也行 -->
+	<canvas id="canvas" width="500" height="400" style="transform: rotateY(180deg);"></canvas>
+	<!-- 拍照按钮 -->
+	<div><button id="capture" style="color: blue;">拍照</button></div>
+	<!-- 描绘video截图 -->
+	<img id="img" alt="" src="">
+	
+	<script type="text/javascript">
+		var video = document.getElementById("video");
+		var img = document.getElementById("img");
+		var canvas = document.getElementById("canvas");
+		var context = canvas.getContext("2d");
+		var v_t_w = 500, v_t_h = 400;
+		
+		// 访问用户媒体设备的兼容方法
+		function getUserMedia(constrains, successFun, errorFun){
+			//like12 modified,20210628,bug,navigator.mediaDevices为空会导致后面卡死
+			if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia){
+				//最新标准API(新版浏览器https)
+				navigator.mediaDevices.getUserMedia(constrains).then(successFun).catch(errorFun);
+			} else if (navigator.webkitGetUserMedia){
+				//like12 modified,20210628,不是这种调用方法 应该为后者
+				//webkit内核浏览器(老版浏览器)
+				//navigator.webkitGetUserMedia(constrains).then(successFun).catch(errorFun);
+				navigator.webkitGetUserMedia({ "video": true }, successFun, errorFun);
+			} else if (navigator.mozGetUserMedia){
+				//Firefox浏览器
+				navagator.mozGetUserMedia(constrains).then(successFun).catch(errorFun);
+			} else if (navigator.getUserMedia){
+				//旧版API
+				navigator.getUserMedia(constrains).then(successFun).catch(errorFun);
+			}
+		}
+		
+		// 成功的回调函数
+		function successFun(stream){
+			//like12 modifed,20210618,Chrome升级后,新版本不再支持该用法
+			//摄像头视频流显示报错Failed to execute 'createObjectURL' on 'URL'
+			//研究即时通信的过程中需要调用摄像头,发现报错,原来是谷歌弃用了这个方法,根据官方提示修改即可
+			//所以原先的代码:video.src = URL.createObjectURL(stream);
+			//需要被修改为:video.srcObject = stream;
+			//(新版浏览器https)
+			if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia){
+				video.srcObject = stream;
+			}
+			//(老版浏览器)
+			else{
+				//兼容webkit内核浏览器
+				var CompatibleURL = window.URL || window.webkitURL;
+				//将视频流设置为video元素的源
+				//此处的代码将会报错  解决的办法是将video的srcObject属性指向stream即可
+				video.src = CompatibleURL.createObjectURL(stream);
+			}
+			// 播放视频
+			video.play();
+			// 可以通过画布canvas渲染,获取使用默认的video也行
+			setInterval(function(){
+				canvas.width = v_t_w;
+		        canvas.height = v_t_h;
+				context.drawImage(video, 0, 0, v_t_w, v_t_h);
+			}, 10);
+		}
+		
+		// 异常的回调函数
+		function errorFun(error){
+			console.log("访问用户媒体设备失败:", error.name, error.message);
+			alert("访问用户媒体设备失败:" + error.name + " " + error.message);
+		}
+		
+		// 注册拍照按钮的单击事件-截图
+		document.getElementById("capture").addEventListener("click",function(){
+			var base64Img = canvas.toDataURL('image/jpg');
+			//var base64 = canvas.toDataURL('image/jpeg',0.5);// 图片质量0.5
+			img.src = base64Img;
+		});
+		
+		// 开始调用摄像头
+		//like12 modified,20210628,bug,navigator.mediaDevices为空会导致后面卡死
+		if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia
+				|| navigator.getUserMedia
+				|| navigator.webkitGetUserMedia
+				|| navigator.mozGetUserMedia){
+			// 调用用户媒体设备,访问摄像头
+			getUserMedia({
+				video:{width:v_t_w, height:v_t_h}
+			}, successFun, errorFun);
+		} else {
+			alert("你的浏览器不支持访问用户媒体设备");
+		}
+		
+	</script>
+</body>
+</html>

BIN
static/logo.png


+ 10 - 0
uni.promisify.adaptor.js

@@ -0,0 +1,10 @@
+uni.addInterceptor({
+  returnValue (res) {
+    if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
+      return res;
+    }
+    return new Promise((resolve, reject) => {
+      res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));
+    });
+  },
+});

+ 76 - 0
uni.scss

@@ -0,0 +1,76 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;

+ 26 - 0
uni_modules/uni-card/changelog.md

@@ -0,0 +1,26 @@
+## 1.3.1(2021-12-20)
+- 修复 在vue页面下略缩图显示不正常的bug
+## 1.3.0(2021-11-19)
+- 重构插槽的用法 ,header 替换为 title 
+- 新增 actions 插槽
+- 新增 cover 封面图属性和插槽
+- 新增 padding 内容默认内边距离
+- 新增 margin 卡片默认外边距离
+- 新增 spacing 卡片默认内边距
+- 新增 shadow 卡片阴影属性
+- 取消 mode 属性,可使用组合插槽代替
+- 取消 note 属性 ,使用actions插槽代替
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
+## 1.2.1(2021-07-30)
+- 优化 vue3下事件警告的问题
+## 1.2.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.8(2021-07-01)
+- 优化 图文卡片无图片加载时,提供占位图标
+- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
+- 修复 thumbnail 不存在仍然占位的 bug
+## 1.1.7(2021-05-12)
+- 新增 组件示例地址
+## 1.1.6(2021-02-04)
+- 调整为uni_modules目录规范

+ 270 - 0
uni_modules/uni-card/components/uni-card/uni-card.vue

@@ -0,0 +1,270 @@
+<template>
+	<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
+		:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
+		<!-- 封面 -->
+		<slot name="cover">
+			<view v-if="cover" class="uni-card__cover">
+				<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
+			</view>
+		</slot>
+		<slot name="title">
+			<view v-if="title || extra" class="uni-card__header">
+				<!-- 卡片标题 -->
+				<view class="uni-card__header-box" @click="onClick('title')">
+					<view v-if="thumbnail" class="uni-card__header-avatar">
+						<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
+					</view>
+					<view class="uni-card__header-content">
+						<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
+						<text v-if="title&&subTitle"
+							class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
+					</view>
+				</view>
+				<view class="uni-card__header-extra" @click="onClick('extra')">
+					<text class="uni-card__header-extra-text">{{ extra }}</text>
+				</view>
+			</view>
+		</slot>
+		<!-- 卡片内容 -->
+		<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
+			<slot></slot>
+		</view>
+		<view class="uni-card__actions" @click="onClick('actions')">
+			<slot name="actions"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Card 卡片
+	 * @description 卡片视图组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=22
+	 * @property {String} title 标题文字
+	 * @property {String} subTitle 副标题
+	 * @property {Number} padding 内容内边距
+	 * @property {Number} margin 卡片外边距
+	 * @property {Number} spacing 卡片内边距
+	 * @property {String} extra 标题额外信息
+	 * @property {String} cover 封面图(本地路径需要引入)
+	 * @property {String} thumbnail 标题左侧缩略图
+	 * @property {Boolean} is-full = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
+	 * @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
+	 * @property {String} shadow 卡片阴影
+	 * @property {Boolean} border 卡片边框
+	 * @event {Function} click 点击 Card 触发事件
+	 */
+	export default {
+		name: 'UniCard',
+		emits: ['click'],
+		props: {
+			title: {
+				type: String,
+				default: ''
+			},
+			subTitle: {
+				type: String,
+				default: ''
+			},
+			padding: {
+				type: String,
+				default: '10px'
+			},
+			margin: {
+				type: String,
+				default: '15px'
+			},
+			spacing: {
+				type: String,
+				default: '0 10px'
+			},
+			extra: {
+				type: String,
+				default: ''
+			},
+			cover: {
+				type: String,
+				default: ''
+			},
+			thumbnail: {
+				type: String,
+				default: ''
+			},
+			isFull: {
+				// 内容区域是否通栏
+				type: Boolean,
+				default: false
+			},
+			isShadow: {
+				// 是否开启阴影
+				type: Boolean,
+				default: true
+			},
+			shadow: {
+				type: String,
+				default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
+			},
+			border: {
+				type: Boolean,
+				default: true
+			}
+		},
+		methods: {
+			onClick(type) {
+				this.$emit('click', type)
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	$uni-border-3: #EBEEF5 !default;
+	$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
+	$uni-main-color: #3a3a3a !default;
+	$uni-base-color: #6a6a6a !default;
+	$uni-secondary-color: #909399 !default;
+	$uni-spacing-sm: 8px !default;
+	$uni-border-color:$uni-border-3;
+	$uni-shadow: $uni-shadow-base;
+	$uni-card-title: 15px;
+	$uni-cart-title-color:$uni-main-color;
+	$uni-card-subtitle: 12px;
+	$uni-cart-subtitle-color:$uni-secondary-color;
+	$uni-card-spacing: 10px;
+	$uni-card-content-color: $uni-base-color;
+
+	.uni-card {
+		margin: $uni-card-spacing;
+		padding: 0 $uni-spacing-sm;
+		border-radius: 4px;
+		overflow: hidden;
+		font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
+		background-color: #fff;
+		flex: 1;
+
+		.uni-card__cover {
+			position: relative;
+			margin-top: $uni-card-spacing;
+			flex-direction: row;
+			overflow: hidden;
+			border-radius: 4px;
+			.uni-card__cover-image {
+				flex: 1;
+				// width: 100%;
+				/* #ifndef APP-PLUS */
+				vertical-align: middle;
+				/* #endif */
+			}
+		}
+
+		.uni-card__header {
+			display: flex;
+			border-bottom: 1px $uni-border-color solid;
+			flex-direction: row;
+			align-items: center;
+			padding: $uni-card-spacing;
+			overflow: hidden;
+
+			.uni-card__header-box {
+				/* #ifndef APP-NVUE */
+				display: flex;
+				/* #endif */
+				flex: 1;
+				flex-direction: row;
+				align-items: center;
+				overflow: hidden;
+			}
+
+			.uni-card__header-avatar {
+				width: 40px;
+				height: 40px;
+				overflow: hidden;
+				border-radius: 5px;
+				margin-right: $uni-card-spacing;
+				.uni-card__header-avatar-image {
+					flex: 1;
+					width: 40px;
+					height: 40px;
+				}
+			}
+
+			.uni-card__header-content {
+				/* #ifndef APP-NVUE */
+				display: flex;
+				/* #endif */
+				flex-direction: column;
+				justify-content: center;
+				flex: 1;
+				// height: 40px;
+				overflow: hidden;
+
+				.uni-card__header-content-title {
+					font-size: $uni-card-title;
+					color: $uni-cart-title-color;
+					// line-height: 22px;
+				}
+
+				.uni-card__header-content-subtitle {
+					font-size: $uni-card-subtitle;
+					margin-top: 5px;
+					color: $uni-cart-subtitle-color;
+				}
+			}
+
+			.uni-card__header-extra {
+				line-height: 12px;
+
+				.uni-card__header-extra-text {
+					font-size: 12px;
+					color: $uni-cart-subtitle-color;
+				}
+			}
+		}
+
+		.uni-card__content {
+			padding: $uni-card-spacing;
+			font-size: 14px;
+			color: $uni-card-content-color;
+			line-height: 22px;
+		}
+
+		.uni-card__actions {
+			font-size: 12px;
+		}
+	}
+
+	.uni-card--border {
+		border: 1px solid $uni-border-color;
+	}
+
+	.uni-card--shadow {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		box-shadow: $uni-shadow;
+		/* #endif */
+	}
+
+	.uni-card--full {
+		margin: 0;
+		border-left-width: 0;
+		border-left-width: 0;
+		border-radius: 0;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-card--full:after {
+		border-radius: 0;
+	}
+
+	/* #endif */
+	.uni-ellipsis {
+		/* #ifndef APP-NVUE */
+		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		lines: 1;
+		/* #endif */
+	}
+</style>

+ 90 - 0
uni_modules/uni-card/package.json

@@ -0,0 +1,90 @@
+{
+  "id": "uni-card",
+  "displayName": "uni-card 卡片",
+  "version": "1.3.1",
+  "description": "Card 组件,提供常见的卡片样式。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "card",
+    "",
+    "卡片"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uni-icons",
+			"uni-scss"
+		],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 12 - 0
uni_modules/uni-card/readme.md

@@ -0,0 +1,12 @@
+
+
+## Card 卡片
+> **组件名:uni-card**
+> 代码块: `uCard`
+
+卡片视图组件。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
+
+

+ 47 - 0
uni_modules/uni-data-checkbox/changelog.md

@@ -0,0 +1,47 @@
+## 1.0.4(2024-01-27)
+- 修复 修复错别字chagne为change
+## 1.0.3(2022-09-16)
+- 可以使用 uni-scss 控制主题色
+## 1.0.2(2022-06-30)
+- 优化 在 uni-forms 中的依赖注入方式
+## 1.0.1(2022-02-07)
+- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
+## 1.0.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
+## 0.2.5(2021-08-23)
+- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
+## 0.2.4(2021-08-17)
+- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题
+## 0.2.3(2021-08-11)
+- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
+## 0.2.2(2021-07-30)
+- 优化 在uni-forms组件,与label不对齐的问题
+## 0.2.1(2021-07-27)
+- 修复 单选默认值为0不能选中的Bug
+## 0.2.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 0.1.11(2021-07-06)
+- 优化 删除无用日志
+## 0.1.10(2021-07-05)
+- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
+## 0.1.9(2021-07-05)
+- 修复 nvue 黑框样式问题
+## 0.1.8(2021-06-28)
+- 修复 selectedTextColor 属性不生效的Bug
+## 0.1.7(2021-06-02)
+- 新增 map 属性,可以方便映射text/value属性
+## 0.1.6(2021-05-26)
+- 修复 不关联服务空间的情况下组件报错的Bug
+## 0.1.5(2021-05-12)
+- 新增 组件示例地址
+## 0.1.4(2021-04-09)
+- 修复 nvue 下无法选中的问题
+## 0.1.3(2021-03-22)
+- 新增 disabled属性
+## 0.1.2(2021-02-24)
+- 优化 默认颜色显示
+## 0.1.1(2021-02-24)
+- 新增 支持nvue
+## 0.1.0(2021-02-18)
+- “暂无数据”显示居中

+ 821 - 0
uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue

@@ -0,0 +1,821 @@
+<template>
+	<view class="uni-data-checklist" :style="{'margin-top':isTop+'px'}">
+		<template v-if="!isLocal">
+			<view class="uni-data-loading">
+				<uni-load-more v-if="!mixinDatacomErrorMessage" status="loading" iconType="snow" :iconSize="18" :content-text="contentText"></uni-load-more>
+				<text v-else>{{mixinDatacomErrorMessage}}</text>
+			</view>
+		</template>
+		<template v-else>
+			<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list' || wrap}" @change="change">
+				<label class="checklist-box" :class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
+				 :style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
+					<checkbox class="hidden" hidden :disabled="disabled || !!item.disabled" :value="item[map.value]+''" :checked="item.selected" />
+					<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="checkbox__inner"  :style="item.styleIcon">
+						<view class="checkbox__inner-icon"></view>
+					</view>
+					<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
+						<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
+						<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :style="item.styleBackgroud"></view>
+					</view>
+				</label>
+			</checkbox-group>
+			<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="change">
+				<!-- -->
+				<label class="checklist-box" :class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
+				 :style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
+					<radio class="hidden" hidden :disabled="disabled || item.disabled" :value="item[map.value]+''" :checked="item.selected" />
+					<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner"
+					 :style="item.styleBackgroud">
+						<view class="radio__inner-icon" :style="item.styleIcon"></view>
+					</view>
+					<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
+						<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
+						<view v-if="mode === 'list' && icon === 'right'" :style="item.styleRightIcon" class="checkobx__list"></view>
+					</view>
+				</label>
+			</radio-group>
+		</template>
+	</view>
+</template>
+
+<script>
+	/**
+	 * DataChecklist 数据选择器
+	 * @description 通过数据渲染 checkbox 和 radio
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
+	 * @property {String} mode = [default| list | button | tag] 显示模式
+	 * @value default  	默认横排模式
+	 * @value list		列表模式
+	 * @value button	按钮模式
+	 * @value tag 		标签模式
+	 * @property {Boolean} multiple = [true|false] 是否多选
+	 * @property {Array|String|Number} value 默认值
+	 * @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
+	 * @property {Number|String} min 最小选择个数 ,multiple为true时生效
+	 * @property {Number|String} max 最大选择个数 ,multiple为true时生效
+	 * @property {Boolean} wrap 是否换行显示
+	 * @property {String} icon = [left|right]  list 列表模式下icon显示位置
+	 * @property {Boolean} selectedColor 选中颜色
+	 * @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效
+	 * @property {Boolean} selectedTextColor 选中文本颜色,如不填写则自动显示
+	 * @property {Object} map 字段映射, 默认 map={text:'text',value:'value'}
+	 * @value left 左侧显示
+	 * @value right 右侧显示
+	 * @event {Function} change  选中发生变化触发
+	 */
+
+	export default {
+		name: 'uniDataChecklist',
+		mixins: [uniCloud.mixinDatacom || {}],
+		emits:['input','update:modelValue','change'],
+		props: {
+			mode: {
+				type: String,
+				default: 'default'
+			},
+
+			multiple: {
+				type: Boolean,
+				default: false
+			},
+			value: {
+				type: [Array, String, Number],
+				default () {
+					return ''
+				}
+			},
+			// TODO vue3
+			modelValue: {
+				type: [Array, String, Number],
+				default() {
+					return '';
+				}
+			},
+			localdata: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			min: {
+				type: [Number, String],
+				default: ''
+			},
+			max: {
+				type: [Number, String],
+				default: ''
+			},
+			wrap: {
+				type: Boolean,
+				default: false
+			},
+			icon: {
+				type: String,
+				default: 'left'
+			},
+			selectedColor: {
+				type: String,
+				default: ''
+			},
+			selectedTextColor: {
+				type: String,
+				default: ''
+			},
+			emptyText:{
+				type: String,
+				default: '暂无数据'
+			},
+			disabled:{
+				type: Boolean,
+				default: false
+			},
+			map:{
+				type: Object,
+				default(){
+					return {
+						text:'text',
+						value:'value'
+					}
+				}
+			}
+		},
+		watch: {
+			localdata: {
+				handler(newVal) {
+					this.range = newVal
+					this.dataList = this.getDataList(this.getSelectedValue(newVal))
+				},
+				deep: true
+			},
+			mixinDatacomResData(newVal) {
+				this.range = newVal
+				this.dataList = this.getDataList(this.getSelectedValue(newVal))
+			},
+			value(newVal) {
+				this.dataList = this.getDataList(newVal)
+				// fix by mehaotian is_reset 在 uni-forms 中定义
+				// if(!this.is_reset){
+				// 	this.is_reset = false
+				// 	this.formItem && this.formItem.setValue(newVal)
+				// }
+			},
+			modelValue(newVal) {
+				this.dataList = this.getDataList(newVal);
+				// if(!this.is_reset){
+				// 	this.is_reset = false
+				// 	this.formItem && this.formItem.setValue(newVal)
+				// }
+			}
+		},
+		data() {
+			return {
+				dataList: [],
+				range: [],
+				contentText: {
+					contentdown: '查看更多',
+					contentrefresh: '加载中',
+					contentnomore: '没有更多'
+				},
+				isLocal:true,
+				styles: {
+					selectedColor: '#2979ff',
+					selectedTextColor: '#666',
+				},
+				isTop:0
+			};
+		},
+		computed:{
+			dataValue(){
+				if(this.value === '')return this.modelValue
+				if(this.modelValue === '') return this.value
+				return this.value
+			}
+		},
+		created() {
+			// this.form = this.getForm('uniForms')
+			// this.formItem = this.getForm('uniFormsItem')
+			// this.formItem && this.formItem.setValue(this.value)
+
+			// if (this.formItem) {
+			// 	this.isTop = 6
+			// 	if (this.formItem.name) {
+			// 		// 如果存在name添加默认值,否则formData 中不存在这个字段不校验
+			// 		if(!this.is_reset){
+			// 			this.is_reset = false
+			// 			this.formItem.setValue(this.dataValue)
+			// 		}
+			// 		this.rename = this.formItem.name
+			// 		this.form.inputChildrens.push(this)
+			// 	}
+			// }
+
+			if (this.localdata && this.localdata.length !== 0) {
+				this.isLocal = true
+				this.range = this.localdata
+				this.dataList = this.getDataList(this.getSelectedValue(this.range))
+			} else {
+				if (this.collection) {
+					this.isLocal = false
+					this.loadData()
+				}
+			}
+		},
+		methods: {
+			loadData() {
+				this.mixinDatacomGet().then(res=>{
+					this.mixinDatacomResData = res.result.data
+					if(this.mixinDatacomResData.length === 0){
+						this.isLocal = false
+						this.mixinDatacomErrorMessage = this.emptyText
+					}else{
+						this.isLocal = true
+					}
+				}).catch(err=>{
+					this.mixinDatacomErrorMessage = err.message
+				})
+			},
+			/**
+			 * 获取父元素实例
+			 */
+			getForm(name = 'uniForms') {
+				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;
+			},
+			change(e) {
+				const values = e.detail.value
+
+				let detail = {
+					value: [],
+					data: []
+				}
+
+				if (this.multiple) {
+					this.range.forEach(item => {
+
+						if (values.includes(item[this.map.value] + '')) {
+							detail.value.push(item[this.map.value])
+							detail.data.push(item)
+						}
+					})
+				} else {
+					const range = this.range.find(item => (item[this.map.value] + '') === values)
+					if (range) {
+						detail = {
+							value: range[this.map.value],
+							data: range
+						}
+					}
+				}
+				// this.formItem && this.formItem.setValue(detail.value)
+				// TODO 兼容 vue2
+				this.$emit('input', detail.value);
+				// // TOTO 兼容 vue3
+				this.$emit('update:modelValue', detail.value);
+				this.$emit('change', {
+					detail
+				})
+				if (this.multiple) {
+					// 如果 v-model 没有绑定 ,则走内部逻辑
+					// if (this.value.length === 0) {
+					this.dataList = this.getDataList(detail.value, true)
+					// }
+				} else {
+					this.dataList = this.getDataList(detail.value)
+				}
+			},
+
+			/**
+			 * 获取渲染的新数组
+			 * @param {Object} value 选中内容
+			 */
+			getDataList(value) {
+				// 解除引用关系,破坏原引用关系,避免污染源数据
+				let dataList = JSON.parse(JSON.stringify(this.range))
+				let list = []
+				if (this.multiple) {
+					if (!Array.isArray(value)) {
+						value = []
+					}
+				}
+				dataList.forEach((item, index) => {
+					item.disabled = item.disable || item.disabled || false
+					if (this.multiple) {
+						if (value.length > 0) {
+							let have = value.find(val => val === item[this.map.value])
+							item.selected = have !== undefined
+						} else {
+							item.selected = false
+						}
+					} else {
+						item.selected = value === item[this.map.value]
+					}
+
+					list.push(item)
+				})
+				return this.setRange(list)
+			},
+			/**
+			 * 处理最大最小值
+			 * @param {Object} list
+			 */
+			setRange(list) {
+				let selectList = list.filter(item => item.selected)
+				let min = Number(this.min) || 0
+				let max = Number(this.max) || ''
+				list.forEach((item, index) => {
+					if (this.multiple) {
+						if (selectList.length <= min) {
+							let have = selectList.find(val => val[this.map.value] === item[this.map.value])
+							if (have !== undefined) {
+								item.disabled = true
+							}
+						}
+
+						if (selectList.length >= max && max !== '') {
+							let have = selectList.find(val => val[this.map.value] === item[this.map.value])
+							if (have === undefined) {
+								item.disabled = true
+							}
+						}
+					}
+					this.setStyles(item, index)
+					list[index] = item
+				})
+				return list
+			},
+			/**
+			 * 设置 class
+			 * @param {Object} item
+			 * @param {Object} index
+			 */
+			setStyles(item, index) {
+				//  设置自定义样式
+				item.styleBackgroud = this.setStyleBackgroud(item)
+				item.styleIcon = this.setStyleIcon(item)
+				item.styleIconText = this.setStyleIconText(item)
+				item.styleRightIcon = this.setStyleRightIcon(item)
+			},
+
+			/**
+			 * 获取选中值
+			 * @param {Object} range
+			 */
+			getSelectedValue(range) {
+				if (!this.multiple) return this.dataValue
+				let selectedArr = []
+				range.forEach((item) => {
+					if (item.selected) {
+						selectedArr.push(item[this.map.value])
+					}
+				})
+				return this.dataValue.length > 0 ? this.dataValue : selectedArr
+			},
+
+			/**
+			 * 设置背景样式
+			 */
+			setStyleBackgroud(item) {
+				let styles = {}
+				let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
+				if (this.selectedColor) {
+					if (this.mode !== 'list') {
+						styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
+					}
+					if (this.mode === 'tag') {
+						styles['background-color'] = item.selected? selectedColor:'#f5f5f5'
+					}
+				}
+				let classles = ''
+				for (let i in styles) {
+					classles += `${i}:${styles[i]};`
+				}
+				return classles
+			},
+			setStyleIcon(item) {
+				let styles = {}
+				let classles = ''
+				if (this.selectedColor) {
+					let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
+					styles['background-color'] = item.selected?selectedColor:'#fff'
+					styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
+					
+					if(!item.selected && item.disabled){
+						styles['background-color'] = '#F2F6FC'
+						styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
+					}
+				}
+				for (let i in styles) {
+					classles += `${i}:${styles[i]};`
+				}
+				return classles
+			},
+			setStyleIconText(item) {
+				let styles = {}
+				let classles = ''
+				if (this.selectedColor) {
+					let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
+					if (this.mode === 'tag') {
+						styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666'
+					} else {
+						styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:selectedColor):'#666'
+					}
+					if(!item.selected && item.disabled){
+						styles.color = '#999'
+					}
+				}
+				for (let i in styles) {
+					classles += `${i}:${styles[i]};`
+				}
+				return classles
+			},
+			setStyleRightIcon(item) {
+				let styles = {}
+				let classles = ''
+				if (this.mode === 'list') {
+					styles['border-color'] = item.selected?this.styles.selectedColor:'#DCDFE6'
+				}
+				for (let i in styles) {
+					classles += `${i}:${styles[i]};`
+				}
+
+				return classles
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	$uni-primary: #2979ff !default;
+	$border-color: #DCDFE6;
+	$disable:0.4;
+
+	@mixin flex {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+	}
+
+	.uni-data-loading {
+		@include flex;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 36px;
+		padding-left: 10px;
+		color: #999;
+	}
+
+	.uni-data-checklist {
+		position: relative;
+		z-index: 0;
+		flex: 1;
+		// 多选样式
+		.checklist-group {
+			@include flex;
+			flex-direction: row;
+			flex-wrap: wrap;
+
+			&.is-list {
+				flex-direction: column;
+			}
+
+			.checklist-box {
+				@include flex;
+				flex-direction: row;
+				align-items: center;
+				position: relative;
+				margin: 5px 0;
+				margin-right: 25px;
+
+				.hidden {
+					position: absolute;
+					opacity: 0;
+				}
+
+				// 文字样式
+				.checklist-content {
+					@include flex;
+					flex: 1;
+					flex-direction: row;
+					align-items: center;
+					justify-content: space-between;
+					.checklist-text {
+						font-size: 14px;
+						color: #666;
+						margin-left: 5px;
+						line-height: 14px;
+					}
+
+					.checkobx__list {
+						border-right-width: 1px;
+						border-right-color: #007aff;
+						border-right-style: solid;
+						border-bottom-width:1px;
+						border-bottom-color: #007aff;
+						border-bottom-style: solid;
+						height: 12px;
+						width: 6px;
+						left: -5px;
+						transform-origin: center;
+						transform: rotate(45deg);
+						opacity: 0;
+					}
+				}
+
+				// 多选样式
+				.checkbox__inner {
+					/* #ifndef APP-NVUE */
+					flex-shrink: 0;
+					box-sizing: border-box;
+					/* #endif */
+					position: relative;
+					width: 16px;
+					height: 16px;
+					border: 1px solid $border-color;
+					border-radius: 4px;
+					background-color: #fff;
+					z-index: 1;
+					.checkbox__inner-icon {
+						position: absolute;
+						/* #ifdef APP-NVUE */
+						top: 2px;
+						/* #endif */
+						/* #ifndef APP-NVUE */
+						top: 1px;
+						/* #endif */
+						left: 5px;
+						height: 8px;
+						width: 4px;
+						border-right-width: 1px;
+						border-right-color: #fff;
+						border-right-style: solid;
+						border-bottom-width:1px ;
+						border-bottom-color: #fff;
+						border-bottom-style: solid;
+						opacity: 0;
+						transform-origin: center;
+						transform: rotate(40deg);
+					}
+				}
+
+				// 单选样式
+				.radio__inner {
+					@include flex;
+					/* #ifndef APP-NVUE */
+					flex-shrink: 0;
+					box-sizing: border-box;
+					/* #endif */
+					justify-content: center;
+					align-items: center;
+					position: relative;
+					width: 16px;
+					height: 16px;
+					border: 1px solid $border-color;
+					border-radius: 16px;
+					background-color: #fff;
+					z-index: 1;
+
+					.radio__inner-icon {
+						width: 8px;
+						height: 8px;
+						border-radius: 10px;
+						opacity: 0;
+					}
+				}
+
+				// 默认样式
+				&.is--default {
+
+					// 禁用
+					&.is-disable {
+						/* #ifdef H5 */
+						cursor: not-allowed;
+						/* #endif */
+						.checkbox__inner {
+							background-color: #F2F6FC;
+							border-color: $border-color;
+							/* #ifdef H5 */
+							cursor: not-allowed;
+							/* #endif */
+						}
+
+						.radio__inner {
+							background-color: #F2F6FC;
+							border-color: $border-color;
+						}
+						.checklist-text {
+							color: #999;
+						}
+					}
+
+					// 选中
+					&.is-checked {
+						.checkbox__inner {
+							border-color: $uni-primary;
+							background-color: $uni-primary;
+
+							.checkbox__inner-icon {
+								opacity: 1;
+								transform: rotate(45deg);
+							}
+						}
+						.radio__inner {
+							border-color: $uni-primary;
+							.radio__inner-icon {
+								opacity: 1;
+								background-color: $uni-primary;
+							}
+						}
+						.checklist-text {
+							color: $uni-primary;
+						}
+						// 选中禁用
+						&.is-disable {
+							.checkbox__inner {
+								opacity: $disable;
+							}
+
+							.checklist-text {
+								opacity: $disable;
+							}
+							.radio__inner {
+								opacity: $disable;
+							}
+						}
+					}
+				}
+
+				// 按钮样式
+				&.is--button {
+					margin-right: 10px;
+					padding: 5px 10px;
+					border: 1px $border-color solid;
+					border-radius: 3px;
+					transition: border-color 0.2s;
+
+					// 禁用
+					&.is-disable {
+						/* #ifdef H5 */
+						cursor: not-allowed;
+						/* #endif */
+						border: 1px #eee solid;
+						opacity: $disable;
+						.checkbox__inner {
+							background-color: #F2F6FC;
+							border-color: $border-color;
+							/* #ifdef H5 */
+							cursor: not-allowed;
+							/* #endif */
+						}
+						.radio__inner {
+							background-color: #F2F6FC;
+							border-color: $border-color;
+							/* #ifdef H5 */
+							cursor: not-allowed;
+							/* #endif */
+						}
+						.checklist-text {
+							color: #999;
+						}
+					}
+
+					&.is-checked {
+						border-color: $uni-primary;
+						.checkbox__inner {
+							border-color: $uni-primary;
+							background-color: $uni-primary;
+							.checkbox__inner-icon {
+								opacity: 1;
+								transform: rotate(45deg);
+							}
+						}
+
+						.radio__inner {
+							border-color: $uni-primary;
+
+							.radio__inner-icon {
+								opacity: 1;
+								background-color: $uni-primary;
+							}
+						}
+
+						.checklist-text {
+							color: $uni-primary;
+						}
+
+						// 选中禁用
+						&.is-disable {
+							opacity: $disable;
+						}
+					}
+				}
+
+				// 标签样式
+				&.is--tag {
+					margin-right: 10px;
+					padding: 5px 10px;
+					border: 1px $border-color solid;
+					border-radius: 3px;
+					background-color: #f5f5f5;
+
+					.checklist-text {
+						margin: 0;
+						color: #666;
+					}
+
+					// 禁用
+					&.is-disable {
+						/* #ifdef H5 */
+						cursor: not-allowed;
+						/* #endif */
+						opacity: $disable;
+					}
+
+					&.is-checked {
+						background-color: $uni-primary;
+						border-color: $uni-primary;
+
+						.checklist-text {
+							color: #fff;
+						}
+					}
+				}
+				// 列表样式
+				&.is--list {
+					/* #ifndef APP-NVUE */
+					display: flex;
+					/* #endif */
+					padding: 10px 15px;
+					padding-left: 0;
+					margin: 0;
+
+					&.is-list-border {
+						border-top: 1px #eee solid;
+					}
+
+					// 禁用
+					&.is-disable {
+						/* #ifdef H5 */
+						cursor: not-allowed;
+						/* #endif */
+						.checkbox__inner {
+							background-color: #F2F6FC;
+							border-color: $border-color;
+							/* #ifdef H5 */
+							cursor: not-allowed;
+							/* #endif */
+						}
+						.checklist-text {
+							color: #999;
+						}
+					}
+
+					&.is-checked {
+						.checkbox__inner {
+							border-color: $uni-primary;
+							background-color: $uni-primary;
+
+							.checkbox__inner-icon {
+								opacity: 1;
+								transform: rotate(45deg);
+							}
+						}
+						.radio__inner {
+							.radio__inner-icon {
+								opacity: 1;
+							}
+						}
+						.checklist-text {
+							color: $uni-primary;
+						}
+
+						.checklist-content {
+							.checkobx__list {
+								opacity: 1;
+								border-color: $uni-primary;
+							}
+						}
+
+						// 选中禁用
+						&.is-disable {
+							.checkbox__inner {
+								opacity: $disable;
+							}
+
+							.checklist-text {
+								opacity: $disable;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+</style>

+ 84 - 0
uni_modules/uni-data-checkbox/package.json

@@ -0,0 +1,84 @@
+{
+  "id": "uni-data-checkbox",
+  "displayName": "uni-data-checkbox 数据选择器",
+  "version": "1.0.4",
+  "description": "通过数据驱动的单选框和复选框",
+  "keywords": [
+    "uni-ui",
+    "checkbox",
+    "单选",
+    "多选",
+    "单选多选"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": "^3.1.1"
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-load-more","uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 18 - 0
uni_modules/uni-data-checkbox/readme.md

@@ -0,0 +1,18 @@
+
+
+## DataCheckbox 数据驱动的单选复选框
+> **组件名:uni-data-checkbox**
+> 代码块: `uDataCheckbox`
+
+
+本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括:
+
+1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能
+2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验
+3. 本组件合并了单选多选
+4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性
+
+在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 99 - 0
uni_modules/uni-easyinput/changelog.md

@@ -0,0 +1,99 @@
+## 1.1.10(2024-01-22)
+- 去除 移除无用的log输出
+## 1.1.9(2023-04-11)
+- 修复 vue3 下 keyboardheightchange 事件报错的bug
+## 1.1.8(2023-03-29)
+- 优化 trim 属性默认值
+## 1.1.7(2023-03-29)
+- 新增 cursor-spacing 属性
+## 1.1.6(2023-01-28)
+- 新增 keyboardheightchange 事件,可监听键盘高度变化
+## 1.1.5(2022-11-29)
+- 优化 主题样式
+## 1.1.4(2022-10-27)
+- 修复 props 中背景颜色无默认值的bug
+## 1.1.0(2022-06-30)
+
+- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
+- 新增 clear 事件,点击右侧叉号图标触发
+- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
+- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
+
+## 1.0.5(2022-06-07)
+
+- 优化 clearable 显示策略
+
+## 1.0.4(2022-06-07)
+
+- 优化 clearable 显示策略
+
+## 1.0.3(2022-05-20)
+
+- 修复 关闭图标某些情况下无法取消的 bug
+
+## 1.0.2(2022-04-12)
+
+- 修复 默认值不生效的 bug
+
+## 1.0.1(2022-04-02)
+
+- 修复 value 不能为 0 的 bug
+
+## 1.0.0(2021-11-19)
+
+- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
+
+## 0.1.4(2021-08-20)
+
+- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
+
+## 0.1.3(2021-08-11)
+
+- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
+
+## 0.1.2(2021-07-30)
+
+- 优化 vue3 下事件警告的问题
+
+## 0.1.1
+
+- 优化 errorMessage 属性支持 Boolean 类型
+
+## 0.1.0(2021-07-13)
+
+- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+
+## 0.0.16(2021-06-29)
+
+- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug
+
+## 0.0.15(2021-06-21)
+
+- 修复 passwordIcon 属性拼写错误的 bug
+
+## 0.0.14(2021-06-18)
+
+- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标
+- 修复 confirmType 属性不生效的问题
+
+## 0.0.13(2021-06-04)
+
+- 修复 disabled 状态可清出内容的 bug
+
+## 0.0.12(2021-05-12)
+
+- 新增 组件示例地址
+
+## 0.0.11(2021-05-07)
+
+- 修复 input-border 属性不生效的问题
+
+## 0.0.10(2021-04-30)
+
+- 修复 ios 遮挡文字、显示一半的问题
+
+## 0.0.9(2021-02-05)
+
+- 调整为 uni_modules 目录规范
+- 优化 兼容 nvue 页面

+ 54 - 0
uni_modules/uni-easyinput/components/uni-easyinput/common.js

@@ -0,0 +1,54 @@
+/**
+ * @desc 函数防抖
+ * @param func 目标函数
+ * @param wait 延迟执行毫秒数
+ * @param immediate true - 立即执行, false - 延迟执行
+ */
+export const debounce = function(func, wait = 1000, immediate = true) {
+	let timer;
+	return function() {
+		let context = this,
+			args = arguments;
+		if (timer) clearTimeout(timer);
+		if (immediate) {
+			let callNow = !timer;
+			timer = setTimeout(() => {
+				timer = null;
+			}, wait);
+			if (callNow) func.apply(context, args);
+		} else {
+			timer = setTimeout(() => {
+				func.apply(context, args);
+			}, wait)
+		}
+	}
+}
+/**
+ * @desc 函数节流
+ * @param func 函数
+ * @param wait 延迟执行毫秒数
+ * @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发
+ */
+export const throttle = (func, wait = 1000, type = 1) => {
+	let previous = 0;
+	let timeout;
+	return function() {
+		let context = this;
+		let args = arguments;
+		if (type === 1) {
+			let now = Date.now();
+
+			if (now - previous > wait) {
+				func.apply(context, args);
+				previous = now;
+			}
+		} else if (type === 2) {
+			if (!timeout) {
+				timeout = setTimeout(() => {
+					timeout = null;
+					func.apply(context, args)
+				}, wait)
+			}
+		}
+	}
+}

+ 657 - 0
uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue

@@ -0,0 +1,657 @@
+<template>
+	<view class="uni-easyinput" :class="{ 'uni-easyinput-error': msg }" :style="boxStyle">
+		<view class="uni-easyinput__content" :class="inputContentClass" :style="inputContentStyle">
+			<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')" size="22"></uni-icons>
+			<textarea
+				v-if="type === 'textarea'"
+				class="uni-easyinput__content-textarea"
+				:class="{ 'input-padding': inputBorder }"
+				:name="name"
+				:value="val"
+				:placeholder="placeholder"
+				:placeholderStyle="placeholderStyle"
+				:disabled="disabled"
+				placeholder-class="uni-easyinput__placeholder-class"
+				:maxlength="inputMaxlength"
+				:focus="focused"
+				:autoHeight="autoHeight"
+				:cursor-spacing="cursorSpacing"
+				@input="onInput"
+				@blur="_Blur"
+				@focus="_Focus"
+				@confirm="onConfirm"
+        @keyboardheightchange="onkeyboardheightchange"
+			></textarea>
+			<input
+				v-else
+				:type="type === 'password' ? 'text' : type"
+				class="uni-easyinput__content-input"
+				:style="inputStyle"
+				:name="name"
+				:value="val"
+				:password="!showPassword && type === 'password'"
+				:placeholder="placeholder"
+				:placeholderStyle="placeholderStyle"
+				placeholder-class="uni-easyinput__placeholder-class"
+				:disabled="disabled"
+				:maxlength="inputMaxlength"
+				:focus="focused"
+				:confirmType="confirmType"
+				:cursor-spacing="cursorSpacing"
+				@focus="_Focus"
+				@blur="_Blur"
+				@input="onInput"
+				@confirm="onConfirm"
+        @keyboardheightchange="onkeyboardheightchange"
+			/>
+			<template v-if="type === 'password' && passwordIcon">
+				<!-- 开启密码时显示小眼睛 -->
+				<uni-icons
+					v-if="isVal"
+					class="content-clear-icon"
+					:class="{ 'is-textarea-icon': type === 'textarea' }"
+					:type="showPassword ? 'eye-slash-filled' : 'eye-filled'"
+					:size="22"
+					:color="focusShow ? primaryColor : '#c0c4cc'"
+					@click="onEyes"
+				></uni-icons>
+			</template>
+			<template v-else-if="suffixIcon">
+				<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')" size="22"></uni-icons>
+			</template>
+			<template v-else>
+				<uni-icons
+					v-if="clearable && isVal && !disabled && type !== 'textarea'"
+					class="content-clear-icon"
+					:class="{ 'is-textarea-icon': type === 'textarea' }"
+					type="clear"
+					:size="clearSize"
+					:color="msg ? '#dd524d' : focusShow ? primaryColor : '#c0c4cc'"
+					@click="onClear"
+				></uni-icons>
+			</template>
+			<slot name="right"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * Easyinput 输入框
+ * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=3455
+ * @property {String}	value	输入内容
+ * @property {String }	type	输入框的类型(默认text) password/text/textarea/..
+ * 	@value text			文本输入键盘
+ * 	@value textarea	多行文本输入键盘
+ * 	@value password	密码输入键盘
+ * 	@value number		数字输入键盘,注意iOS上app-vue弹出的数字键盘并非9宫格方式
+ * 	@value idcard		身份证输入键盘,信、支付宝、百度、QQ小程序
+ * 	@value digit		带小数点的数字键盘	,App的nvue页面、微信、支付宝、百度、头条、QQ小程序支持
+ * @property {Boolean}	clearable	是否显示右侧清空内容的图标控件,点击可清空输入框内容(默认true)
+ * @property {Boolean}	autoHeight	是否自动增高输入区域,type为textarea时有效(默认true)
+ * @property {String }	placeholder	输入框的提示文字
+ * @property {String }	placeholderStyle	placeholder的样式(内联样式,字符串),如"color: #ddd"
+ * @property {Boolean}	focus	是否自动获得焦点(默认false)
+ * @property {Boolean}	disabled	是否禁用(默认false)
+ * @property {Number }	maxlength	最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
+ * @property {String }	confirmType	设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
+ * @property {Number }	clearSize	清除图标的大小,单位px(默认15)
+ * @property {String}	prefixIcon	输入框头部图标
+ * @property {String}	suffixIcon	输入框尾部图标
+ * @property {String}	primaryColor	设置主题色(默认#2979ff)
+ * @property {Boolean}	trim	是否自动去除两端的空格
+ * @property {Boolean}	cursorSpacing	指定光标与键盘的距离,单位 px
+ * @value both	去除两端空格
+ * @value left	去除左侧空格
+ * @value right	去除右侧空格
+ * @value start	去除左侧空格
+ * @value end		去除右侧空格
+ * @value all		去除全部空格
+ * @value none	不去除空格
+ * @property {Boolean}	inputBorder	是否显示input输入框的边框(默认true)
+ * @property {Boolean}	passwordIcon	type=password时是否显示小眼睛图标
+ * @property {Object}	styles	自定义颜色
+ * @event {Function}	input	输入框内容发生变化时触发
+ * @event {Function}	focus	输入框获得焦点时触发
+ * @event {Function}	blur	输入框失去焦点时触发
+ * @event {Function}	confirm	点击完成按钮时触发
+ * @event {Function}	iconClick	点击图标时触发
+ * @example <uni-easyinput v-model="mobile"></uni-easyinput>
+ */
+function obj2strClass(obj) {
+	let classess = '';
+	for (let key in obj) {
+		const val = obj[key];
+		if (val) {
+			classess += `${key} `;
+		}
+	}
+	return classess;
+}
+
+function obj2strStyle(obj) {
+	let style = '';
+	for (let key in obj) {
+		const val = obj[key];
+		style += `${key}:${val};`;
+	}
+	return style;
+}
+export default {
+	name: 'uni-easyinput',
+	emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm', 'clear', 'eyes', 'change', 'keyboardheightchange'],
+	model: {
+		prop: 'modelValue',
+		event: 'update:modelValue'
+	},
+	options: {
+		virtualHost: true
+	},
+	inject: {
+		form: {
+			from: 'uniForm',
+			default: null
+		},
+		formItem: {
+			from: 'uniFormItem',
+			default: null
+		}
+	},
+	props: {
+		name: String,
+		value: [Number, String],
+		modelValue: [Number, String],
+		type: {
+			type: String,
+			default: 'text'
+		},
+		clearable: {
+			type: Boolean,
+			default: true
+		},
+		autoHeight: {
+			type: Boolean,
+			default: false
+		},
+		placeholder: {
+			type: String,
+			default: ' '
+		},
+		placeholderStyle: String,
+		focus: {
+			type: Boolean,
+			default: false
+		},
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		maxlength: {
+			type: [Number, String],
+			default: 140
+		},
+		confirmType: {
+			type: String,
+			default: 'done'
+		},
+		clearSize: {
+			type: [Number, String],
+			default: 24
+		},
+		inputBorder: {
+			type: Boolean,
+			default: true
+		},
+		prefixIcon: {
+			type: String,
+			default: ''
+		},
+		suffixIcon: {
+			type: String,
+			default: ''
+		},
+		trim: {
+			type: [Boolean, String],
+			default: false
+		},
+		cursorSpacing: {
+			type: Number,
+			default: 0
+		},
+		passwordIcon: {
+			type: Boolean,
+			default: true
+		},
+		primaryColor: {
+			type: String,
+			default: '#2979ff'
+		},
+		styles: {
+			type: Object,
+			default() {
+				return {
+					color: '#333',
+					backgroundColor: '#fff',
+					disableColor: '#F7F6F6',
+					borderColor: '#e5e5e5'
+				};
+			}
+		},
+		errorMessage: {
+			type: [String, Boolean],
+			default: ''
+		}
+	},
+	data() {
+		return {
+			focused: false,
+			val: '',
+			showMsg: '',
+			border: false,
+			isFirstBorder: false,
+			showClearIcon: false,
+			showPassword: false,
+			focusShow: false,
+			localMsg: '',
+			isEnter: false // 用于判断当前是否是使用回车操作
+		};
+	},
+	computed: {
+		// 输入框内是否有值
+		isVal() {
+			const val = this.val;
+			// fixed by mehaotian 处理值为0的情况,字符串0不在处理范围
+			if (val || val === 0) {
+				return true;
+			}
+			return false;
+		},
+
+		msg() {
+			// console.log('computed', this.form, this.formItem);
+			// if (this.form) {
+			// 	return this.errorMessage || this.formItem.errMsg;
+			// }
+			// TODO 处理头条 formItem 中 errMsg 不更新的问题
+			return this.localMsg || this.errorMessage;
+		},
+		// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,用户可以传入字符串数值
+		inputMaxlength() {
+			return Number(this.maxlength);
+		},
+
+		// 处理外层样式的style
+		boxStyle() {
+			return `color:${this.inputBorder && this.msg ? '#e43d33' : this.styles.color};`;
+		},
+		// input 内容的类和样式处理
+		inputContentClass() {
+			return obj2strClass({
+				'is-input-border': this.inputBorder,
+				'is-input-error-border': this.inputBorder && this.msg,
+				'is-textarea': this.type === 'textarea',
+				'is-disabled': this.disabled,
+				'is-focused': this.focusShow
+			});
+		},
+		inputContentStyle() {
+			const focusColor = this.focusShow ? this.primaryColor : this.styles.borderColor;
+			const borderColor = this.inputBorder && this.msg ? '#dd524d' : focusColor;
+			return obj2strStyle({
+				'border-color': borderColor || '#e5e5e5',
+				'background-color': this.disabled ? this.styles.disableColor : this.styles.backgroundColor
+			});
+		},
+		// input右侧样式
+		inputStyle() {
+			const paddingRight = this.type === 'password' || this.clearable || this.prefixIcon ? '' : '10px';
+			return obj2strStyle({
+				'padding-right': paddingRight,
+				'padding-left': this.prefixIcon ? '' : '10px'
+			});
+		}
+	},
+	watch: {
+		value(newVal) {
+			this.val = newVal;
+		},
+		modelValue(newVal) {
+			this.val = newVal;
+		},
+		focus(newVal) {
+			this.$nextTick(() => {
+				this.focused = this.focus;
+				this.focusShow = this.focus;
+			});
+		}
+	},
+	created() {
+		this.init();
+		// TODO 处理头条vue3 computed 不监听 inject 更改的问题(formItem.errMsg)
+		if (this.form && this.formItem) {
+			this.$watch('formItem.errMsg', newVal => {
+				this.localMsg = newVal;
+			});
+		}
+	},
+	mounted() {
+		this.$nextTick(() => {
+			this.focused = this.focus;
+			this.focusShow = this.focus;
+		});
+	},
+	methods: {
+		/**
+		 * 初始化变量值
+		 */
+		init() {
+			if (this.value || this.value === 0) {
+				this.val = this.value;
+			} else if (this.modelValue || this.modelValue === 0 || this.modelValue === '') {
+				this.val = this.modelValue;
+			} else {
+				this.val = null;
+			}
+		},
+
+		/**
+		 * 点击图标时触发
+		 * @param {Object} type
+		 */
+		onClickIcon(type) {
+			this.$emit('iconClick', type);
+		},
+
+		/**
+		 * 显示隐藏内容,密码框时生效
+		 */
+		onEyes() {
+			this.showPassword = !this.showPassword;
+			this.$emit('eyes', this.showPassword);
+		},
+
+		/**
+		 * 输入时触发
+		 * @param {Object} event
+		 */
+		onInput(event) {
+			let value = event.detail.value;
+			// 判断是否去除空格
+			if (this.trim) {
+				if (typeof this.trim === 'boolean' && this.trim) {
+					value = this.trimStr(value);
+				}
+				if (typeof this.trim === 'string') {
+					value = this.trimStr(value, this.trim);
+				}
+			}
+			if (this.errMsg) this.errMsg = '';
+			this.val = value;
+			// TODO 兼容 vue2
+			this.$emit('input', value);
+			// TODO 兼容 vue3
+			this.$emit('update:modelValue', value);
+		},
+
+		/**
+		 * 外部调用方法
+		 * 获取焦点时触发
+		 * @param {Object} event
+		 */
+		onFocus() {
+			this.$nextTick(() => {
+				this.focused = true;
+			});
+			this.$emit('focus', null);
+		},
+
+		_Focus(event) {
+			this.focusShow = true;
+			this.$emit('focus', event);
+		},
+
+		/**
+		 * 外部调用方法
+		 * 失去焦点时触发
+		 * @param {Object} event
+		 */
+		onBlur() {
+			this.focused = false;
+			this.$emit('focus', null);
+		},
+		_Blur(event) {
+			let value = event.detail.value;
+			this.focusShow = false;
+			this.$emit('blur', event);
+			// 根据类型返回值,在event中获取的值理论上讲都是string
+			if (this.isEnter === false) {
+				this.$emit('change', this.val);
+			}
+			// 失去焦点时参与表单校验
+			if (this.form && this.formItem) {
+				const { validateTrigger } = this.form;
+				if (validateTrigger === 'blur') {
+					this.formItem.onFieldChange();
+				}
+			}
+		},
+
+		/**
+		 * 按下键盘的发送键
+		 * @param {Object} e
+		 */
+		onConfirm(e) {
+			this.$emit('confirm', this.val);
+			this.isEnter = true;
+			this.$emit('change', this.val);
+			this.$nextTick(() => {
+				this.isEnter = false;
+			});
+		},
+
+		/**
+		 * 清理内容
+		 * @param {Object} event
+		 */
+		onClear(event) {
+			this.val = '';
+			// TODO 兼容 vue2
+			this.$emit('input', '');
+			// TODO 兼容 vue2
+			// TODO 兼容 vue3
+			this.$emit('update:modelValue', '');
+			// 点击叉号触发
+			this.$emit('clear');
+		},
+
+    /**
+     * 键盘高度发生变化的时候触发此事件
+     * 兼容性:微信小程序2.7.0+、App 3.1.0+
+     * @param {Object} event
+     */
+    onkeyboardheightchange(event) {
+      this.$emit("keyboardheightchange",event);
+    },
+
+		/**
+		 * 去除空格
+		 */
+		trimStr(str, pos = 'both') {
+			if (pos === 'both') {
+				return str.trim();
+			} else if (pos === 'left') {
+				return str.trimLeft();
+			} else if (pos === 'right') {
+				return str.trimRight();
+			} else if (pos === 'start') {
+				return str.trimStart();
+			} else if (pos === 'end') {
+				return str.trimEnd();
+			} else if (pos === 'all') {
+				return str.replace(/\s+/g, '');
+			} else if (pos === 'none') {
+				return str;
+			}
+			return str;
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+$uni-error: #e43d33;
+$uni-border-1: #dcdfe6 !default;
+
+.uni-easyinput {
+	/* #ifndef APP-NVUE */
+	width: 100%;
+	/* #endif */
+	flex: 1;
+	position: relative;
+	text-align: left;
+	color: #333;
+	font-size: 14px;
+}
+
+.uni-easyinput__content {
+	flex: 1;
+	/* #ifndef APP-NVUE */
+	width: 100%;
+	display: flex;
+	box-sizing: border-box;
+	// min-height: 36px;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	// 处理border动画刚开始显示黑色的问题
+	border-color: #fff;
+	transition-property: border-color;
+	transition-duration: 0.3s;
+}
+
+.uni-easyinput__content-input {
+	/* #ifndef APP-NVUE */
+	width: auto;
+	/* #endif */
+	position: relative;
+	overflow: hidden;
+	flex: 1;
+	line-height: 1;
+	font-size: 14px;
+	height: 35px;
+	// min-height: 36px;
+}
+
+.uni-easyinput__placeholder-class {
+	color: #999;
+	font-size: 12px;
+	// font-weight: 200;
+}
+
+.is-textarea {
+	align-items: flex-start;
+}
+
+.is-textarea-icon {
+	margin-top: 5px;
+}
+
+.uni-easyinput__content-textarea {
+	position: relative;
+	overflow: hidden;
+	flex: 1;
+	line-height: 1.5;
+	font-size: 14px;
+	margin: 6px;
+	margin-left: 0;
+	height: 80px;
+	min-height: 80px;
+	/* #ifndef APP-NVUE */
+	min-height: 80px;
+	width: auto;
+	/* #endif */
+}
+
+.input-padding {
+	padding-left: 10px;
+}
+
+.content-clear-icon {
+	padding: 0 5px;
+}
+
+.label-icon {
+	margin-right: 5px;
+	margin-top: -1px;
+}
+
+// 显示边框
+.is-input-border {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	box-sizing: border-box;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	border: 1px solid $uni-border-1;
+	border-radius: 4px;
+	/* #ifdef MP-ALIPAY */
+	overflow: hidden;
+	/* #endif */
+}
+
+.uni-error-message {
+	position: absolute;
+	bottom: -17px;
+	left: 0;
+	line-height: 12px;
+	color: $uni-error;
+	font-size: 12px;
+	text-align: left;
+}
+
+.uni-error-msg--boeder {
+	position: relative;
+	bottom: 0;
+	line-height: 22px;
+}
+
+.is-input-error-border {
+	border-color: $uni-error;
+
+	.uni-easyinput__placeholder-class {
+		color: mix(#fff, $uni-error, 50%);
+	}
+}
+
+.uni-easyinput--border {
+	margin-bottom: 0;
+	padding: 10px 15px;
+	// padding-bottom: 0;
+	border-top: 1px #eee solid;
+}
+
+.uni-easyinput-error {
+	padding-bottom: 0;
+}
+
+.is-first-border {
+	/* #ifndef APP-NVUE */
+	border: none;
+	/* #endif */
+	/* #ifdef APP-NVUE */
+	border-width: 0;
+	/* #endif */
+}
+
+.is-disabled {
+	background-color: #f7f6f6;
+	color: #d5d5d5;
+
+	.uni-easyinput__placeholder-class {
+		color: #d5d5d5;
+		font-size: 12px;
+	}
+}
+</style>

+ 87 - 0
uni_modules/uni-easyinput/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uni-easyinput",
+  "displayName": "uni-easyinput 增强输入框",
+  "version": "1.1.10",
+  "description": "Easyinput 组件是对原生input组件的增强",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "input",
+    "uni-easyinput",
+    "输入框"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uni-scss",
+      "uni-icons"
+    ],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 11 - 0
uni_modules/uni-easyinput/readme.md

@@ -0,0 +1,11 @@
+
+
+### Easyinput 增强输入框
+> **组件名:uni-easyinput**
+> 代码块: `uEasyinput`
+
+
+easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的,easyinput 内置了边框,图标等,同时包含 input 所有功能
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 94 - 0
uni_modules/uni-forms/changelog.md

@@ -0,0 +1,94 @@
+## 1.4.10(2023-11-03)
+- 优化 labelWidth 描述错误
+## 1.4.9(2023-02-10)
+- 修复 required 参数无法动态绑定
+## 1.4.8(2022-08-23)
+- 优化 根据 rules 自动添加 required 的问题
+## 1.4.7(2022-08-22)
+- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540)
+## 1.4.6(2022-07-13)
+- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug
+## 1.4.5(2022-07-05)
+- 新增 更多表单示例
+- 优化 子表单组件过期提示的问题
+- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式
+## 1.4.4(2022-07-04)
+- 更新 删除组件日志
+## 1.4.3(2022-07-04)
+- 修复 由 1.4.0 引发的 label 插槽不生效的bug
+## 1.4.2(2022-07-04)
+- 修复 子组件找不到 setValue 报错的bug
+## 1.4.1(2022-07-04)
+- 修复 uni-data-picker 在 uni-forms-item 中报错的bug
+- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
+## 1.4.0(2022-06-30)
+- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题
+- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力
+- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃
+- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效
+- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法
+- 新增 子表单的 setRules 方法,配合自定义校验函数使用
+- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则
+- 优化 动态表单校验方式,废弃拼接name的方式
+## 1.3.3(2022-06-22)
+- 修复 表单校验顺序无序问题
+## 1.3.2(2021-12-09)
+-
+## 1.3.1(2021-11-19)
+- 修复 label 插槽不生效的bug
+## 1.3.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-forms](https://uniapp.dcloud.io/component/uniui/uni-forms)
+## 1.2.7(2021-08-13)
+- 修复 没有添加校验规则的字段依然报错的Bug
+## 1.2.6(2021-08-11)
+- 修复 重置表单错误信息无法清除的问题
+## 1.2.5(2021-08-11)
+- 优化 组件文档
+## 1.2.4(2021-08-11)
+- 修复 表单验证只生效一次的问题
+## 1.2.3(2021-07-30)
+- 优化 vue3下事件警告的问题
+## 1.2.2(2021-07-26)
+- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug
+- 修复 1.2.1 引起的示例在小程序平台报错的Bug
+## 1.2.1(2021-07-22)
+- 修复 动态校验表单,默认值为空的情况下校验失效的Bug
+- 修复 不指定name属性时,运行报错的Bug
+- 优化 label默认宽度从65调整至70,使required为true且四字时不换行
+- 优化 组件示例,新增动态校验示例代码
+- 优化 组件文档,使用方式更清晰
+## 1.2.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.2(2021-06-25)
+- 修复 pattern 属性在微信小程序平台无效的问题
+## 1.1.1(2021-06-22)
+- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug
+## 1.1.0(2021-06-22)
+- 修复 只写setRules方法而导致校验不生效的Bug
+- 修复 由上个办法引发的错误提示文字错位的Bug
+## 1.0.48(2021-06-21)
+- 修复 不设置 label 属性 ,无法设置label插槽的问题
+## 1.0.47(2021-06-21)
+- 修复 不设置label属性,label-width属性不生效的bug
+- 修复 setRules 方法与rules属性冲突的问题
+## 1.0.46(2021-06-04)
+- 修复 动态删减数据导致报错的问题
+## 1.0.45(2021-06-04)
+- 新增 modelValue 属性 ,value 即将废弃
+## 1.0.44(2021-06-02)
+- 新增 uni-forms-item 可以设置单独的 rules
+- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤
+- 优化 submit 事件重命名为 validate
+## 1.0.43(2021-05-12)
+- 新增 组件示例地址
+## 1.0.42(2021-04-30)
+- 修复 自定义检验器失效的问题
+## 1.0.41(2021-03-05)
+- 更新 校验器
+- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug
+## 1.0.40(2021-03-04)
+- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug
+## 1.0.39(2021-02-05)
+- 调整为uni_modules目录规范
+- 修复 校验器传入 int 等类型 ,返回String类型的Bug

+ 627 - 0
uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue

@@ -0,0 +1,627 @@
+<template>
+	<view class="uni-forms-item"
+		:class="['is-direction-' + localLabelPos ,border?'uni-forms-item--border':'' ,border && isFirstBorder?'is-first-border':'']">
+		<slot name="label">
+			<view class="uni-forms-item__label" :class="{'no-label':!label && !required}"
+				:style="{width:localLabelWidth,justifyContent: localLabelAlign}">
+				<text v-if="required" class="is-required">*</text>
+				<text>{{label}}</text>
+			</view>
+		</slot>
+		<!-- #ifndef APP-NVUE -->
+		<view class="uni-forms-item__content">
+			<slot></slot>
+			<view class="uni-forms-item__error" :class="{'msg--active':msg}">
+				<text>{{msg}}</text>
+			</view>
+		</view>
+		<!-- #endif -->
+		<!-- #ifdef APP-NVUE -->
+		<view class="uni-forms-item__nuve-content">
+			<view class="uni-forms-item__content">
+				<slot></slot>
+			</view>
+			<view class="uni-forms-item__error" :class="{'msg--active':msg}">
+				<text class="error-text">{{msg}}</text>
+			</view>
+		</view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	/**
+	 * uni-fomrs-item 表单子组件
+	 * @description uni-fomrs-item 表单子组件,提供了基础布局已经校验能力
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
+	 * @property {Boolean} required 是否必填,左边显示红色"*"号
+	 * @property {String } 	label 				输入框左边的文字提示
+	 * @property {Number } 	labelWidth 			label的宽度,单位px(默认70)
+	 * @property {String } 	labelAlign = [left|center|right] label的文字对齐方式(默认left)
+	 * 	@value left		label 左侧显示
+	 * 	@value center	label 居中
+	 * 	@value right	label 右侧对齐
+	 * @property {String } 	errorMessage 		显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
+	 * @property {String } 	name 				表单域的属性名,在使用校验规则时必填
+	 * @property {String } 	leftIcon 			【1.4.0废弃】label左边的图标,限 uni-ui 的图标名称
+	 * @property {String } 	iconColor 		【1.4.0废弃】左边通过icon配置的图标的颜色(默认#606266)
+	 * @property {String} validateTrigger = [bind|submit|blur]	【1.4.0废弃】校验触发器方式 默认 submit
+	 * 	@value bind 	发生变化时触发
+	 * 	@value submit 提交时触发
+	 * 	@value blur 	失去焦点触发
+	 * @property {String } 	labelPosition = [top|left] 【1.4.0废弃】label的文字的位置(默认left)
+	 * 	@value top	顶部显示 label
+	 * 	@value left	左侧显示 label
+	 */
+
+	export default {
+		name: 'uniFormsItem',
+		options: {
+			virtualHost: true
+		},
+		provide() {
+			return {
+				uniFormItem: this
+			}
+		},
+		inject: {
+			form: {
+				from: 'uniForm',
+				default: null
+			},
+		},
+		props: {
+			// 表单校验规则
+			rules: {
+				type: Array,
+				default () {
+					return null;
+				}
+			},
+			// 表单域的属性名,在使用校验规则时必填
+			name: {
+				type: [String, Array],
+				default: ''
+			},
+			required: {
+				type: Boolean,
+				default: false
+			},
+			label: {
+				type: String,
+				default: ''
+			},
+			// label的宽度
+			labelWidth: {
+				type: [String, Number],
+				default: ''
+			},
+			// label 居中方式,默认 left 取值 left/center/right
+			labelAlign: {
+				type: String,
+				default: ''
+			},
+			// 强制显示错误信息
+			errorMessage: {
+				type: [String, Boolean],
+				default: ''
+			},
+			// 1.4.0 弃用,统一使用 form 的校验时机
+			// validateTrigger: {
+			// 	type: String,
+			// 	default: ''
+			// },
+			// 1.4.0 弃用,统一使用 form 的label 位置
+			// labelPosition: {
+			// 	type: String,
+			// 	default: ''
+			// },
+			// 1.4.0 以下属性已经废弃,请使用  #label 插槽代替
+			leftIcon: String,
+			iconColor: {
+				type: String,
+				default: '#606266'
+			},
+		},
+		data() {
+			return {
+				errMsg: '',
+				userRules: null,
+				localLabelAlign: 'left',
+				localLabelWidth: '70px',
+				localLabelPos: 'left',
+				border: false,
+				isFirstBorder: false,
+			};
+		},
+		computed: {
+			// 处理错误信息
+			msg() {
+				return this.errorMessage || this.errMsg;
+			}
+		},
+		watch: {
+			// 规则发生变化通知子组件更新
+			'form.formRules'(val) {
+				// TODO 处理头条vue3 watch不生效的问题
+				// #ifndef MP-TOUTIAO
+				this.init()
+				// #endif
+			},
+			'form.labelWidth'(val) {
+				// 宽度
+				this.localLabelWidth = this._labelWidthUnit(val)
+
+			},
+			'form.labelPosition'(val) {
+				// 标签位置
+				this.localLabelPos = this._labelPosition()
+			},
+			'form.labelAlign'(val) {
+
+			}
+		},
+		created() {
+			this.init(true)
+			if (this.name && this.form) {
+				// TODO 处理头条vue3 watch不生效的问题
+				// #ifdef MP-TOUTIAO
+				this.$watch('form.formRules', () => {
+					this.init()
+				})
+				// #endif
+
+				// 监听变化
+				this.$watch(
+					() => {
+						const val = this.form._getDataValue(this.name, this.form.localData)
+						return val
+					},
+					(value, oldVal) => {
+						const isEqual = this.form._isEqual(value, oldVal)
+						// 简单判断前后值的变化,只有发生变化才会发生校验
+						// TODO  如果 oldVal = undefined ,那么大概率是源数据里没有值导致 ,这个情况不哦校验 ,可能不严谨 ,需要在做观察
+						// fix by mehaotian 暂时取消 && oldVal !== undefined ,如果formData 中不存在,可能会不校验
+						if (!isEqual) {
+							const val = this.itemSetValue(value)
+							this.onFieldChange(val, false)
+						}
+					}, {
+						immediate: false
+					}
+				);
+			}
+
+		},
+		// #ifndef VUE3
+		destroyed() {
+			if (this.__isUnmounted) return
+			this.unInit()
+		},
+		// #endif
+		// #ifdef VUE3
+		unmounted() {
+			this.__isUnmounted = true
+			this.unInit()
+		},
+		// #endif
+		methods: {
+			/**
+			 * 外部调用方法
+			 * 设置规则 ,主要用于小程序自定义检验规则
+			 * @param {Array} rules 规则源数据
+			 */
+			setRules(rules = null) {
+				this.userRules = rules
+				this.init(false)
+			},
+			// 兼容老版本表单组件
+			setValue() {
+				// console.log('setValue 方法已经弃用,请使用最新版本的 uni-forms 表单组件以及其他关联组件。');
+			},
+			/**
+			 * 外部调用方法
+			 * 校验数据
+			 * @param {any} value 需要校验的数据
+			 * @param {boolean} 是否立即校验
+			 * @return {Array|null} 校验内容
+			 */
+			async onFieldChange(value, formtrigger = true) {
+				const {
+					formData,
+					localData,
+					errShowType,
+					validateCheck,
+					validateTrigger,
+					_isRequiredField,
+					_realName
+				} = this.form
+				const name = _realName(this.name)
+				if (!value) {
+					value = this.form.formData[name]
+				}
+				// fixd by mehaotian 不在校验前清空信息,解决闪屏的问题
+				// this.errMsg = '';
+
+				// fix by mehaotian 解决没有检验规则的情况下,抛出错误的问题
+				const ruleLen = this.itemRules.rules && this.itemRules.rules.length
+				if (!this.validator || !ruleLen || ruleLen === 0) return;
+
+				// 检验时机
+				// let trigger = this.isTrigger(this.itemRules.validateTrigger, this.validateTrigger, validateTrigger);
+				const isRequiredField = _isRequiredField(this.itemRules.rules || []);
+				let result = null;
+				// 只有等于 bind 时 ,才能开启时实校验
+				if (validateTrigger === 'bind' || formtrigger) {
+					// 校验当前表单项
+					result = await this.validator.validateUpdate({
+							[name]: value
+						},
+						formData
+					);
+
+					// 判断是否必填,非必填,不填不校验,填写才校验 ,暂时只处理 undefined  和空的情况
+					if (!isRequiredField && (value === undefined || value === '')) {
+						result = null;
+					}
+
+					// 判断错误信息显示类型
+					if (result && result.errorMessage) {
+						if (errShowType === 'undertext') {
+							// 获取错误信息
+							this.errMsg = !result ? '' : result.errorMessage;
+						}
+						if (errShowType === 'toast') {
+							uni.showToast({
+								title: result.errorMessage || '校验错误',
+								icon: 'none'
+							});
+						}
+						if (errShowType === 'modal') {
+							uni.showModal({
+								title: '提示',
+								content: result.errorMessage || '校验错误'
+							});
+						}
+					} else {
+						this.errMsg = ''
+					}
+					// 通知 form 组件更新事件
+					validateCheck(result ? result : null)
+				} else {
+					this.errMsg = ''
+				}
+				return result ? result : null;
+			},
+			/**
+			 * 初始组件数据
+			 */
+			init(type = false) {
+				const {
+					validator,
+					formRules,
+					childrens,
+					formData,
+					localData,
+					_realName,
+					labelWidth,
+					_getDataValue,
+					_setDataValue
+				} = this.form || {}
+				// 对齐方式
+				this.localLabelAlign = this._justifyContent()
+				// 宽度
+				this.localLabelWidth = this._labelWidthUnit(labelWidth)
+				// 标签位置
+				this.localLabelPos = this._labelPosition()
+				// 将需要校验的子组件加入form 队列
+				this.form && type && childrens.push(this)
+
+				if (!validator || !formRules) return
+				// 判断第一个 item
+				if (!this.form.isFirstBorder) {
+					this.form.isFirstBorder = true;
+					this.isFirstBorder = true;
+				}
+
+				// 判断 group 里的第一个 item
+				if (this.group) {
+					if (!this.group.isFirstBorder) {
+						this.group.isFirstBorder = true;
+						this.isFirstBorder = true;
+					}
+				}
+				this.border = this.form.border;
+				// 获取子域的真实名称
+				const name = _realName(this.name)
+				const itemRule = this.userRules || this.rules
+				if (typeof formRules === 'object' && itemRule) {
+					// 子规则替换父规则
+					formRules[name] = {
+						rules: itemRule
+					}
+					validator.updateSchema(formRules);
+				}
+				// 注册校验规则
+				const itemRules = formRules[name] || {}
+				this.itemRules = itemRules
+				// 注册校验函数
+				this.validator = validator
+				// 默认值赋予
+				this.itemSetValue(_getDataValue(this.name, localData))
+			},
+			unInit() {
+				if (this.form) {
+					const {
+						childrens,
+						formData,
+						_realName
+					} = this.form
+					childrens.forEach((item, index) => {
+						if (item === this) {
+							this.form.childrens.splice(index, 1)
+							delete formData[_realName(item.name)]
+						}
+					})
+				}
+			},
+			// 设置item 的值
+			itemSetValue(value) {
+				const name = this.form._realName(this.name)
+				const rules = this.itemRules.rules || []
+				const val = this.form._getValue(name, value, rules)
+				this.form._setDataValue(name, this.form.formData, val)
+				return val
+			},
+
+			/**
+			 * 移除该表单项的校验结果
+			 */
+			clearValidate() {
+				this.errMsg = '';
+			},
+
+			// 是否显示星号
+			_isRequired() {
+				// TODO 不根据规则显示 星号,考虑后续兼容
+				// if (this.form) {
+				// 	if (this.form._isRequiredField(this.itemRules.rules || []) && this.required) {
+				// 		return true
+				// 	}
+				// 	return false
+				// }
+				return this.required
+			},
+
+			// 处理对齐方式
+			_justifyContent() {
+				if (this.form) {
+					const {
+						labelAlign
+					} = this.form
+					let labelAli = this.labelAlign ? this.labelAlign : labelAlign;
+					if (labelAli === 'left') return 'flex-start';
+					if (labelAli === 'center') return 'center';
+					if (labelAli === 'right') return 'flex-end';
+				}
+				return 'flex-start';
+			},
+			// 处理 label宽度单位 ,继承父元素的值
+			_labelWidthUnit(labelWidth) {
+
+				// if (this.form) {
+				// 	const {
+				// 		labelWidth
+				// 	} = this.form
+				return this.num2px(this.labelWidth ? this.labelWidth : (labelWidth || (this.label ? 70 : 'auto')))
+				// }
+				// return '70px'
+			},
+			// 处理 label 位置
+			_labelPosition() {
+				if (this.form) return this.form.labelPosition || 'left'
+				return 'left'
+
+			},
+
+			/**
+			 * 触发时机
+			 * @param {Object} rule 当前规则内时机
+			 * @param {Object} itemRlue 当前组件时机
+			 * @param {Object} parentRule 父组件时机
+			 */
+			isTrigger(rule, itemRlue, parentRule) {
+				//  bind  submit
+				if (rule === 'submit' || !rule) {
+					if (rule === undefined) {
+						if (itemRlue !== 'bind') {
+							if (!itemRlue) {
+								return parentRule === '' ? 'bind' : 'submit';
+							}
+							return 'submit';
+						}
+						return 'bind';
+					}
+					return 'submit';
+				}
+				return 'bind';
+			},
+			num2px(num) {
+				if (typeof num === 'number') {
+					return `${num}px`
+				}
+				return num
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.uni-forms-item {
+		position: relative;
+		display: flex;
+		/* #ifdef APP-NVUE */
+		// 在 nvue 中,使用 margin-bottom error 信息会被隐藏
+		padding-bottom: 22px;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		margin-bottom: 22px;
+		/* #endif */
+		flex-direction: row;
+
+		&__label {
+			display: flex;
+			flex-direction: row;
+			align-items: center;
+			text-align: left;
+			font-size: 14px;
+			color: #606266;
+			height: 36px;
+			padding: 0 12px 0 0;
+			/* #ifndef APP-NVUE */
+			vertical-align: middle;
+			flex-shrink: 0;
+			/* #endif */
+
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+
+			/* #endif */
+			&.no-label {
+				padding: 0;
+			}
+		}
+
+		&__content {
+			/* #ifndef MP-TOUTIAO */
+			// display: flex;
+			// align-items: center;
+			/* #endif */
+			position: relative;
+			font-size: 14px;
+			flex: 1;
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			flex-direction: row;
+
+			/* #ifndef APP || H5 || MP-WEIXIN || APP-NVUE */
+			// TODO 因为小程序平台会多一层标签节点 ,所以需要在多余节点继承当前样式
+			&>uni-easyinput,
+			&>uni-data-picker {
+				width: 100%;
+			}
+
+			/* #endif */
+
+		}
+
+		& .uni-forms-item__nuve-content {
+			display: flex;
+			flex-direction: column;
+			flex: 1;
+		}
+
+		&__error {
+			color: #f56c6c;
+			font-size: 12px;
+			line-height: 1;
+			padding-top: 4px;
+			position: absolute;
+			/* #ifndef APP-NVUE */
+			top: 100%;
+			left: 0;
+			transition: transform 0.3s;
+			transform: translateY(-100%);
+			/* #endif */
+			/* #ifdef APP-NVUE */
+			bottom: 5px;
+			/* #endif */
+
+			opacity: 0;
+
+			.error-text {
+				// 只有 nvue 下这个样式才生效
+				color: #f56c6c;
+				font-size: 12px;
+			}
+
+			&.msg--active {
+				opacity: 1;
+				transform: translateY(0%);
+			}
+		}
+
+		// 位置修饰样式
+		&.is-direction-left {
+			flex-direction: row;
+		}
+
+		&.is-direction-top {
+			flex-direction: column;
+
+			.uni-forms-item__label {
+				padding: 0 0 8px;
+				line-height: 1.5715;
+				text-align: left;
+				/* #ifndef APP-NVUE */
+				white-space: initial;
+				/* #endif */
+			}
+		}
+
+		.is-required {
+			// color: $uni-color-error;
+			color: #dd524d;
+			font-weight: bold;
+		}
+	}
+
+
+	.uni-forms-item--border {
+		margin-bottom: 0;
+		padding: 10px 0;
+		// padding-bottom: 0;
+		border-top: 1px #eee solid;
+
+		/* #ifndef APP-NVUE */
+		.uni-forms-item__content {
+			flex-direction: column;
+			justify-content: flex-start;
+			align-items: flex-start;
+
+			.uni-forms-item__error {
+				position: relative;
+				top: 5px;
+				left: 0;
+				padding-top: 0;
+			}
+		}
+
+		/* #endif */
+
+		/* #ifdef APP-NVUE */
+		display: flex;
+		flex-direction: column;
+
+		.uni-forms-item__error {
+			position: relative;
+			top: 0px;
+			left: 0;
+			padding-top: 0;
+			margin-top: 5px;
+		}
+
+		/* #endif */
+
+	}
+
+	.is-first-border {
+		/* #ifndef APP-NVUE */
+		border: none;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		border-width: 0;
+		/* #endif */
+	}
+</style>

+ 397 - 0
uni_modules/uni-forms/components/uni-forms/uni-forms.vue

@@ -0,0 +1,397 @@
+<template>
+	<view class="uni-forms">
+		<form>
+			<slot></slot>
+		</form>
+	</view>
+</template>
+
+<script>
+	import Validator from './validate.js';
+	import {
+		deepCopy,
+		getValue,
+		isRequiredField,
+		setDataValue,
+		getDataValue,
+		realName,
+		isRealName,
+		rawData,
+		isEqual
+	} from './utils.js'
+
+	// #ifndef VUE3
+	// 后续会慢慢废弃这个方法
+	import Vue from 'vue';
+	Vue.prototype.binddata = function(name, value, formName) {
+		if (formName) {
+			this.$refs[formName].setValue(name, value);
+		} else {
+			let formVm;
+			for (let i in this.$refs) {
+				const vm = this.$refs[i];
+				if (vm && vm.$options && vm.$options.name === 'uniForms') {
+					formVm = vm;
+					break;
+				}
+			}
+			if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
+			formVm.setValue(name, value);
+		}
+	};
+	// #endif
+	/**
+	 * Forms 表单
+	 * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
+	 * @property {Object} rules	表单校验规则
+	 * @property {String} validateTrigger = [bind|submit|blur]	校验触发器方式 默认 submit
+	 * @value bind		发生变化时触发
+	 * @value submit	提交时触发
+	 * @value blur	  失去焦点时触发
+	 * @property {String} labelPosition = [top|left]	label 位置 默认 left
+	 * @value top		顶部显示 label
+	 * @value left	左侧显示 label
+	 * @property {String} labelWidth	label 宽度,默认 70px
+	 * @property {String} labelAlign = [left|center|right]	label 居中方式  默认 left
+	 * @value left		label 左侧显示
+	 * @value center	label 居中
+	 * @value right		label 右侧对齐
+	 * @property {String} errShowType = [undertext|toast|modal]	校验错误信息提示方式
+	 * @value undertext	错误信息在底部显示
+	 * @value toast			错误信息toast显示
+	 * @value modal			错误信息modal显示
+	 * @event {Function} submit	提交时触发
+	 * @event {Function} validate	校验结果发生变化触发
+	 */
+	export default {
+		name: 'uniForms',
+		emits: ['validate', 'submit'],
+		options: {
+			virtualHost: true
+		},
+		props: {
+			// 即将弃用
+			value: {
+				type: Object,
+				default () {
+					return null;
+				}
+			},
+			// vue3 替换 value 属性
+			modelValue: {
+				type: Object,
+				default () {
+					return null;
+				}
+			},
+			// 1.4.0 开始将不支持 v-model ,且废弃 value 和 modelValue
+			model: {
+				type: Object,
+				default () {
+					return null;
+				}
+			},
+			// 表单校验规则
+			rules: {
+				type: Object,
+				default () {
+					return {};
+				}
+			},
+			//校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
+			errShowType: {
+				type: String,
+				default: 'undertext'
+			},
+			// 校验触发器方式 默认 bind 取值 [bind|submit]
+			validateTrigger: {
+				type: String,
+				default: 'submit'
+			},
+			// label 位置,默认 left 取值  top/left
+			labelPosition: {
+				type: String,
+				default: 'left'
+			},
+			// label 宽度
+			labelWidth: {
+				type: [String, Number],
+				default: ''
+			},
+			// label 居中方式,默认 left 取值 left/center/right
+			labelAlign: {
+				type: String,
+				default: 'left'
+			},
+			border: {
+				type: Boolean,
+				default: false
+			}
+		},
+		provide() {
+			return {
+				uniForm: this
+			}
+		},
+		data() {
+			return {
+				// 表单本地值的记录,不应该与传如的值进行关联
+				formData: {},
+				formRules: {}
+			};
+		},
+		computed: {
+			// 计算数据源变化的
+			localData() {
+				const localVal = this.model || this.modelValue || this.value
+				if (localVal) {
+					return deepCopy(localVal)
+				}
+				return {}
+			}
+		},
+		watch: {
+			// 监听数据变化 ,暂时不使用,需要单独赋值
+			// localData: {},
+			// 监听规则变化
+			rules: {
+				handler: function(val, oldVal) {
+					this.setRules(val)
+				},
+				deep: true,
+				immediate: true
+			}
+		},
+		created() {
+			// #ifdef VUE3
+			let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
+			if (!getbinddata) {
+				getApp().$vm.$.appContext.config.globalProperties.binddata = function(name, value, formName) {
+					if (formName) {
+						this.$refs[formName].setValue(name, value);
+					} else {
+						let formVm;
+						for (let i in this.$refs) {
+							const vm = this.$refs[i];
+							if (vm && vm.$options && vm.$options.name === 'uniForms') {
+								formVm = vm;
+								break;
+							}
+						}
+						if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
+						formVm.setValue(name, value);
+					}
+				}
+			}
+			// #endif
+
+			// 子组件实例数组
+			this.childrens = []
+			// TODO 兼容旧版 uni-data-picker ,新版本中无效,只是避免报错
+			this.inputChildrens = []
+			this.setRules(this.rules)
+		},
+		methods: {
+			/**
+			 * 外部调用方法
+			 * 设置规则 ,主要用于小程序自定义检验规则
+			 * @param {Array} rules 规则源数据
+			 */
+			setRules(rules) {
+				// TODO 有可能子组件合并规则的时机比这个要早,所以需要合并对象 ,而不是直接赋值,可能会被覆盖
+				this.formRules = Object.assign({}, this.formRules, rules)
+				// 初始化校验函数
+				this.validator = new Validator(rules);
+			},
+
+			/**
+			 * 外部调用方法
+			 * 设置数据,用于设置表单数据,公开给用户使用 , 不支持在动态表单中使用
+			 * @param {Object} key
+			 * @param {Object} value
+			 */
+			setValue(key, value) {
+				let example = this.childrens.find(child => child.name === key);
+				if (!example) return null;
+				this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || [])
+				return example.onFieldChange(this.formData[key]);
+			},
+
+			/**
+			 * 外部调用方法
+			 * 手动提交校验表单
+			 * 对整个表单进行校验的方法,参数为一个回调函数。
+			 * @param {Array} keepitem 保留不参与校验的字段
+			 * @param {type} callback 方法回调
+			 */
+			validate(keepitem, callback) {
+				return this.checkAll(this.formData, keepitem, callback);
+			},
+
+			/**
+			 * 外部调用方法
+			 * 部分表单校验
+			 * @param {Array|String} props 需要校验的字段
+			 * @param {Function} 回调函数
+			 */
+			validateField(props = [], callback) {
+				props = [].concat(props);
+				let invalidFields = {};
+				this.childrens.forEach(item => {
+					const name = realName(item.name)
+					if (props.indexOf(name) !== -1) {
+						invalidFields = Object.assign({}, invalidFields, {
+							[name]: this.formData[name]
+						});
+					}
+				});
+				return this.checkAll(invalidFields, [], callback);
+			},
+
+			/**
+			 * 外部调用方法
+			 * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
+			 * @param {Array|String} props 需要移除校验的字段 ,不填为所有
+			 */
+			clearValidate(props = []) {
+				props = [].concat(props);
+				this.childrens.forEach(item => {
+					if (props.length === 0) {
+						item.errMsg = '';
+					} else {
+						const name = realName(item.name)
+						if (props.indexOf(name) !== -1) {
+							item.errMsg = '';
+						}
+					}
+				});
+			},
+
+			/**
+			 * 外部调用方法 ,即将废弃
+			 * 手动提交校验表单
+			 * 对整个表单进行校验的方法,参数为一个回调函数。
+			 * @param {Array} keepitem 保留不参与校验的字段
+			 * @param {type} callback 方法回调
+			 */
+			submit(keepitem, callback, type) {
+				for (let i in this.dataValue) {
+					const itemData = this.childrens.find(v => v.name === i);
+					if (itemData) {
+						if (this.formData[i] === undefined) {
+							this.formData[i] = this._getValue(i, this.dataValue[i]);
+						}
+					}
+				}
+
+				if (!type) {
+					console.warn('submit 方法即将废弃,请使用validate方法代替!');
+				}
+
+				return this.checkAll(this.formData, keepitem, callback, 'submit');
+			},
+
+			// 校验所有
+			async checkAll(invalidFields, keepitem, callback, type) {
+				// 不存在校验规则 ,则停止校验流程
+				if (!this.validator) return
+				let childrens = []
+				// 处理参与校验的item实例
+				for (let i in invalidFields) {
+					const item = this.childrens.find(v => realName(v.name) === i)
+					if (item) {
+						childrens.push(item)
+					}
+				}
+
+				// 如果validate第一个参数是funciont ,那就走回调
+				if (!callback && typeof keepitem === 'function') {
+					callback = keepitem;
+				}
+
+				let promise;
+				// 如果不存在回调,那么使用 Promise 方式返回
+				if (!callback && typeof callback !== 'function' && Promise) {
+					promise = new Promise((resolve, reject) => {
+						callback = function(valid, invalidFields) {
+							!valid ? resolve(invalidFields) : reject(valid);
+						};
+					});
+				}
+
+				let results = [];
+				// 避免引用错乱 ,建议拷贝对象处理
+				let tempFormData = JSON.parse(JSON.stringify(invalidFields))
+				// 所有子组件参与校验,使用 for 可以使用  awiat
+				for (let i in childrens) {
+					const child = childrens[i]
+					let name = realName(child.name);
+					const result = await child.onFieldChange(tempFormData[name]);
+					if (result) {
+						results.push(result);
+						// toast ,modal 只需要执行第一次就可以
+						if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
+					}
+				}
+
+
+				if (Array.isArray(results)) {
+					if (results.length === 0) results = null;
+				}
+				if (Array.isArray(keepitem)) {
+					keepitem.forEach(v => {
+						let vName = realName(v);
+						let value = getDataValue(v, this.localData)
+						if (value !== undefined) {
+							tempFormData[vName] = value
+						}
+					});
+				}
+
+				// TODO submit 即将废弃
+				if (type === 'submit') {
+					this.$emit('submit', {
+						detail: {
+							value: tempFormData,
+							errors: results
+						}
+					});
+				} else {
+					this.$emit('validate', results);
+				}
+
+				// const resetFormData = rawData(tempFormData, this.localData, this.name)
+				let resetFormData = {}
+				resetFormData = rawData(tempFormData, this.name)
+				callback && typeof callback === 'function' && callback(results, resetFormData);
+
+				if (promise && callback) {
+					return promise;
+				} else {
+					return null;
+				}
+
+			},
+
+			/**
+			 * 返回validate事件
+			 * @param {Object} result
+			 */
+			validateCheck(result) {
+				this.$emit('validate', result);
+			},
+			_getValue: getValue,
+			_isRequiredField: isRequiredField,
+			_setDataValue: setDataValue,
+			_getDataValue: getDataValue,
+			_realName: realName,
+			_isRealName: isRealName,
+			_isEqual: isEqual
+		}
+	};
+</script>
+
+<style lang="scss">
+	.uni-forms {}
+</style>

+ 293 - 0
uni_modules/uni-forms/components/uni-forms/utils.js

@@ -0,0 +1,293 @@
+/**
+ * 简单处理对象拷贝
+ * @param {Obejct} 被拷贝对象
+ * @@return {Object} 拷贝对象
+ */
+export const deepCopy = (val) => {
+	return JSON.parse(JSON.stringify(val))
+}
+/**
+ * 过滤数字类型
+ * @param {String} format 数字类型
+ * @@return {Boolean} 返回是否为数字类型
+ */
+export const typeFilter = (format) => {
+	return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
+}
+
+/**
+ * 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined
+ * @param {String} key 字段名
+ * @param {any} value 字段值
+ * @param {Object} rules 表单校验规则
+ */
+export const getValue = (key, value, rules) => {
+	const isRuleNumType = rules.find(val => val.format && typeFilter(val.format));
+	const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
+	// 输入类型为 number
+	if (!!isRuleNumType) {
+		if (!value && value !== 0) {
+			value = null
+		} else {
+			value = isNumber(Number(value)) ? Number(value) : value
+		}
+	}
+
+	// 输入类型为 boolean
+	if (!!isRuleBoolType) {
+		value = isBoolean(value) ? value : false
+	}
+
+	return value;
+}
+
+/**
+ * 获取表单数据
+ * @param {String|Array} name 真实名称,需要使用 realName 获取
+ * @param {Object} data 原始数据
+ * @param {any} value  需要设置的值
+ */
+export const setDataValue = (field, formdata, value) => {
+	formdata[field] = value
+	return value || ''
+}
+
+/**
+ * 获取表单数据
+ * @param {String|Array} field 真实名称,需要使用 realName 获取
+ * @param {Object} data 原始数据
+ */
+export const getDataValue = (field, data) => {
+	return objGet(data, field)
+}
+
+/**
+ * 获取表单类型
+ * @param {String|Array} field 真实名称,需要使用 realName 获取
+ */
+export const getDataValueType = (field, data) => {
+	const value = getDataValue(field, data)
+	return {
+		type: type(value),
+		value
+	}
+}
+
+/**
+ * 获取表单可用的真实name
+ * @param {String|Array} name 表单name
+ * @@return {String} 表单可用的真实name
+ */
+export const realName = (name, data = {}) => {
+	const base_name = _basePath(name)
+	if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) {
+		const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_')
+		return realname
+	}
+	return base_name[0] || name
+}
+
+/**
+ * 判断是否表单可用的真实name
+ * @param {String|Array} name 表单name
+ * @@return {String} 表单可用的真实name
+ */
+export const isRealName = (name) => {
+	const reg = /^_formdata_#*/
+	return reg.test(name)
+}
+
+/**
+ * 获取表单数据的原始格式
+ * @@return {Object|Array} object 需要解析的数据
+ */
+export const rawData = (object = {}, name) => {
+	let newData = JSON.parse(JSON.stringify(object))
+	let formData = {}
+	for(let i in newData){
+		let path = name2arr(i)
+		objSet(formData,path,newData[i])
+	}
+	return formData
+}
+
+/**
+ * 真实name还原为 array
+ * @param {*} name 
+ */
+export const name2arr = (name) => {
+	let field = name.replace('_formdata_#', '')
+	field = field.split('#').map(v => (isNumber(v) ? Number(v) : v))
+	return field
+}
+
+/**
+ * 对象中设置值
+ * @param {Object|Array} object 源数据
+ * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
+ * @param {String} value 需要设置的值
+ */
+export const objSet = (object, path, value) => {
+	if (typeof object !== 'object') return object;
+	_basePath(path).reduce((o, k, i, _) => {
+		if (i === _.length - 1) { 
+			// 若遍历结束直接赋值
+			o[k] = value
+			return null
+		} else if (k in o) { 
+			// 若存在对应路径,则返回找到的对象,进行下一次遍历
+			return o[k]
+		} else { 
+			// 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象
+			o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {}
+			return o[k]
+		}
+	}, object)
+	// 返回object
+	return object;
+}
+
+// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用
+function _basePath(path) {
+	// 若是数组,则直接返回
+	if (Array.isArray(path)) return path
+	// 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']'
+	return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
+}
+
+/**
+ * 从对象中获取值
+ * @param {Object|Array} object 源数据
+ * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
+ * @param {String} defaultVal 如果无法从调用链中获取值的默认值
+ */
+export const objGet = (object, path, defaultVal = 'undefined') => {
+	// 先将path处理成统一格式
+	let newPath = _basePath(path)
+	// 递归处理,返回最后结果
+	let val = newPath.reduce((o, k) => {
+		return (o || {})[k]
+	}, object);
+	return !val || val !== undefined ? val : defaultVal
+}
+
+
+/**
+ * 是否为 number 类型 
+ * @param {any} num 需要判断的值
+ * @return {Boolean} 是否为 number
+ */
+export const isNumber = (num) => {
+	return !isNaN(Number(num))
+}
+
+/**
+ * 是否为 boolean 类型 
+ * @param {any} bool 需要判断的值
+ * @return {Boolean} 是否为 boolean
+ */
+export const isBoolean = (bool) => {
+	return (typeof bool === 'boolean')
+}
+/**
+ * 是否有必填字段
+ * @param {Object} rules 规则
+ * @return {Boolean} 是否有必填字段
+ */
+export const isRequiredField = (rules) => {
+	let isNoField = false;
+	for (let i = 0; i < rules.length; i++) {
+		const ruleData = rules[i];
+		if (ruleData.required) {
+			isNoField = true;
+			break;
+		}
+	}
+	return isNoField;
+}
+
+
+/**
+ * 获取数据类型
+ * @param {Any} obj 需要获取数据类型的值
+ */
+export const type = (obj) => {
+	var class2type = {};
+
+	// 生成class2type映射
+	"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
+		class2type["[object " + item + "]"] = item.toLowerCase();
+	})
+	if (obj == null) {
+		return obj + "";
+	}
+	return typeof obj === "object" || typeof obj === "function" ?
+		class2type[Object.prototype.toString.call(obj)] || "object" :
+		typeof obj;
+}
+
+/**
+ * 判断两个值是否相等
+ * @param {any} a 值  
+ * @param {any} b 值  
+ * @return {Boolean} 是否相等
+ */
+export const isEqual = (a, b) => {
+	//如果a和b本来就全等
+	if (a === b) {
+		//判断是否为0和-0
+		return a !== 0 || 1 / a === 1 / b;
+	}
+	//判断是否为null和undefined
+	if (a == null || b == null) {
+		return a === b;
+	}
+	//接下来判断a和b的数据类型
+	var classNameA = toString.call(a),
+		classNameB = toString.call(b);
+	//如果数据类型不相等,则返回false
+	if (classNameA !== classNameB) {
+		return false;
+	}
+	//如果数据类型相等,再根据不同数据类型分别判断
+	switch (classNameA) {
+		case '[object RegExp]':
+		case '[object String]':
+			//进行字符串转换比较
+			return '' + a === '' + b;
+		case '[object Number]':
+			//进行数字转换比较,判断是否为NaN
+			if (+a !== +a) {
+				return +b !== +b;
+			}
+			//判断是否为0或-0
+			return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+		case '[object Date]':
+		case '[object Boolean]':
+			return +a === +b;
+	}
+	//如果是对象类型
+	if (classNameA == '[object Object]') {
+		//获取a和b的属性长度
+		var propsA = Object.getOwnPropertyNames(a),
+			propsB = Object.getOwnPropertyNames(b);
+		if (propsA.length != propsB.length) {
+			return false;
+		}
+		for (var i = 0; i < propsA.length; i++) {
+			var propName = propsA[i];
+			//如果对应属性对应值不相等,则返回false
+			if (a[propName] !== b[propName]) {
+				return false;
+			}
+		}
+		return true;
+	}
+	//如果是数组类型
+	if (classNameA == '[object Array]') {
+		if (a.toString() == b.toString()) {
+			return true;
+		}
+		return false;
+	}
+}

+ 486 - 0
uni_modules/uni-forms/components/uni-forms/validate.js

@@ -0,0 +1,486 @@
+var pattern = {
+	email: /^\S+?@\S+?\.\S+?$/,
+	idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
+	url: new RegExp(
+		"^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$",
+		'i')
+};
+
+const FORMAT_MAPPING = {
+	"int": 'integer',
+	"bool": 'boolean',
+	"double": 'number',
+	"long": 'number',
+	"password": 'string'
+	// "fileurls": 'array'
+}
+
+function formatMessage(args, resources = '') {
+	var defaultMessage = ['label']
+	defaultMessage.forEach((item) => {
+		if (args[item] === undefined) {
+			args[item] = ''
+		}
+	})
+
+	let str = resources
+	for (let key in args) {
+		let reg = new RegExp('{' + key + '}')
+		str = str.replace(reg, args[key])
+	}
+	return str
+}
+
+function isEmptyValue(value, type) {
+	if (value === undefined || value === null) {
+		return true;
+	}
+
+	if (typeof value === 'string' && !value) {
+		return true;
+	}
+
+	if (Array.isArray(value) && !value.length) {
+		return true;
+	}
+
+	if (type === 'object' && !Object.keys(value).length) {
+		return true;
+	}
+
+	return false;
+}
+
+const types = {
+	integer(value) {
+		return types.number(value) && parseInt(value, 10) === value;
+	},
+	string(value) {
+		return typeof value === 'string';
+	},
+	number(value) {
+		if (isNaN(value)) {
+			return false;
+		}
+		return typeof value === 'number';
+	},
+	"boolean": function(value) {
+		return typeof value === 'boolean';
+	},
+	"float": function(value) {
+		return types.number(value) && !types.integer(value);
+	},
+	array(value) {
+		return Array.isArray(value);
+	},
+	object(value) {
+		return typeof value === 'object' && !types.array(value);
+	},
+	date(value) {
+		return value instanceof Date;
+	},
+	timestamp(value) {
+		if (!this.integer(value) || Math.abs(value).toString().length > 16) {
+			return false
+		}
+		return true;
+	},
+	file(value) {
+		return typeof value.url === 'string';
+	},
+	email(value) {
+		return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
+	},
+	url(value) {
+		return typeof value === 'string' && !!value.match(pattern.url);
+	},
+	pattern(reg, value) {
+		try {
+			return new RegExp(reg).test(value);
+		} catch (e) {
+			return false;
+		}
+	},
+	method(value) {
+		return typeof value === 'function';
+	},
+	idcard(value) {
+		return typeof value === 'string' && !!value.match(pattern.idcard);
+	},
+	'url-https'(value) {
+		return this.url(value) && value.startsWith('https://');
+	},
+	'url-scheme'(value) {
+		return value.startsWith('://');
+	},
+	'url-web'(value) {
+		return false;
+	}
+}
+
+class RuleValidator {
+
+	constructor(message) {
+		this._message = message
+	}
+
+	async validateRule(fieldKey, fieldValue, value, data, allData) {
+		var result = null
+
+		let rules = fieldValue.rules
+
+		let hasRequired = rules.findIndex((item) => {
+			return item.required
+		})
+		if (hasRequired < 0) {
+			if (value === null || value === undefined) {
+				return result
+			}
+			if (typeof value === 'string' && !value.length) {
+				return result
+			}
+		}
+
+		var message = this._message
+
+		if (rules === undefined) {
+			return message['default']
+		}
+
+		for (var i = 0; i < rules.length; i++) {
+			let rule = rules[i]
+			let vt = this._getValidateType(rule)
+
+			Object.assign(rule, {
+				label: fieldValue.label || `["${fieldKey}"]`
+			})
+
+			if (RuleValidatorHelper[vt]) {
+				result = RuleValidatorHelper[vt](rule, value, message)
+				if (result != null) {
+					break
+				}
+			}
+
+			if (rule.validateExpr) {
+				let now = Date.now()
+				let resultExpr = rule.validateExpr(value, allData, now)
+				if (resultExpr === false) {
+					result = this._getMessage(rule, rule.errorMessage || this._message['default'])
+					break
+				}
+			}
+
+			if (rule.validateFunction) {
+				result = await this.validateFunction(rule, value, data, allData, vt)
+				if (result !== null) {
+					break
+				}
+			}
+		}
+
+		if (result !== null) {
+			result = message.TAG + result
+		}
+
+		return result
+	}
+
+	async validateFunction(rule, value, data, allData, vt) {
+		let result = null
+		try {
+			let callbackMessage = null
+			const res = await rule.validateFunction(rule, value, allData || data, (message) => {
+				callbackMessage = message
+			})
+			if (callbackMessage || (typeof res === 'string' && res) || res === false) {
+				result = this._getMessage(rule, callbackMessage || res, vt)
+			}
+		} catch (e) {
+			result = this._getMessage(rule, e.message, vt)
+		}
+		return result
+	}
+
+	_getMessage(rule, message, vt) {
+		return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default'])
+	}
+
+	_getValidateType(rule) {
+		var result = ''
+		if (rule.required) {
+			result = 'required'
+		} else if (rule.format) {
+			result = 'format'
+		} else if (rule.arrayType) {
+			result = 'arrayTypeFormat'
+		} else if (rule.range) {
+			result = 'range'
+		} else if (rule.maximum !== undefined || rule.minimum !== undefined) {
+			result = 'rangeNumber'
+		} else if (rule.maxLength !== undefined || rule.minLength !== undefined) {
+			result = 'rangeLength'
+		} else if (rule.pattern) {
+			result = 'pattern'
+		} else if (rule.validateFunction) {
+			result = 'validateFunction'
+		}
+		return result
+	}
+}
+
+const RuleValidatorHelper = {
+	required(rule, value, message) {
+		if (rule.required && isEmptyValue(value, rule.format || typeof value)) {
+			return formatMessage(rule, rule.errorMessage || message.required);
+		}
+
+		return null
+	},
+
+	range(rule, value, message) {
+		const {
+			range,
+			errorMessage
+		} = rule;
+
+		let list = new Array(range.length);
+		for (let i = 0; i < range.length; i++) {
+			const item = range[i];
+			if (types.object(item) && item.value !== undefined) {
+				list[i] = item.value;
+			} else {
+				list[i] = item;
+			}
+		}
+
+		let result = false
+		if (Array.isArray(value)) {
+			result = (new Set(value.concat(list)).size === list.length);
+		} else {
+			if (list.indexOf(value) > -1) {
+				result = true;
+			}
+		}
+
+		if (!result) {
+			return formatMessage(rule, errorMessage || message['enum']);
+		}
+
+		return null
+	},
+
+	rangeNumber(rule, value, message) {
+		if (!types.number(value)) {
+			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
+		}
+
+		let {
+			minimum,
+			maximum,
+			exclusiveMinimum,
+			exclusiveMaximum
+		} = rule;
+		let min = exclusiveMinimum ? value <= minimum : value < minimum;
+		let max = exclusiveMaximum ? value >= maximum : value > maximum;
+
+		if (minimum !== undefined && min) {
+			return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ?
+				'exclusiveMinimum' : 'minimum'
+			])
+		} else if (maximum !== undefined && max) {
+			return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ?
+				'exclusiveMaximum' : 'maximum'
+			])
+		} else if (minimum !== undefined && maximum !== undefined && (min || max)) {
+			return formatMessage(rule, rule.errorMessage || message['number'].range)
+		}
+
+		return null
+	},
+
+	rangeLength(rule, value, message) {
+		if (!types.string(value) && !types.array(value)) {
+			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
+		}
+
+		let min = rule.minLength;
+		let max = rule.maxLength;
+		let val = value.length;
+
+		if (min !== undefined && val < min) {
+			return formatMessage(rule, rule.errorMessage || message['length'].minLength)
+		} else if (max !== undefined && val > max) {
+			return formatMessage(rule, rule.errorMessage || message['length'].maxLength)
+		} else if (min !== undefined && max !== undefined && (val < min || val > max)) {
+			return formatMessage(rule, rule.errorMessage || message['length'].range)
+		}
+
+		return null
+	},
+
+	pattern(rule, value, message) {
+		if (!types['pattern'](rule.pattern, value)) {
+			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
+		}
+
+		return null
+	},
+
+	format(rule, value, message) {
+		var customTypes = Object.keys(types);
+		var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType);
+
+		if (customTypes.indexOf(format) > -1) {
+			if (!types[format](value)) {
+				return formatMessage(rule, rule.errorMessage || message.typeError);
+			}
+		}
+
+		return null
+	},
+
+	arrayTypeFormat(rule, value, message) {
+		if (!Array.isArray(value)) {
+			return formatMessage(rule, rule.errorMessage || message.typeError);
+		}
+
+		for (let i = 0; i < value.length; i++) {
+			const element = value[i];
+			let formatResult = this.format(rule, element, message)
+			if (formatResult !== null) {
+				return formatResult
+			}
+		}
+
+		return null
+	}
+}
+
+class SchemaValidator extends RuleValidator {
+
+	constructor(schema, options) {
+		super(SchemaValidator.message);
+
+		this._schema = schema
+		this._options = options || null
+	}
+
+	updateSchema(schema) {
+		this._schema = schema
+	}
+
+	async validate(data, allData) {
+		let result = this._checkFieldInSchema(data)
+		if (!result) {
+			result = await this.invokeValidate(data, false, allData)
+		}
+		return result.length ? result[0] : null
+	}
+
+	async validateAll(data, allData) {
+		let result = this._checkFieldInSchema(data)
+		if (!result) {
+			result = await this.invokeValidate(data, true, allData)
+		}
+		return result
+	}
+
+	async validateUpdate(data, allData) {
+		let result = this._checkFieldInSchema(data)
+		if (!result) {
+			result = await this.invokeValidateUpdate(data, false, allData)
+		}
+		return result.length ? result[0] : null
+	}
+
+	async invokeValidate(data, all, allData) {
+		let result = []
+		let schema = this._schema
+		for (let key in schema) {
+			let value = schema[key]
+			let errorMessage = await this.validateRule(key, value, data[key], data, allData)
+			if (errorMessage != null) {
+				result.push({
+					key,
+					errorMessage
+				})
+				if (!all) break
+			}
+		}
+		return result
+	}
+
+	async invokeValidateUpdate(data, all, allData) {
+		let result = []
+		for (let key in data) {
+			let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData)
+			if (errorMessage != null) {
+				result.push({
+					key,
+					errorMessage
+				})
+				if (!all) break
+			}
+		}
+		return result
+	}
+
+	_checkFieldInSchema(data) {
+		var keys = Object.keys(data)
+		var keys2 = Object.keys(this._schema)
+		if (new Set(keys.concat(keys2)).size === keys2.length) {
+			return ''
+		}
+
+		var noExistFields = keys.filter((key) => {
+			return keys2.indexOf(key) < 0;
+		})
+		var errorMessage = formatMessage({
+			field: JSON.stringify(noExistFields)
+		}, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid'])
+		return [{
+			key: 'invalid',
+			errorMessage
+		}]
+	}
+}
+
+function Message() {
+	return {
+		TAG: "",
+		default: '验证错误',
+		defaultInvalid: '提交的字段{field}在数据库中并不存在',
+		validateFunction: '验证无效',
+		required: '{label}必填',
+		'enum': '{label}超出范围',
+		timestamp: '{label}格式无效',
+		whitespace: '{label}不能为空',
+		typeError: '{label}类型无效',
+		date: {
+			format: '{label}日期{value}格式无效',
+			parse: '{label}日期无法解析,{value}无效',
+			invalid: '{label}日期{value}无效'
+		},
+		length: {
+			minLength: '{label}长度不能少于{minLength}',
+			maxLength: '{label}长度不能超过{maxLength}',
+			range: '{label}必须介于{minLength}和{maxLength}之间'
+		},
+		number: {
+			minimum: '{label}不能小于{minimum}',
+			maximum: '{label}不能大于{maximum}',
+			exclusiveMinimum: '{label}不能小于等于{minimum}',
+			exclusiveMaximum: '{label}不能大于等于{maximum}',
+			range: '{label}必须介于{minimum}and{maximum}之间'
+		},
+		pattern: {
+			mismatch: '{label}格式不匹配'
+		}
+	};
+}
+
+
+SchemaValidator.message = new Message();
+
+export default SchemaValidator

+ 88 - 0
uni_modules/uni-forms/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uni-forms",
+  "displayName": "uni-forms 表单",
+  "version": "1.4.10",
+  "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据",
+  "keywords": [
+    "uni-ui",
+    "表单",
+    "校验",
+    "表单校验",
+    "表单验证"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uni-scss",
+      "uni-icons"
+    ],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+        "QQ": "y",
+        "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 23 - 0
uni_modules/uni-forms/readme.md

@@ -0,0 +1,23 @@
+
+
+## Forms 表单
+
+> **组件名:uni-forms**
+> 代码块: `uForms`、`uni-forms-item`
+> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。
+
+
+uni-app的内置组件已经有了 `<form>`组件,用于提交表单内容。
+
+然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 `<form>`组件封装了 `<uni-forms>`组件,内置了表单验证功能。
+
+`<uni-forms>` 提供了 `rules`属性来描述校验规则、`<uni-forms-item>`子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。
+
+每个要校验的表单项,不管input还是checkbox,都必须放在`<uni-forms-item>`组件中,且一个`<uni-forms-item>`组件只能放置一个表单项。
+
+`<uni-forms-item>`组件内部预留了显示error message的区域,默认是在表单项的底部。
+
+另外,`<uni-forms>`组件下面的各个表单项,可以通过`<uni-group>`包裹为不同的分组。同一`<uni-group>`下的不同表单项目将聚拢在一起,同其他group保持垂直间距。`<uni-group>`仅影响视觉效果。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-forms)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 40 - 0
uni_modules/uni-icons/changelog.md

@@ -0,0 +1,40 @@
+## 2.0.9(2024-01-12)
+fix: 修复图标大小默认值错误的问题
+## 2.0.8(2023-12-14)
+- 修复 项目未使用 ts 情况下,打包报错的bug
+## 2.0.7(2023-12-14)
+- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug
+## 2.0.6(2023-12-11)
+- 优化 兼容老版本icon类型,如 top ,bottom 等
+## 2.0.5(2023-12-11)
+- 优化 兼容老版本icon类型,如 top ,bottom 等
+## 2.0.4(2023-12-06)
+- 优化 uni-app x 下示例项目图标排序
+## 2.0.3(2023-12-06)
+- 修复 nvue下引入组件报错的bug
+## 2.0.2(2023-12-05)
+-优化 size 属性支持单位
+## 2.0.1(2023-12-05)
+- 新增 uni-app x 支持定义图标
+## 1.3.5(2022-01-24)
+- 优化 size 属性可以传入不带单位的字符串数值
+## 1.3.4(2022-01-24)
+- 优化 size 支持其他单位
+## 1.3.3(2022-01-17)
+- 修复 nvue 有些图标不显示的bug,兼容老版本图标
+## 1.3.2(2021-12-01)
+- 优化 示例可复制图标名称
+## 1.3.1(2021-11-23)
+- 优化 兼容旧组件 type 值
+## 1.3.0(2021-11-19)
+- 新增 更多图标
+- 优化 自定义图标使用方式
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
+## 1.1.7(2021-11-08)
+## 1.2.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.5(2021-05-12)
+- 新增 组件示例地址
+## 1.1.4(2021-02-05)
+- 调整为uni_modules目录规范

+ 91 - 0
uni_modules/uni-icons/components/uni-icons/uni-icons.uvue

@@ -0,0 +1,91 @@
+<template>
+	<text class="uni-icons" :style="styleObj">
+		<slot>{{unicode}}</slot>
+	</text>
+</template>
+
+<script>
+	import { fontData, IconsDataItem } from './uniicons_file'
+
+	/**
+	 * Icons 图标
+	 * @description 用于展示 icon 图标
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=28
+	 * @property {Number} size 图标大小
+	 * @property {String} type 图标图案,参考示例
+	 * @property {String} color 图标颜色
+	 * @property {String} customPrefix 自定义图标
+	 * @event {Function} click 点击 Icon 触发事件
+	 */
+	export default {
+		name: "uni-icons",
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			color: {
+				type: String,
+				default: '#333333'
+			},
+			size: {
+				type: Object,
+				default: 16
+			},
+			fontFamily: {
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {};
+		},
+		computed: {
+			unicode() : string {
+				let codes = fontData.find((item : IconsDataItem) : boolean => { return item.font_class == this.type })
+				if (codes !== null) {
+					return codes.unicode
+				}
+				return ''
+			},
+			iconSize() : string {
+				const size = this.size
+				if (typeof size == 'string') {
+					const reg = /^[0-9]*$/g
+					return reg.test(size as string) ? '' + size + 'px' : '' + size;
+					// return '' + this.size
+				}
+				return this.getFontSize(size as number)
+			},
+			styleObj() : UTSJSONObject {
+				if (this.fontFamily !== '') {
+					return { color: this.color, fontSize: this.iconSize, fontFamily: this.fontFamily }
+				}
+				return { color: this.color, fontSize: this.iconSize }
+			}
+		},
+		created() { },
+		methods: {
+			/**
+			 * 字体大小
+			 */
+			getFontSize(size : number) : string {
+				return size + 'px';
+			},
+		},
+	}
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: UniIconsFontFamily;
+		src: url('./uniicons.ttf');
+	}
+
+	.uni-icons {
+		font-family: UniIconsFontFamily;
+		font-size: 18px;
+		font-style: normal;
+		color: #333;
+	}
+</style>

+ 110 - 0
uni_modules/uni-icons/components/uni-icons/uni-icons.vue

@@ -0,0 +1,110 @@
+<template>
+	<!-- #ifdef APP-NVUE -->
+	<text :style="styleObj" class="uni-icons" @click="_onClick">{{unicode}}</text>
+	<!-- #endif -->
+	<!-- #ifndef APP-NVUE -->
+	<text :style="styleObj" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick">
+		<slot></slot>
+	</text>
+	<!-- #endif -->
+</template>
+
+<script>
+	import { fontData } from './uniicons_file_vue.js';
+
+	const getVal = (val) => {
+		const reg = /^[0-9]*$/g
+		return (typeof val === 'number' || reg.test(val)) ? val + 'px' : val;
+	}
+
+	// #ifdef APP-NVUE
+	var domModule = weex.requireModule('dom');
+	import iconUrl from './uniicons.ttf'
+	domModule.addRule('fontFace', {
+		'fontFamily': "uniicons",
+		'src': "url('" + iconUrl + "')"
+	});
+	// #endif
+
+	/**
+	 * Icons 图标
+	 * @description 用于展示 icons 图标
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=28
+	 * @property {Number} size 图标大小
+	 * @property {String} type 图标图案,参考示例
+	 * @property {String} color 图标颜色
+	 * @property {String} customPrefix 自定义图标
+	 * @event {Function} click 点击 Icon 触发事件
+	 */
+	export default {
+		name: 'UniIcons',
+		emits: ['click'],
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			color: {
+				type: String,
+				default: '#333333'
+			},
+			size: {
+				type: [Number, String],
+				default: 16
+			},
+			customPrefix: {
+				type: String,
+				default: ''
+			},
+			fontFamily: {
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				icons: fontData
+			}
+		},
+		computed: {
+			unicode() {
+				let code = this.icons.find(v => v.font_class === this.type)
+				if (code) {
+					return code.unicode
+				}
+				return ''
+			},
+			iconSize() {
+				return getVal(this.size)
+			},
+			styleObj() {
+				if (this.fontFamily !== '') {
+					return `color: ${this.color}; font-size: ${this.iconSize}; font-family: ${this.fontFamily};`
+				}
+				return `color: ${this.color}; font-size: ${this.iconSize};`
+			}
+		},
+		methods: {
+			_onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	/* #ifndef APP-NVUE */
+	@import './uniicons.css';
+
+	@font-face {
+		font-family: uniicons;
+		src: url('./uniicons.ttf');
+	}
+
+	/* #endif */
+	.uni-icons {
+		font-family: uniicons;
+		text-decoration: none;
+		text-align: center;
+	}
+</style>

+ 664 - 0
uni_modules/uni-icons/components/uni-icons/uniicons.css

@@ -0,0 +1,664 @@
+
+.uniui-cart-filled:before {
+  content: "\e6d0";
+}
+
+.uniui-gift-filled:before {
+  content: "\e6c4";
+}
+
+.uniui-color:before {
+  content: "\e6cf";
+}
+
+.uniui-wallet:before {
+  content: "\e6b1";
+}
+
+.uniui-settings-filled:before {
+  content: "\e6ce";
+}
+
+.uniui-auth-filled:before {
+  content: "\e6cc";
+}
+
+.uniui-shop-filled:before {
+  content: "\e6cd";
+}
+
+.uniui-staff-filled:before {
+  content: "\e6cb";
+}
+
+.uniui-vip-filled:before {
+  content: "\e6c6";
+}
+
+.uniui-plus-filled:before {
+  content: "\e6c7";
+}
+
+.uniui-folder-add-filled:before {
+  content: "\e6c8";
+}
+
+.uniui-color-filled:before {
+  content: "\e6c9";
+}
+
+.uniui-tune-filled:before {
+  content: "\e6ca";
+}
+
+.uniui-calendar-filled:before {
+  content: "\e6c0";
+}
+
+.uniui-notification-filled:before {
+  content: "\e6c1";
+}
+
+.uniui-wallet-filled:before {
+  content: "\e6c2";
+}
+
+.uniui-medal-filled:before {
+  content: "\e6c3";
+}
+
+.uniui-fire-filled:before {
+  content: "\e6c5";
+}
+
+.uniui-refreshempty:before {
+  content: "\e6bf";
+}
+
+.uniui-location-filled:before {
+  content: "\e6af";
+}
+
+.uniui-person-filled:before {
+  content: "\e69d";
+}
+
+.uniui-personadd-filled:before {
+  content: "\e698";
+}
+
+.uniui-arrowthinleft:before {
+  content: "\e6d2";
+}
+
+.uniui-arrowthinup:before {
+  content: "\e6d3";
+}
+
+.uniui-arrowthindown:before {
+  content: "\e6d4";
+}
+
+.uniui-back:before {
+  content: "\e6b9";
+}
+
+.uniui-forward:before {
+  content: "\e6ba";
+}
+
+.uniui-arrow-right:before {
+  content: "\e6bb";
+}
+
+.uniui-arrow-left:before {
+  content: "\e6bc";
+}
+
+.uniui-arrow-up:before {
+  content: "\e6bd";
+}
+
+.uniui-arrow-down:before {
+  content: "\e6be";
+}
+
+.uniui-arrowthinright:before {
+  content: "\e6d1";
+}
+
+.uniui-down:before {
+  content: "\e6b8";
+}
+
+.uniui-bottom:before {
+  content: "\e6b8";
+}
+
+.uniui-arrowright:before {
+  content: "\e6d5";
+}
+
+.uniui-right:before {
+  content: "\e6b5";
+}
+
+.uniui-up:before {
+  content: "\e6b6";
+}
+
+.uniui-top:before {
+  content: "\e6b6";
+}
+
+.uniui-left:before {
+  content: "\e6b7";
+}
+
+.uniui-arrowup:before {
+  content: "\e6d6";
+}
+
+.uniui-eye:before {
+  content: "\e651";
+}
+
+.uniui-eye-filled:before {
+  content: "\e66a";
+}
+
+.uniui-eye-slash:before {
+  content: "\e6b3";
+}
+
+.uniui-eye-slash-filled:before {
+  content: "\e6b4";
+}
+
+.uniui-info-filled:before {
+  content: "\e649";
+}
+
+.uniui-reload:before {
+  content: "\e6b2";
+}
+
+.uniui-micoff-filled:before {
+  content: "\e6b0";
+}
+
+.uniui-map-pin-ellipse:before {
+  content: "\e6ac";
+}
+
+.uniui-map-pin:before {
+  content: "\e6ad";
+}
+
+.uniui-location:before {
+  content: "\e6ae";
+}
+
+.uniui-starhalf:before {
+  content: "\e683";
+}
+
+.uniui-star:before {
+  content: "\e688";
+}
+
+.uniui-star-filled:before {
+  content: "\e68f";
+}
+
+.uniui-calendar:before {
+  content: "\e6a0";
+}
+
+.uniui-fire:before {
+  content: "\e6a1";
+}
+
+.uniui-medal:before {
+  content: "\e6a2";
+}
+
+.uniui-font:before {
+  content: "\e6a3";
+}
+
+.uniui-gift:before {
+  content: "\e6a4";
+}
+
+.uniui-link:before {
+  content: "\e6a5";
+}
+
+.uniui-notification:before {
+  content: "\e6a6";
+}
+
+.uniui-staff:before {
+  content: "\e6a7";
+}
+
+.uniui-vip:before {
+  content: "\e6a8";
+}
+
+.uniui-folder-add:before {
+  content: "\e6a9";
+}
+
+.uniui-tune:before {
+  content: "\e6aa";
+}
+
+.uniui-auth:before {
+  content: "\e6ab";
+}
+
+.uniui-person:before {
+  content: "\e699";
+}
+
+.uniui-email-filled:before {
+  content: "\e69a";
+}
+
+.uniui-phone-filled:before {
+  content: "\e69b";
+}
+
+.uniui-phone:before {
+  content: "\e69c";
+}
+
+.uniui-email:before {
+  content: "\e69e";
+}
+
+.uniui-personadd:before {
+  content: "\e69f";
+}
+
+.uniui-chatboxes-filled:before {
+  content: "\e692";
+}
+
+.uniui-contact:before {
+  content: "\e693";
+}
+
+.uniui-chatbubble-filled:before {
+  content: "\e694";
+}
+
+.uniui-contact-filled:before {
+  content: "\e695";
+}
+
+.uniui-chatboxes:before {
+  content: "\e696";
+}
+
+.uniui-chatbubble:before {
+  content: "\e697";
+}
+
+.uniui-upload-filled:before {
+  content: "\e68e";
+}
+
+.uniui-upload:before {
+  content: "\e690";
+}
+
+.uniui-weixin:before {
+  content: "\e691";
+}
+
+.uniui-compose:before {
+  content: "\e67f";
+}
+
+.uniui-qq:before {
+  content: "\e680";
+}
+
+.uniui-download-filled:before {
+  content: "\e681";
+}
+
+.uniui-pyq:before {
+  content: "\e682";
+}
+
+.uniui-sound:before {
+  content: "\e684";
+}
+
+.uniui-trash-filled:before {
+  content: "\e685";
+}
+
+.uniui-sound-filled:before {
+  content: "\e686";
+}
+
+.uniui-trash:before {
+  content: "\e687";
+}
+
+.uniui-videocam-filled:before {
+  content: "\e689";
+}
+
+.uniui-spinner-cycle:before {
+  content: "\e68a";
+}
+
+.uniui-weibo:before {
+  content: "\e68b";
+}
+
+.uniui-videocam:before {
+  content: "\e68c";
+}
+
+.uniui-download:before {
+  content: "\e68d";
+}
+
+.uniui-help:before {
+  content: "\e679";
+}
+
+.uniui-navigate-filled:before {
+  content: "\e67a";
+}
+
+.uniui-plusempty:before {
+  content: "\e67b";
+}
+
+.uniui-smallcircle:before {
+  content: "\e67c";
+}
+
+.uniui-minus-filled:before {
+  content: "\e67d";
+}
+
+.uniui-micoff:before {
+  content: "\e67e";
+}
+
+.uniui-closeempty:before {
+  content: "\e66c";
+}
+
+.uniui-clear:before {
+  content: "\e66d";
+}
+
+.uniui-navigate:before {
+  content: "\e66e";
+}
+
+.uniui-minus:before {
+  content: "\e66f";
+}
+
+.uniui-image:before {
+  content: "\e670";
+}
+
+.uniui-mic:before {
+  content: "\e671";
+}
+
+.uniui-paperplane:before {
+  content: "\e672";
+}
+
+.uniui-close:before {
+  content: "\e673";
+}
+
+.uniui-help-filled:before {
+  content: "\e674";
+}
+
+.uniui-paperplane-filled:before {
+  content: "\e675";
+}
+
+.uniui-plus:before {
+  content: "\e676";
+}
+
+.uniui-mic-filled:before {
+  content: "\e677";
+}
+
+.uniui-image-filled:before {
+  content: "\e678";
+}
+
+.uniui-locked-filled:before {
+  content: "\e668";
+}
+
+.uniui-info:before {
+  content: "\e669";
+}
+
+.uniui-locked:before {
+  content: "\e66b";
+}
+
+.uniui-camera-filled:before {
+  content: "\e658";
+}
+
+.uniui-chat-filled:before {
+  content: "\e659";
+}
+
+.uniui-camera:before {
+  content: "\e65a";
+}
+
+.uniui-circle:before {
+  content: "\e65b";
+}
+
+.uniui-checkmarkempty:before {
+  content: "\e65c";
+}
+
+.uniui-chat:before {
+  content: "\e65d";
+}
+
+.uniui-circle-filled:before {
+  content: "\e65e";
+}
+
+.uniui-flag:before {
+  content: "\e65f";
+}
+
+.uniui-flag-filled:before {
+  content: "\e660";
+}
+
+.uniui-gear-filled:before {
+  content: "\e661";
+}
+
+.uniui-home:before {
+  content: "\e662";
+}
+
+.uniui-home-filled:before {
+  content: "\e663";
+}
+
+.uniui-gear:before {
+  content: "\e664";
+}
+
+.uniui-smallcircle-filled:before {
+  content: "\e665";
+}
+
+.uniui-map-filled:before {
+  content: "\e666";
+}
+
+.uniui-map:before {
+  content: "\e667";
+}
+
+.uniui-refresh-filled:before {
+  content: "\e656";
+}
+
+.uniui-refresh:before {
+  content: "\e657";
+}
+
+.uniui-cloud-upload:before {
+  content: "\e645";
+}
+
+.uniui-cloud-download-filled:before {
+  content: "\e646";
+}
+
+.uniui-cloud-download:before {
+  content: "\e647";
+}
+
+.uniui-cloud-upload-filled:before {
+  content: "\e648";
+}
+
+.uniui-redo:before {
+  content: "\e64a";
+}
+
+.uniui-images-filled:before {
+  content: "\e64b";
+}
+
+.uniui-undo-filled:before {
+  content: "\e64c";
+}
+
+.uniui-more:before {
+  content: "\e64d";
+}
+
+.uniui-more-filled:before {
+  content: "\e64e";
+}
+
+.uniui-undo:before {
+  content: "\e64f";
+}
+
+.uniui-images:before {
+  content: "\e650";
+}
+
+.uniui-paperclip:before {
+  content: "\e652";
+}
+
+.uniui-settings:before {
+  content: "\e653";
+}
+
+.uniui-search:before {
+  content: "\e654";
+}
+
+.uniui-redo-filled:before {
+  content: "\e655";
+}
+
+.uniui-list:before {
+  content: "\e644";
+}
+
+.uniui-mail-open-filled:before {
+  content: "\e63a";
+}
+
+.uniui-hand-down-filled:before {
+  content: "\e63c";
+}
+
+.uniui-hand-down:before {
+  content: "\e63d";
+}
+
+.uniui-hand-up-filled:before {
+  content: "\e63e";
+}
+
+.uniui-hand-up:before {
+  content: "\e63f";
+}
+
+.uniui-heart-filled:before {
+  content: "\e641";
+}
+
+.uniui-mail-open:before {
+  content: "\e643";
+}
+
+.uniui-heart:before {
+  content: "\e639";
+}
+
+.uniui-loop:before {
+  content: "\e633";
+}
+
+.uniui-pulldown:before {
+  content: "\e632";
+}
+
+.uniui-scan:before {
+  content: "\e62a";
+}
+
+.uniui-bars:before {
+  content: "\e627";
+}
+
+.uniui-checkbox:before {
+  content: "\e62b";
+}
+
+.uniui-checkbox-filled:before {
+  content: "\e62c";
+}
+
+.uniui-shop:before {
+  content: "\e62f";
+}
+
+.uniui-headphones:before {
+  content: "\e630";
+}
+
+.uniui-cart:before {
+  content: "\e631";
+}

BIN
uni_modules/uni-icons/components/uni-icons/uniicons.ttf


+ 664 - 0
uni_modules/uni-icons/components/uni-icons/uniicons_file.ts

@@ -0,0 +1,664 @@
+
+export type IconsData = {
+	id : string
+	name : string
+	font_family : string
+	css_prefix_text : string
+	description : string
+	glyphs : Array<IconsDataItem>
+}
+
+export type IconsDataItem = {
+	font_class : string
+	unicode : string
+}
+
+
+export const fontData = [
+  {
+    "font_class": "arrow-down",
+    "unicode": "\ue6be"
+  },
+  {
+    "font_class": "arrow-left",
+    "unicode": "\ue6bc"
+  },
+  {
+    "font_class": "arrow-right",
+    "unicode": "\ue6bb"
+  },
+  {
+    "font_class": "arrow-up",
+    "unicode": "\ue6bd"
+  },
+  {
+    "font_class": "auth",
+    "unicode": "\ue6ab"
+  },
+  {
+    "font_class": "auth-filled",
+    "unicode": "\ue6cc"
+  },
+  {
+    "font_class": "back",
+    "unicode": "\ue6b9"
+  },
+  {
+    "font_class": "bars",
+    "unicode": "\ue627"
+  },
+  {
+    "font_class": "calendar",
+    "unicode": "\ue6a0"
+  },
+  {
+    "font_class": "calendar-filled",
+    "unicode": "\ue6c0"
+  },
+  {
+    "font_class": "camera",
+    "unicode": "\ue65a"
+  },
+  {
+    "font_class": "camera-filled",
+    "unicode": "\ue658"
+  },
+  {
+    "font_class": "cart",
+    "unicode": "\ue631"
+  },
+  {
+    "font_class": "cart-filled",
+    "unicode": "\ue6d0"
+  },
+  {
+    "font_class": "chat",
+    "unicode": "\ue65d"
+  },
+  {
+    "font_class": "chat-filled",
+    "unicode": "\ue659"
+  },
+  {
+    "font_class": "chatboxes",
+    "unicode": "\ue696"
+  },
+  {
+    "font_class": "chatboxes-filled",
+    "unicode": "\ue692"
+  },
+  {
+    "font_class": "chatbubble",
+    "unicode": "\ue697"
+  },
+  {
+    "font_class": "chatbubble-filled",
+    "unicode": "\ue694"
+  },
+  {
+    "font_class": "checkbox",
+    "unicode": "\ue62b"
+  },
+  {
+    "font_class": "checkbox-filled",
+    "unicode": "\ue62c"
+  },
+  {
+    "font_class": "checkmarkempty",
+    "unicode": "\ue65c"
+  },
+  {
+    "font_class": "circle",
+    "unicode": "\ue65b"
+  },
+  {
+    "font_class": "circle-filled",
+    "unicode": "\ue65e"
+  },
+  {
+    "font_class": "clear",
+    "unicode": "\ue66d"
+  },
+  {
+    "font_class": "close",
+    "unicode": "\ue673"
+  },
+  {
+    "font_class": "closeempty",
+    "unicode": "\ue66c"
+  },
+  {
+    "font_class": "cloud-download",
+    "unicode": "\ue647"
+  },
+  {
+    "font_class": "cloud-download-filled",
+    "unicode": "\ue646"
+  },
+  {
+    "font_class": "cloud-upload",
+    "unicode": "\ue645"
+  },
+  {
+    "font_class": "cloud-upload-filled",
+    "unicode": "\ue648"
+  },
+  {
+    "font_class": "color",
+    "unicode": "\ue6cf"
+  },
+  {
+    "font_class": "color-filled",
+    "unicode": "\ue6c9"
+  },
+  {
+    "font_class": "compose",
+    "unicode": "\ue67f"
+  },
+  {
+    "font_class": "contact",
+    "unicode": "\ue693"
+  },
+  {
+    "font_class": "contact-filled",
+    "unicode": "\ue695"
+  },
+  {
+    "font_class": "down",
+    "unicode": "\ue6b8"
+  },
+	{
+	  "font_class": "bottom",
+	  "unicode": "\ue6b8"
+	},
+  {
+    "font_class": "download",
+    "unicode": "\ue68d"
+  },
+  {
+    "font_class": "download-filled",
+    "unicode": "\ue681"
+  },
+  {
+    "font_class": "email",
+    "unicode": "\ue69e"
+  },
+  {
+    "font_class": "email-filled",
+    "unicode": "\ue69a"
+  },
+  {
+    "font_class": "eye",
+    "unicode": "\ue651"
+  },
+  {
+    "font_class": "eye-filled",
+    "unicode": "\ue66a"
+  },
+  {
+    "font_class": "eye-slash",
+    "unicode": "\ue6b3"
+  },
+  {
+    "font_class": "eye-slash-filled",
+    "unicode": "\ue6b4"
+  },
+  {
+    "font_class": "fire",
+    "unicode": "\ue6a1"
+  },
+  {
+    "font_class": "fire-filled",
+    "unicode": "\ue6c5"
+  },
+  {
+    "font_class": "flag",
+    "unicode": "\ue65f"
+  },
+  {
+    "font_class": "flag-filled",
+    "unicode": "\ue660"
+  },
+  {
+    "font_class": "folder-add",
+    "unicode": "\ue6a9"
+  },
+  {
+    "font_class": "folder-add-filled",
+    "unicode": "\ue6c8"
+  },
+  {
+    "font_class": "font",
+    "unicode": "\ue6a3"
+  },
+  {
+    "font_class": "forward",
+    "unicode": "\ue6ba"
+  },
+  {
+    "font_class": "gear",
+    "unicode": "\ue664"
+  },
+  {
+    "font_class": "gear-filled",
+    "unicode": "\ue661"
+  },
+  {
+    "font_class": "gift",
+    "unicode": "\ue6a4"
+  },
+  {
+    "font_class": "gift-filled",
+    "unicode": "\ue6c4"
+  },
+  {
+    "font_class": "hand-down",
+    "unicode": "\ue63d"
+  },
+  {
+    "font_class": "hand-down-filled",
+    "unicode": "\ue63c"
+  },
+  {
+    "font_class": "hand-up",
+    "unicode": "\ue63f"
+  },
+  {
+    "font_class": "hand-up-filled",
+    "unicode": "\ue63e"
+  },
+  {
+    "font_class": "headphones",
+    "unicode": "\ue630"
+  },
+  {
+    "font_class": "heart",
+    "unicode": "\ue639"
+  },
+  {
+    "font_class": "heart-filled",
+    "unicode": "\ue641"
+  },
+  {
+    "font_class": "help",
+    "unicode": "\ue679"
+  },
+  {
+    "font_class": "help-filled",
+    "unicode": "\ue674"
+  },
+  {
+    "font_class": "home",
+    "unicode": "\ue662"
+  },
+  {
+    "font_class": "home-filled",
+    "unicode": "\ue663"
+  },
+  {
+    "font_class": "image",
+    "unicode": "\ue670"
+  },
+  {
+    "font_class": "image-filled",
+    "unicode": "\ue678"
+  },
+  {
+    "font_class": "images",
+    "unicode": "\ue650"
+  },
+  {
+    "font_class": "images-filled",
+    "unicode": "\ue64b"
+  },
+  {
+    "font_class": "info",
+    "unicode": "\ue669"
+  },
+  {
+    "font_class": "info-filled",
+    "unicode": "\ue649"
+  },
+  {
+    "font_class": "left",
+    "unicode": "\ue6b7"
+  },
+  {
+    "font_class": "link",
+    "unicode": "\ue6a5"
+  },
+  {
+    "font_class": "list",
+    "unicode": "\ue644"
+  },
+  {
+    "font_class": "location",
+    "unicode": "\ue6ae"
+  },
+  {
+    "font_class": "location-filled",
+    "unicode": "\ue6af"
+  },
+  {
+    "font_class": "locked",
+    "unicode": "\ue66b"
+  },
+  {
+    "font_class": "locked-filled",
+    "unicode": "\ue668"
+  },
+  {
+    "font_class": "loop",
+    "unicode": "\ue633"
+  },
+  {
+    "font_class": "mail-open",
+    "unicode": "\ue643"
+  },
+  {
+    "font_class": "mail-open-filled",
+    "unicode": "\ue63a"
+  },
+  {
+    "font_class": "map",
+    "unicode": "\ue667"
+  },
+  {
+    "font_class": "map-filled",
+    "unicode": "\ue666"
+  },
+  {
+    "font_class": "map-pin",
+    "unicode": "\ue6ad"
+  },
+  {
+    "font_class": "map-pin-ellipse",
+    "unicode": "\ue6ac"
+  },
+  {
+    "font_class": "medal",
+    "unicode": "\ue6a2"
+  },
+  {
+    "font_class": "medal-filled",
+    "unicode": "\ue6c3"
+  },
+  {
+    "font_class": "mic",
+    "unicode": "\ue671"
+  },
+  {
+    "font_class": "mic-filled",
+    "unicode": "\ue677"
+  },
+  {
+    "font_class": "micoff",
+    "unicode": "\ue67e"
+  },
+  {
+    "font_class": "micoff-filled",
+    "unicode": "\ue6b0"
+  },
+  {
+    "font_class": "minus",
+    "unicode": "\ue66f"
+  },
+  {
+    "font_class": "minus-filled",
+    "unicode": "\ue67d"
+  },
+  {
+    "font_class": "more",
+    "unicode": "\ue64d"
+  },
+  {
+    "font_class": "more-filled",
+    "unicode": "\ue64e"
+  },
+  {
+    "font_class": "navigate",
+    "unicode": "\ue66e"
+  },
+  {
+    "font_class": "navigate-filled",
+    "unicode": "\ue67a"
+  },
+  {
+    "font_class": "notification",
+    "unicode": "\ue6a6"
+  },
+  {
+    "font_class": "notification-filled",
+    "unicode": "\ue6c1"
+  },
+  {
+    "font_class": "paperclip",
+    "unicode": "\ue652"
+  },
+  {
+    "font_class": "paperplane",
+    "unicode": "\ue672"
+  },
+  {
+    "font_class": "paperplane-filled",
+    "unicode": "\ue675"
+  },
+  {
+    "font_class": "person",
+    "unicode": "\ue699"
+  },
+  {
+    "font_class": "person-filled",
+    "unicode": "\ue69d"
+  },
+  {
+    "font_class": "personadd",
+    "unicode": "\ue69f"
+  },
+  {
+    "font_class": "personadd-filled",
+    "unicode": "\ue698"
+  },
+  {
+    "font_class": "personadd-filled-copy",
+    "unicode": "\ue6d1"
+  },
+  {
+    "font_class": "phone",
+    "unicode": "\ue69c"
+  },
+  {
+    "font_class": "phone-filled",
+    "unicode": "\ue69b"
+  },
+  {
+    "font_class": "plus",
+    "unicode": "\ue676"
+  },
+  {
+    "font_class": "plus-filled",
+    "unicode": "\ue6c7"
+  },
+  {
+    "font_class": "plusempty",
+    "unicode": "\ue67b"
+  },
+  {
+    "font_class": "pulldown",
+    "unicode": "\ue632"
+  },
+  {
+    "font_class": "pyq",
+    "unicode": "\ue682"
+  },
+  {
+    "font_class": "qq",
+    "unicode": "\ue680"
+  },
+  {
+    "font_class": "redo",
+    "unicode": "\ue64a"
+  },
+  {
+    "font_class": "redo-filled",
+    "unicode": "\ue655"
+  },
+  {
+    "font_class": "refresh",
+    "unicode": "\ue657"
+  },
+  {
+    "font_class": "refresh-filled",
+    "unicode": "\ue656"
+  },
+  {
+    "font_class": "refreshempty",
+    "unicode": "\ue6bf"
+  },
+  {
+    "font_class": "reload",
+    "unicode": "\ue6b2"
+  },
+  {
+    "font_class": "right",
+    "unicode": "\ue6b5"
+  },
+  {
+    "font_class": "scan",
+    "unicode": "\ue62a"
+  },
+  {
+    "font_class": "search",
+    "unicode": "\ue654"
+  },
+  {
+    "font_class": "settings",
+    "unicode": "\ue653"
+  },
+  {
+    "font_class": "settings-filled",
+    "unicode": "\ue6ce"
+  },
+  {
+    "font_class": "shop",
+    "unicode": "\ue62f"
+  },
+  {
+    "font_class": "shop-filled",
+    "unicode": "\ue6cd"
+  },
+  {
+    "font_class": "smallcircle",
+    "unicode": "\ue67c"
+  },
+  {
+    "font_class": "smallcircle-filled",
+    "unicode": "\ue665"
+  },
+  {
+    "font_class": "sound",
+    "unicode": "\ue684"
+  },
+  {
+    "font_class": "sound-filled",
+    "unicode": "\ue686"
+  },
+  {
+    "font_class": "spinner-cycle",
+    "unicode": "\ue68a"
+  },
+  {
+    "font_class": "staff",
+    "unicode": "\ue6a7"
+  },
+  {
+    "font_class": "staff-filled",
+    "unicode": "\ue6cb"
+  },
+  {
+    "font_class": "star",
+    "unicode": "\ue688"
+  },
+  {
+    "font_class": "star-filled",
+    "unicode": "\ue68f"
+  },
+  {
+    "font_class": "starhalf",
+    "unicode": "\ue683"
+  },
+  {
+    "font_class": "trash",
+    "unicode": "\ue687"
+  },
+  {
+    "font_class": "trash-filled",
+    "unicode": "\ue685"
+  },
+  {
+    "font_class": "tune",
+    "unicode": "\ue6aa"
+  },
+  {
+    "font_class": "tune-filled",
+    "unicode": "\ue6ca"
+  },
+  {
+    "font_class": "undo",
+    "unicode": "\ue64f"
+  },
+  {
+    "font_class": "undo-filled",
+    "unicode": "\ue64c"
+  },
+  {
+    "font_class": "up",
+    "unicode": "\ue6b6"
+  },
+	{
+	  "font_class": "top",
+	  "unicode": "\ue6b6"
+	},
+  {
+    "font_class": "upload",
+    "unicode": "\ue690"
+  },
+  {
+    "font_class": "upload-filled",
+    "unicode": "\ue68e"
+  },
+  {
+    "font_class": "videocam",
+    "unicode": "\ue68c"
+  },
+  {
+    "font_class": "videocam-filled",
+    "unicode": "\ue689"
+  },
+  {
+    "font_class": "vip",
+    "unicode": "\ue6a8"
+  },
+  {
+    "font_class": "vip-filled",
+    "unicode": "\ue6c6"
+  },
+  {
+    "font_class": "wallet",
+    "unicode": "\ue6b1"
+  },
+  {
+    "font_class": "wallet-filled",
+    "unicode": "\ue6c2"
+  },
+  {
+    "font_class": "weibo",
+    "unicode": "\ue68b"
+  },
+  {
+    "font_class": "weixin",
+    "unicode": "\ue691"
+  }
+] as IconsDataItem[]
+
+// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)

+ 649 - 0
uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js

@@ -0,0 +1,649 @@
+
+export const fontData = [
+  {
+    "font_class": "arrow-down",
+    "unicode": "\ue6be"
+  },
+  {
+    "font_class": "arrow-left",
+    "unicode": "\ue6bc"
+  },
+  {
+    "font_class": "arrow-right",
+    "unicode": "\ue6bb"
+  },
+  {
+    "font_class": "arrow-up",
+    "unicode": "\ue6bd"
+  },
+  {
+    "font_class": "auth",
+    "unicode": "\ue6ab"
+  },
+  {
+    "font_class": "auth-filled",
+    "unicode": "\ue6cc"
+  },
+  {
+    "font_class": "back",
+    "unicode": "\ue6b9"
+  },
+  {
+    "font_class": "bars",
+    "unicode": "\ue627"
+  },
+  {
+    "font_class": "calendar",
+    "unicode": "\ue6a0"
+  },
+  {
+    "font_class": "calendar-filled",
+    "unicode": "\ue6c0"
+  },
+  {
+    "font_class": "camera",
+    "unicode": "\ue65a"
+  },
+  {
+    "font_class": "camera-filled",
+    "unicode": "\ue658"
+  },
+  {
+    "font_class": "cart",
+    "unicode": "\ue631"
+  },
+  {
+    "font_class": "cart-filled",
+    "unicode": "\ue6d0"
+  },
+  {
+    "font_class": "chat",
+    "unicode": "\ue65d"
+  },
+  {
+    "font_class": "chat-filled",
+    "unicode": "\ue659"
+  },
+  {
+    "font_class": "chatboxes",
+    "unicode": "\ue696"
+  },
+  {
+    "font_class": "chatboxes-filled",
+    "unicode": "\ue692"
+  },
+  {
+    "font_class": "chatbubble",
+    "unicode": "\ue697"
+  },
+  {
+    "font_class": "chatbubble-filled",
+    "unicode": "\ue694"
+  },
+  {
+    "font_class": "checkbox",
+    "unicode": "\ue62b"
+  },
+  {
+    "font_class": "checkbox-filled",
+    "unicode": "\ue62c"
+  },
+  {
+    "font_class": "checkmarkempty",
+    "unicode": "\ue65c"
+  },
+  {
+    "font_class": "circle",
+    "unicode": "\ue65b"
+  },
+  {
+    "font_class": "circle-filled",
+    "unicode": "\ue65e"
+  },
+  {
+    "font_class": "clear",
+    "unicode": "\ue66d"
+  },
+  {
+    "font_class": "close",
+    "unicode": "\ue673"
+  },
+  {
+    "font_class": "closeempty",
+    "unicode": "\ue66c"
+  },
+  {
+    "font_class": "cloud-download",
+    "unicode": "\ue647"
+  },
+  {
+    "font_class": "cloud-download-filled",
+    "unicode": "\ue646"
+  },
+  {
+    "font_class": "cloud-upload",
+    "unicode": "\ue645"
+  },
+  {
+    "font_class": "cloud-upload-filled",
+    "unicode": "\ue648"
+  },
+  {
+    "font_class": "color",
+    "unicode": "\ue6cf"
+  },
+  {
+    "font_class": "color-filled",
+    "unicode": "\ue6c9"
+  },
+  {
+    "font_class": "compose",
+    "unicode": "\ue67f"
+  },
+  {
+    "font_class": "contact",
+    "unicode": "\ue693"
+  },
+  {
+    "font_class": "contact-filled",
+    "unicode": "\ue695"
+  },
+  {
+    "font_class": "down",
+    "unicode": "\ue6b8"
+  },
+	{
+	  "font_class": "bottom",
+	  "unicode": "\ue6b8"
+	},
+  {
+    "font_class": "download",
+    "unicode": "\ue68d"
+  },
+  {
+    "font_class": "download-filled",
+    "unicode": "\ue681"
+  },
+  {
+    "font_class": "email",
+    "unicode": "\ue69e"
+  },
+  {
+    "font_class": "email-filled",
+    "unicode": "\ue69a"
+  },
+  {
+    "font_class": "eye",
+    "unicode": "\ue651"
+  },
+  {
+    "font_class": "eye-filled",
+    "unicode": "\ue66a"
+  },
+  {
+    "font_class": "eye-slash",
+    "unicode": "\ue6b3"
+  },
+  {
+    "font_class": "eye-slash-filled",
+    "unicode": "\ue6b4"
+  },
+  {
+    "font_class": "fire",
+    "unicode": "\ue6a1"
+  },
+  {
+    "font_class": "fire-filled",
+    "unicode": "\ue6c5"
+  },
+  {
+    "font_class": "flag",
+    "unicode": "\ue65f"
+  },
+  {
+    "font_class": "flag-filled",
+    "unicode": "\ue660"
+  },
+  {
+    "font_class": "folder-add",
+    "unicode": "\ue6a9"
+  },
+  {
+    "font_class": "folder-add-filled",
+    "unicode": "\ue6c8"
+  },
+  {
+    "font_class": "font",
+    "unicode": "\ue6a3"
+  },
+  {
+    "font_class": "forward",
+    "unicode": "\ue6ba"
+  },
+  {
+    "font_class": "gear",
+    "unicode": "\ue664"
+  },
+  {
+    "font_class": "gear-filled",
+    "unicode": "\ue661"
+  },
+  {
+    "font_class": "gift",
+    "unicode": "\ue6a4"
+  },
+  {
+    "font_class": "gift-filled",
+    "unicode": "\ue6c4"
+  },
+  {
+    "font_class": "hand-down",
+    "unicode": "\ue63d"
+  },
+  {
+    "font_class": "hand-down-filled",
+    "unicode": "\ue63c"
+  },
+  {
+    "font_class": "hand-up",
+    "unicode": "\ue63f"
+  },
+  {
+    "font_class": "hand-up-filled",
+    "unicode": "\ue63e"
+  },
+  {
+    "font_class": "headphones",
+    "unicode": "\ue630"
+  },
+  {
+    "font_class": "heart",
+    "unicode": "\ue639"
+  },
+  {
+    "font_class": "heart-filled",
+    "unicode": "\ue641"
+  },
+  {
+    "font_class": "help",
+    "unicode": "\ue679"
+  },
+  {
+    "font_class": "help-filled",
+    "unicode": "\ue674"
+  },
+  {
+    "font_class": "home",
+    "unicode": "\ue662"
+  },
+  {
+    "font_class": "home-filled",
+    "unicode": "\ue663"
+  },
+  {
+    "font_class": "image",
+    "unicode": "\ue670"
+  },
+  {
+    "font_class": "image-filled",
+    "unicode": "\ue678"
+  },
+  {
+    "font_class": "images",
+    "unicode": "\ue650"
+  },
+  {
+    "font_class": "images-filled",
+    "unicode": "\ue64b"
+  },
+  {
+    "font_class": "info",
+    "unicode": "\ue669"
+  },
+  {
+    "font_class": "info-filled",
+    "unicode": "\ue649"
+  },
+  {
+    "font_class": "left",
+    "unicode": "\ue6b7"
+  },
+  {
+    "font_class": "link",
+    "unicode": "\ue6a5"
+  },
+  {
+    "font_class": "list",
+    "unicode": "\ue644"
+  },
+  {
+    "font_class": "location",
+    "unicode": "\ue6ae"
+  },
+  {
+    "font_class": "location-filled",
+    "unicode": "\ue6af"
+  },
+  {
+    "font_class": "locked",
+    "unicode": "\ue66b"
+  },
+  {
+    "font_class": "locked-filled",
+    "unicode": "\ue668"
+  },
+  {
+    "font_class": "loop",
+    "unicode": "\ue633"
+  },
+  {
+    "font_class": "mail-open",
+    "unicode": "\ue643"
+  },
+  {
+    "font_class": "mail-open-filled",
+    "unicode": "\ue63a"
+  },
+  {
+    "font_class": "map",
+    "unicode": "\ue667"
+  },
+  {
+    "font_class": "map-filled",
+    "unicode": "\ue666"
+  },
+  {
+    "font_class": "map-pin",
+    "unicode": "\ue6ad"
+  },
+  {
+    "font_class": "map-pin-ellipse",
+    "unicode": "\ue6ac"
+  },
+  {
+    "font_class": "medal",
+    "unicode": "\ue6a2"
+  },
+  {
+    "font_class": "medal-filled",
+    "unicode": "\ue6c3"
+  },
+  {
+    "font_class": "mic",
+    "unicode": "\ue671"
+  },
+  {
+    "font_class": "mic-filled",
+    "unicode": "\ue677"
+  },
+  {
+    "font_class": "micoff",
+    "unicode": "\ue67e"
+  },
+  {
+    "font_class": "micoff-filled",
+    "unicode": "\ue6b0"
+  },
+  {
+    "font_class": "minus",
+    "unicode": "\ue66f"
+  },
+  {
+    "font_class": "minus-filled",
+    "unicode": "\ue67d"
+  },
+  {
+    "font_class": "more",
+    "unicode": "\ue64d"
+  },
+  {
+    "font_class": "more-filled",
+    "unicode": "\ue64e"
+  },
+  {
+    "font_class": "navigate",
+    "unicode": "\ue66e"
+  },
+  {
+    "font_class": "navigate-filled",
+    "unicode": "\ue67a"
+  },
+  {
+    "font_class": "notification",
+    "unicode": "\ue6a6"
+  },
+  {
+    "font_class": "notification-filled",
+    "unicode": "\ue6c1"
+  },
+  {
+    "font_class": "paperclip",
+    "unicode": "\ue652"
+  },
+  {
+    "font_class": "paperplane",
+    "unicode": "\ue672"
+  },
+  {
+    "font_class": "paperplane-filled",
+    "unicode": "\ue675"
+  },
+  {
+    "font_class": "person",
+    "unicode": "\ue699"
+  },
+  {
+    "font_class": "person-filled",
+    "unicode": "\ue69d"
+  },
+  {
+    "font_class": "personadd",
+    "unicode": "\ue69f"
+  },
+  {
+    "font_class": "personadd-filled",
+    "unicode": "\ue698"
+  },
+  {
+    "font_class": "personadd-filled-copy",
+    "unicode": "\ue6d1"
+  },
+  {
+    "font_class": "phone",
+    "unicode": "\ue69c"
+  },
+  {
+    "font_class": "phone-filled",
+    "unicode": "\ue69b"
+  },
+  {
+    "font_class": "plus",
+    "unicode": "\ue676"
+  },
+  {
+    "font_class": "plus-filled",
+    "unicode": "\ue6c7"
+  },
+  {
+    "font_class": "plusempty",
+    "unicode": "\ue67b"
+  },
+  {
+    "font_class": "pulldown",
+    "unicode": "\ue632"
+  },
+  {
+    "font_class": "pyq",
+    "unicode": "\ue682"
+  },
+  {
+    "font_class": "qq",
+    "unicode": "\ue680"
+  },
+  {
+    "font_class": "redo",
+    "unicode": "\ue64a"
+  },
+  {
+    "font_class": "redo-filled",
+    "unicode": "\ue655"
+  },
+  {
+    "font_class": "refresh",
+    "unicode": "\ue657"
+  },
+  {
+    "font_class": "refresh-filled",
+    "unicode": "\ue656"
+  },
+  {
+    "font_class": "refreshempty",
+    "unicode": "\ue6bf"
+  },
+  {
+    "font_class": "reload",
+    "unicode": "\ue6b2"
+  },
+  {
+    "font_class": "right",
+    "unicode": "\ue6b5"
+  },
+  {
+    "font_class": "scan",
+    "unicode": "\ue62a"
+  },
+  {
+    "font_class": "search",
+    "unicode": "\ue654"
+  },
+  {
+    "font_class": "settings",
+    "unicode": "\ue653"
+  },
+  {
+    "font_class": "settings-filled",
+    "unicode": "\ue6ce"
+  },
+  {
+    "font_class": "shop",
+    "unicode": "\ue62f"
+  },
+  {
+    "font_class": "shop-filled",
+    "unicode": "\ue6cd"
+  },
+  {
+    "font_class": "smallcircle",
+    "unicode": "\ue67c"
+  },
+  {
+    "font_class": "smallcircle-filled",
+    "unicode": "\ue665"
+  },
+  {
+    "font_class": "sound",
+    "unicode": "\ue684"
+  },
+  {
+    "font_class": "sound-filled",
+    "unicode": "\ue686"
+  },
+  {
+    "font_class": "spinner-cycle",
+    "unicode": "\ue68a"
+  },
+  {
+    "font_class": "staff",
+    "unicode": "\ue6a7"
+  },
+  {
+    "font_class": "staff-filled",
+    "unicode": "\ue6cb"
+  },
+  {
+    "font_class": "star",
+    "unicode": "\ue688"
+  },
+  {
+    "font_class": "star-filled",
+    "unicode": "\ue68f"
+  },
+  {
+    "font_class": "starhalf",
+    "unicode": "\ue683"
+  },
+  {
+    "font_class": "trash",
+    "unicode": "\ue687"
+  },
+  {
+    "font_class": "trash-filled",
+    "unicode": "\ue685"
+  },
+  {
+    "font_class": "tune",
+    "unicode": "\ue6aa"
+  },
+  {
+    "font_class": "tune-filled",
+    "unicode": "\ue6ca"
+  },
+  {
+    "font_class": "undo",
+    "unicode": "\ue64f"
+  },
+  {
+    "font_class": "undo-filled",
+    "unicode": "\ue64c"
+  },
+  {
+    "font_class": "up",
+    "unicode": "\ue6b6"
+  },
+	{
+	  "font_class": "top",
+	  "unicode": "\ue6b6"
+	},
+  {
+    "font_class": "upload",
+    "unicode": "\ue690"
+  },
+  {
+    "font_class": "upload-filled",
+    "unicode": "\ue68e"
+  },
+  {
+    "font_class": "videocam",
+    "unicode": "\ue68c"
+  },
+  {
+    "font_class": "videocam-filled",
+    "unicode": "\ue689"
+  },
+  {
+    "font_class": "vip",
+    "unicode": "\ue6a8"
+  },
+  {
+    "font_class": "vip-filled",
+    "unicode": "\ue6c6"
+  },
+  {
+    "font_class": "wallet",
+    "unicode": "\ue6b1"
+  },
+  {
+    "font_class": "wallet-filled",
+    "unicode": "\ue6c2"
+  },
+  {
+    "font_class": "weibo",
+    "unicode": "\ue68b"
+  },
+  {
+    "font_class": "weixin",
+    "unicode": "\ue691"
+  }
+]
+
+// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)

+ 88 - 0
uni_modules/uni-icons/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uni-icons",
+  "displayName": "uni-icons 图标",
+  "version": "2.0.9",
+  "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "icon",
+    "图标"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": "^3.2.14"
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y",
+          "app-uvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y",
+					"钉钉": "y",
+					"快手": "y",
+					"飞书": "y",
+					"京东": "y"
+        },
+        "快应用": {
+          "华为": "y",
+          "联盟": "y"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 8 - 0
uni_modules/uni-icons/readme.md

@@ -0,0 +1,8 @@
+## Icons 图标
+> **组件名:uni-icons**
+> 代码块: `uIcons`
+
+用于展示 icons 图标 。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 19 - 0
uni_modules/uni-load-more/changelog.md

@@ -0,0 +1,19 @@
+## 1.3.3(2022-01-20)
+- 新增 showText属性 ,是否显示文本
+## 1.3.2(2022-01-19)
+- 修复 nvue 平台下不显示文本的bug
+## 1.3.1(2022-01-19)
+- 修复 微信小程序平台样式选择器报警告的问题
+## 1.3.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-load-more](https://uniapp.dcloud.io/component/uniui/uni-load-more)
+## 1.2.1(2021-08-24)
+- 新增 支持国际化
+## 1.2.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.8(2021-05-12)
+- 新增 组件示例地址
+## 1.1.7(2021-03-30)
+- 修复 uni-load-more 在首页使用时,h5 平台报 'uni is not defined' 的 bug
+## 1.1.6(2021-02-05)
+- 调整为uni_modules目录规范

+ 5 - 0
uni_modules/uni-load-more/components/uni-load-more/i18n/en.json

@@ -0,0 +1,5 @@
+{
+	"uni-load-more.contentdown": "Pull up to show more",
+	"uni-load-more.contentrefresh": "loading...",
+	"uni-load-more.contentnomore": "No more data"
+}

+ 8 - 0
uni_modules/uni-load-more/components/uni-load-more/i18n/index.js

@@ -0,0 +1,8 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+export default {
+	en,
+	'zh-Hans': zhHans,
+	'zh-Hant': zhHant
+}

+ 5 - 0
uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json

@@ -0,0 +1,5 @@
+{
+	"uni-load-more.contentdown": "上拉显示更多",
+	"uni-load-more.contentrefresh": "正在加载...",
+	"uni-load-more.contentnomore": "没有更多数据了"
+}

+ 5 - 0
uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json

@@ -0,0 +1,5 @@
+{
+	"uni-load-more.contentdown": "上拉顯示更多",
+	"uni-load-more.contentrefresh": "正在加載...",
+	"uni-load-more.contentnomore": "沒有更多數據了"
+}

File diff suppressed because it is too large
+ 399 - 0
uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue


+ 86 - 0
uni_modules/uni-load-more/package.json

@@ -0,0 +1,86 @@
+{
+  "id": "uni-load-more",
+  "displayName": "uni-load-more 加载更多",
+  "version": "1.3.3",
+  "description": "LoadMore 组件,常用在列表里面,做滚动加载使用。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "加载更多",
+    "load-more"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 14 - 0
uni_modules/uni-load-more/readme.md

@@ -0,0 +1,14 @@
+
+
+### LoadMore 加载更多
+> **组件名:uni-load-more**
+> 代码块: `uLoadMore`
+
+
+用于列表中,做滚动加载使用,展示 loading 的各种状态。
+
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-load-more)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
+
+

+ 8 - 0
uni_modules/uni-scss/changelog.md

@@ -0,0 +1,8 @@
+## 1.0.3(2022-01-21)
+- 优化 组件示例
+## 1.0.2(2021-11-22)
+- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
+## 1.0.1(2021-11-22)
+- 修复 vue3中scss语法兼容问题
+## 1.0.0(2021-11-18)
+- init

+ 1 - 0
uni_modules/uni-scss/index.scss

@@ -0,0 +1 @@
+@import './styles/index.scss';

+ 0 - 0
uni_modules/uni-scss/package.json


Some files were not shown because too many files changed in this diff