Sfoglia il codice sorgente

1、加上登录;
2、其他优化

wenningning 2 anni fa
parent
commit
7499240d0f

+ 2 - 1
sleep/App.vue

@@ -34,10 +34,11 @@ export default {
         console.log(err);
       },
     });
+
   },
   onShow: function () {
     console.log("App Show");
-	
+
     // this.$audio.on("ended", "event-ended", (data) => {
     //   setTimeout(() => {
     //     this.$store.dispatch("changePlay", 1);

+ 97 - 0
sleep/api/connect.js

@@ -0,0 +1,97 @@
+/**
+ * Created by Andste on 2018/7/2.
+ * 信任登录相关API
+ */
+
+import request, { Method } from '@/utils/request'
+import md5 from 'js-md5'
+
+/**
+ * 账户密码登录绑定
+ * @param uuid
+ */
+export function loginBindConnectByNoAccount(uuid, params) {
+  const _params = { ...params }
+  _params.password = md5(_params.password)
+  return request({
+    url: `passport/login-binder/wapByNOAccount/${uuid}`,
+    config: { method: Method.POST },
+    params: _params
+  })
+}
+
+export function loginBindConnectByMobile(uuid,params){
+	return request({
+	  url: `passport/mobile-binder/${uuid}`,
+	  config: { method: Method.POST },
+	  params
+	})
+}
+
+/**
+ * 注册绑定
+ * @param uuid
+ * @param params
+ */
+export function registerBindConnect(uuid, params) {
+  return request({
+    url: `passport/mini-program/register-bind/${uuid}`,
+    config: { method: Method.POST, needToken: true },
+    params
+  })
+}
+
+/**
+ * 微信小程序自动登录
+ */
+export function loginByAuto(params) {
+  return request({
+    url: 'passport/mini-program/auto-login',
+    config: { method: Method.GET, loading: true },
+    params
+  })
+}
+
+/**
+ * 加密数据解密验证
+ * @param params
+ */
+export function accessUnionID(params) {
+  return request({
+    url: 'passport/mini-program/decryptUserinfo',
+    config: { method: Method.GET, loading: true },
+    params
+  })
+}
+
+export function accessMobile(params) {
+	return request({
+		url: 'passport/mini-program/wx_mobile',
+		config: { method: Method.POST, loading: true },
+		params
+	})
+}
+
+export function checkHasLogin(params) {
+  return request({
+    url: 'passport/mini-program/checkHasLogin',
+    config: { method: Method.GET, loading: true },
+    params
+  })
+}
+
+export function loginById(params) {
+    return request({
+        url: 'passport/mini-program/loginById',
+        config: { method: Method.GET, loading: true },
+        params
+    })
+}
+
+export function snLoginByMemberId(params) {
+    return request({
+        url: 'passport/mini-program/sn_login_by_member_id',
+        config: { method: Method.GET, loading: true },
+        params
+    })
+}

+ 1 - 1
sleep/api/device_member_bind.js

@@ -12,7 +12,7 @@ export function getDeviceByDtypeAndMemberId(params){
     })
 }
 
-// 根据设备类型绑定设备
+// 根据设备类型绑定设备
 export function bindDeviceByDtype(params){
     return request({
         url: `care/care_device_member_bind/bind_device_by_dtype`,

+ 4 - 3
sleep/api/doctor_advert.js

@@ -7,8 +7,8 @@ import request, { Method } from '@/utils/request'
  */
 export function getDoctorAdvertList(params) {
   return request({
-    url: 'care/care_device_member_bind/pageList', // 先写死
-    config: {method: Method.POST, needToken: true, loading: true},
+    url: 'care/health_advert/pageList',
+    config: {method: Method.POST, needToken: false, loading: true},
     params
   })
 }
@@ -17,7 +17,8 @@ export function getDoctorAdvertList(params) {
 export function getDoctorAdvert(id, params) {
   return request({
     url: `care/health_advert/${id}`,
-    config: {method: Method.GET, needToken: true, loading: true},
+    config: {method: Method.GET, needToken: false, loading: true},
     params
   })
 }
+

+ 48 - 0
sleep/api/members.js

@@ -0,0 +1,48 @@
+/**
+ * Created by Andste on 2018/6/8.
+ */
+
+import request, { Method } from '@/utils/request'
+
+/**
+ * 获取当前登录的用户信息
+ * @returns {AxiosPromise}
+ */
+export function getUserInfo() {
+  return request({
+    url: 'members',
+    config: { method: Method.GET, needToken: true }
+  })
+}
+
+/**
+ * 保存用户信息
+ * @param params
+ * @returns {AxiosPromise}
+ */
+export function saveUserInfo(params) {
+  return request({
+    url: 'members',
+    config: { method: Method.PUT, needToken: true },
+    params
+  })
+}
+
+/**
+ * 登出 -- 执行解绑操作
+ * @returns {AxiosPromise}
+ */
+export function logout() {
+  return request({
+    url: 'account-binder/unbind/outnoBind',
+    config: { method: Method.POST, needToken: true, loading: true }
+  })
+}
+
+/** 查询用户信息 */
+export function getMember(id) {
+  return request({
+    url: `members/getMember/${id}`,
+    config: {method: Method.GET, needToken: true, loading: true},
+  })
+}

+ 6 - 6
sleep/api/sleep_report.js

@@ -2,12 +2,12 @@
 /**
  * 睡眠报告API
  */
-import request, { Method } from '@/utils/request'
+import request, { Method,API } from '@/utils/request'
 
 // 获取最近一天的睡眠报告
 export function getNewListByUnionId(params){
     return request({
-        url: `xiaomian/get_new_list_by_union_id`,
+        url: `care/sleep_report/get_new_list_by_union_id`,
         config: {method: Method.POST, needToken: true, loading: true},
         params
     })
@@ -16,7 +16,7 @@ export function getNewListByUnionId(params){
 // 获取日历上有值的天数
 export function getSleepReportDaysTag(params){
     return request({
-        url: `xiaomian/get_sleep_report_days_tag`,
+        url: `care/sleep_report/get_sleep_report_days_tag`,
         config: {method: Method.POST, needToken: true, loading: true},
         params
     })
@@ -25,7 +25,7 @@ export function getSleepReportDaysTag(params){
 // 设置睡眠预警参数
 export function pushSleepWarn(params){
     return request({
-        url: `xiaomian/push/warn`,
+        url: `${API.base}/xiaomian/push/warn`,
         config: {method: Method.POST, needToken: true, loading: true},
         params
     })
@@ -34,7 +34,7 @@ export function pushSleepWarn(params){
 // 获取睡眠预警参数
 export function getWarnResult(imei){
     return request({
-        url: `xiaomian/get_warn_result/${imei}`,
+        url: `${API.base}/xiaomian/get_warn_result/${imei}`,
         config: {method: Method.GET, needToken: true, loading: true}
     })
 }
@@ -42,7 +42,7 @@ export function getWarnResult(imei){
 // 根据imei与用户unionId获取最新报告
 export function getNewReportBySn(params){
     return request({
-        url: `xiaomian/get_new_report_by_sn`,
+        url: `care/sleep_report/get_new_report_by_sn`,
         config: {method: Method.GET, needToken: true, loading: true},
         params
     })

+ 102 - 0
sleep/components/parser/libs/CssHandler.js

@@ -0,0 +1,102 @@
+/*
+  解析和匹配 Css 的选择器
+  github:https://github.com/jin-yufeng/Parser
+  docs:https://jin-yufeng.github.io/Parser
+  author:JinYufeng
+  update:2020/03/15
+*/
+var cfg = require('./config.js');
+class CssHandler {
+	constructor(tagStyle) {
+		var styles = Object.assign({}, cfg.userAgentStyles);
+		for (var item in tagStyle)
+			styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
+		this.styles = styles;
+	}
+	getStyle = data => this.styles = new CssParser(data, this.styles).parse();
+	match(name, attrs) {
+		var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';
+		if (attrs.class) {
+			var items = attrs.class.split(' ');
+			for (var i = 0, item; item = items[i]; i++)
+				if (tmp = this.styles['.' + item])
+					matched += tmp + ';';
+		}
+		if (tmp = this.styles['#' + attrs.id])
+			matched += tmp + ';';
+		return matched;
+	}
+}
+module.exports = CssHandler;
+class CssParser {
+	constructor(data, init) {
+		this.data = data;
+		this.floor = 0;
+		this.i = 0;
+		this.list = [];
+		this.res = init;
+		this.state = this.Space;
+	}
+	parse() {
+		for (var c; c = this.data[this.i]; this.i++)
+			this.state(c);
+		return this.res;
+	}
+	section = () => this.data.substring(this.start, this.i);
+	isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+	// 状态机
+	Space(c) {
+		if (c == '.' || c == '#' || this.isLetter(c)) {
+			this.start = this.i;
+			this.state = this.Name;
+		} else if (c == '/' && this.data[this.i + 1] == '*')
+			this.Comment();
+		else if (!cfg.blankChar[c] && c != ';')
+			this.state = this.Ignore;
+	}
+	Comment() {
+		this.i = this.data.indexOf('*/', this.i) + 1;
+		if (!this.i) this.i = this.data.length;
+		this.state = this.Space;
+	}
+	Ignore(c) {
+		if (c == '{') this.floor++;
+		else if (c == '}' && !--this.floor) this.state = this.Space;
+	}
+	Name(c) {
+		if (cfg.blankChar[c]) {
+			this.list.push(this.section());
+			this.state = this.NameSpace;
+		} else if (c == '{') {
+			this.list.push(this.section());
+			this.Content();
+		} else if (c == ',') {
+			this.list.push(this.section());
+			this.Comma();
+		} else if (!this.isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
+			this.state = this.Ignore;
+	}
+	NameSpace(c) {
+		if (c == '{') this.Content();
+		else if (c == ',') this.Comma();
+		else if (!cfg.blankChar[c]) this.state = this.Ignore;
+	}
+	Comma() {
+		while (cfg.blankChar[this.data[++this.i]]);
+		if (this.data[this.i] == '{') this.Content();
+		else {
+			this.start = this.i--;
+			this.state = this.Name;
+		}
+	}
+	Content() {
+		this.start = ++this.i;
+		if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;
+		var content = this.section();
+		for (var i = 0, item; item = this.list[i++];)
+			if (this.res[item]) this.res[item] += ';' + content;
+			else this.res[item] = content;
+		this.list = [];
+		this.state = this.Space;
+	}
+}

+ 578 - 0
sleep/components/parser/libs/MpHtmlParser.js

@@ -0,0 +1,578 @@
+/*
+  将 html 解析为适用于小程序 rich-text 的 DOM 结构
+  github:https://github.com/jin-yufeng/Parser
+  docs:https://jin-yufeng.github.io/Parser
+  author:JinYufeng
+  update:2020/03/26
+*/
+var cfg = require('./config.js'),
+	blankChar = cfg.blankChar,
+	CssHandler = require('./CssHandler.js'),
+	{
+		screenWidth,
+		system
+	} = wx.getSystemInfoSync();
+// #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
+var entities = {
+	lt: '<',
+	gt: '>',
+	amp: '&',
+	quot: '"',
+	apos: "'",
+	nbsp: '\xA0',
+	ensp: '\u2002',
+	emsp: '\u2003',
+	ndash: '–',
+	mdash: '—',
+	middot: '·',
+	lsquo: '‘',
+	rsquo: '’',
+	ldquo: '“',
+	rdquo: '”',
+	bull: '•',
+	hellip: '…',
+	permil: '‰',
+	copy: '©',
+	reg: '®',
+	trade: '™',
+	times: '×',
+	divide: '÷',
+	cent: '¢',
+	pound: '£',
+	yen: '¥',
+	euro: '€',
+	sect: '§'
+};
+// #endif
+var emoji; // emoji 补丁包 https://jin-yufeng.github.io/Parser/#/instructions?id=emoji
+class MpHtmlParser {
+	constructor(data, options = {}) {
+		this.attrs = {};
+		this.compress = options.compress;
+		this.CssHandler = new CssHandler(options.tagStyle, screenWidth);
+		this.data = data;
+		this.domain = options.domain;
+		this.DOM = [];
+		this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
+		this.protocol = this.domain && this.domain.includes('://') ? this.domain.split('://')[0] : '';
+		this.state = this.Text;
+		this.STACK = [];
+		this.useAnchor = options.useAnchor;
+		this.xml = options.xml;
+	}
+	parse() {
+		if (emoji) this.data = emoji.parseEmoji(this.data);
+		for (var c; c = this.data[this.i]; this.i++)
+			this.state(c);
+		if (this.state == this.Text) this.setText();
+		while (this.STACK.length) this.popNode(this.STACK.pop());
+		// #ifdef MP-BAIDU || MP-TOUTIAO
+		// 将顶层标签的一些样式提取出来给 rich-text
+		(function f(ns) {
+			for (var i = ns.length, n; n = ns[--i];) {
+				if (n.type == 'text') continue;
+				if (!n.c) {
+					var style = n.attrs.style;
+					if (style) {
+						var j, k, res;
+						if ((j = style.indexOf('display')) != -1)
+							res = style.substring(j, (k = style.indexOf(';', j)) == -1 ? style.length : k);
+						if ((j = style.indexOf('float')) != -1)
+							res += ';' + style.substring(j, (k = style.indexOf(';', j)) == -1 ? style.length : k);
+						n.attrs.contain = res;
+					}
+				} else f(n.children);
+			}
+		})(this.DOM);
+		// #endif
+		if (this.DOM.length) {
+			this.DOM[0].PoweredBy = 'Parser';
+			if (this.title) this.DOM[0].title = this.title;
+		}
+		return this.DOM;
+	}
+	// 设置属性
+	setAttr() {
+		var name = this.getName(this.attrName);
+		if (cfg.trustAttrs[name]) {
+			if (!this.attrVal) {
+				if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
+			} else if (name == 'src') this.attrs[name] = this.getUrl(this.attrVal.replace(/&amp;/g, '&'));
+			else this.attrs[name] = this.attrVal;
+		}
+		this.attrVal = '';
+		while (blankChar[this.data[this.i]]) this.i++;
+		if (this.isClose()) this.setNode();
+		else {
+			this.start = this.i;
+			this.state = this.AttrName;
+		}
+	}
+	// 设置文本节点
+	setText() {
+		var back, text = this.section();
+		if (!text) return;
+		text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
+		if (back) {
+			this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
+			let j = this.start + text.length;
+			for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
+			return;
+		}
+		if (!this.pre) {
+			// 合并空白符
+			var tmp = [];
+			for (let i = text.length, c; c = text[--i];)
+				if (!blankChar[c] || (!blankChar[tmp[0]] && (c = ' '))) tmp.unshift(c);
+			text = tmp.join('');
+			if (text == ' ') return;
+		}
+		// 处理实体
+		var siblings = this.siblings(),
+			i = -1,
+			j, en;
+		while (1) {
+			if ((i = text.indexOf('&', i + 1)) == -1) break;
+			if ((j = text.indexOf(';', i + 2)) == -1) break;
+			if (text[i + 1] == '#') {
+				en = parseInt((text[i + 2] == 'x' ? '0' : '') + text.substring(i + 2, j));
+				if (!isNaN(en)) text = text.substr(0, i) + String.fromCharCode(en) + text.substring(j + 1);
+			} else {
+				en = text.substring(i + 1, j);
+				// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
+				if (en == 'nbsp') text = text.substr(0, i) + '\xA0' + text.substr(j + 1); // 解决 &nbsp; 失效
+				else if (en != 'lt' && en != 'gt' && en != 'amp' && en != 'ensp' && en != 'emsp' && en != 'quot' && en != 'apos') {
+					i && siblings.push({
+						type: 'text',
+						text: text.substr(0, i)
+					})
+					siblings.push({
+						type: 'text',
+						text: `&${en};`,
+						en: 1
+					})
+					text = text.substr(j + 1);
+					i = -1;
+				}
+				// #endif
+				// #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
+				if (entities[en]) text = text.substr(0, i) + entities[en] + text.substr(j + 1);
+				// #endif
+			}
+		}
+		text && siblings.push({
+			type: 'text',
+			text
+		})
+	}
+	// 设置元素节点
+	setNode() {
+		var node = {
+				name: this.tagName.toLowerCase(),
+				attrs: this.attrs
+			},
+			close = cfg.selfClosingTags[node.name] || (this.xml && this.data[this.i] == '/');
+		this.attrs = {};
+		if (!cfg.ignoreTags[node.name]) {
+			this.matchAttr(node);
+			if (!close) {
+				node.children = [];
+				if (node.name == 'pre' && cfg.highlight) {
+					this.remove(node);
+					this.pre = node.pre = true;
+				}
+				this.siblings().push(node);
+				this.STACK.push(node);
+			} else if (!cfg.filter || cfg.filter(node, this) != false)
+				this.siblings().push(node);
+		} else {
+			if (!close) this.remove(node);
+			else if (node.name == 'source') {
+				var parent = this.STACK[this.STACK.length - 1],
+					attrs = node.attrs;
+				if (parent && attrs.src)
+					if (parent.name == 'video' || parent.name == 'audio')
+						parent.attrs.source.push(attrs.src);
+					else {
+						var i, media = attrs.media;
+						if (parent.name == 'picture' && !parent.attrs.src && !(attrs.src.indexOf('.webp') && system.includes('iOS')) &&
+							(!media || (media.includes('px') &&
+								(((i = media.indexOf('min-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth > parseInt(
+										media.substr(i + 1))) ||
+									((i = media.indexOf('max-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth < parseInt(
+										media.substr(i + 1)))))))
+							parent.attrs.src = attrs.src;
+					}
+			} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
+		}
+		if (this.data[this.i] == '/') this.i++;
+		this.start = this.i + 1;
+		this.state = this.Text;
+	}
+	// 移除标签
+	remove(node) {
+		var name = node.name,
+			j = this.i;
+		while (1) {
+			if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
+				if (name == 'pre' || name == 'svg') this.i = j;
+				else this.i = this.data.length;
+				return;
+			}
+			this.start = (this.i += 2);
+			while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
+			if (this.getName(this.section()) == name) {
+				// 代码块高亮
+				if (name == 'pre') {
+					this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) +
+						this.data.substr(this.i - 5);
+					return this.i = j;
+				} else if (name == 'style')
+					this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
+				else if (name == 'title')
+					this.title = this.data.substring(j + 1, this.i - 7);
+				if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
+				// 处理 svg
+				if (name == 'svg') {
+					var src = this.data.substring(j, this.i + 1);
+					if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src;
+					var i = j;
+					while (this.data[j] != '<') j--;
+					src = this.data.substring(j, i) + src;
+					var parent = this.STACK[this.STACK.length - 1];
+					if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
+						parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
+					this.siblings().push({
+						name: 'img',
+						attrs: {
+							src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
+							ignore: 'T'
+						}
+					})
+				}
+				return;
+			}
+		}
+	}
+	// 处理属性
+	matchAttr(node) {
+		var attrs = node.attrs,
+			style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
+			styleObj = {};
+		if (attrs.id) {
+			if (this.compress & 1) attrs.id = void 0;
+			else if (this.useAnchor) this.bubble();
+		}
+		if ((this.compress & 2) && attrs.class) attrs.class = void 0;
+		switch (node.name) {
+			case 'img':
+				if (attrs['data-src']) {
+					attrs.src = attrs.src || attrs['data-src'];
+					attrs['data-src'] = void 0;
+				}
+				if (attrs.src && !attrs.ignore) {
+					if (this.bubble()) attrs.i = (this.imgNum++).toString();
+					else attrs.ignore = 'T';
+				}
+				break;
+			case 'a':
+			case 'ad':
+			// #ifdef APP-PLUS
+			case 'iframe':
+			case 'embed':
+			// #endif
+				this.bubble();
+				break;
+			case 'font':
+				if (attrs.color) {
+					styleObj['color'] = attrs.color;
+					attrs.color = void 0;
+				}
+				if (attrs.face) {
+					styleObj['font-family'] = attrs.face;
+					attrs.face = void 0;
+				}
+				if (attrs.size) {
+					var size = parseInt(attrs.size);
+					if (size < 1) size = 1;
+					else if (size > 7) size = 7;
+					var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
+					styleObj['font-size'] = map[size - 1];
+					attrs.size = void 0;
+				}
+				break;
+			case 'video':
+			case 'audio':
+				if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
+				else this[`${node.name}Num`]++;
+				if (node.name == 'video') {
+					if (attrs.width) {
+						style = `width:${parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')};${style}`;
+						attrs.width = void 0;
+					}
+					if (attrs.height) {
+						style = `height:${parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')};${style}`;
+						attrs.height = void 0;
+					}
+					if (this.videoNum > 3) node.lazyLoad = true;
+				}
+				attrs.source = [];
+				if (attrs.src) attrs.source.push(attrs.src);
+				if (!attrs.controls && !attrs.autoplay)
+					console.warn(`存在没有 controls 属性的 ${node.name} 标签,可能导致无法播放`, node);
+				this.bubble();
+				break;
+			case 'td':
+			case 'th':
+				if (attrs.colspan || attrs.rowspan)
+					for (var k = this.STACK.length, item; item = this.STACK[--k];)
+						if (item.name == 'table') {
+							item.c = void 0;
+							break;
+						}
+		}
+		if (attrs.align) {
+			styleObj['text-align'] = attrs.align;
+			attrs.align = void 0;
+		}
+		// 压缩 style
+		var styles = style.replace(/&quot;/g, '"').replace(/&amp;/g, '&').split(';');
+		style = '';
+		for (var i = 0, len = styles.length; i < len; i++) {
+			var info = styles[i].split(':');
+			if (info.length < 2) continue;
+			let key = info[0].trim().toLowerCase(),
+				value = info.slice(1).join(':').trim();
+			if (value.includes('-webkit') || value.includes('-moz') || value.includes('-ms') || value.includes('-o') || value
+				.includes(
+					'safe'))
+				style += `;${key}:${value}`;
+			else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
+				styleObj[key] = value;
+		}
+		if (node.name == 'img' && parseInt(styleObj.width || attrs.width) > screenWidth)
+			styleObj.height = 'auto';
+		for (var key in styleObj) {
+			var value = styleObj[key];
+			if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
+			// 填充链接
+			if (value.includes('url')) {
+				var j = value.indexOf('(');
+				if (j++ != -1) {
+					while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
+					value = value.substr(0, j) + this.getUrl(value.substr(j));
+				}
+			}
+			// 转换 rpx
+			else if (value.includes('rpx'))
+				value = value.replace(/[0-9.\s]*rpx/g, $ => parseFloat($) * screenWidth / 750 + 'px');
+			else if (key == 'white-space' && value.includes('pre'))
+				this.pre = node.pre = true;
+			style += `;${key}:${value}`;
+		}
+		style = style.substr(1);
+		if (style) attrs.style = style;
+	}
+	// 节点出栈处理
+	popNode(node) {
+		// 空白符处理
+		if (node.pre) {
+			node.pre = this.pre = void 0;
+			for (let i = this.STACK.length; i--;)
+				if (this.STACK[i].pre)
+					this.pre = true;
+		}
+		if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
+			return this.siblings().pop();
+		var attrs = node.attrs;
+		// 替换一些标签名
+		if (node.name == 'picture') {
+			node.name = 'img';
+			if (!attrs.src && (node.children[0] || '').name == 'img')
+				attrs.src = node.children[0].attrs.src;
+			if (attrs.src && !attrs.ignore)
+				attrs.i = (this.imgNum++).toString();
+			return node.children = void 0;
+		}
+		if (cfg.blockTags[node.name]) node.name = 'div';
+		else if (!cfg.trustTags[node.name]) node.name = 'span';
+		// 处理列表
+		if (node.c) {
+			if (node.name == 'ul') {
+				var floor = 1;
+				for (let i = this.STACK.length; i--;)
+					if (this.STACK[i].name == 'ul') floor++;
+				if (floor != 1)
+					for (let i = node.children.length; i--;)
+						node.children[i].floor = floor;
+			} else if (node.name == 'ol') {
+				for (let i = 0, num = 1, child; child = node.children[i++];)
+					if (child.name == 'li') {
+						child.type = 'ol';
+						child.num = ((num, type) => {
+							if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
+							if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
+							if (type == 'i' || type == 'I') {
+								num = (num - 1) % 99 + 1;
+								var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
+									ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
+									res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
+								if (type == 'i') return res.toLowerCase();
+								return res;
+							}
+							return num;
+						})(num++, attrs.type) + '.';
+					}
+			}
+		}
+		// 处理表格的边框
+		if (node.name == 'table') {
+			var padding = attrs.cellpadding,
+				spacing = attrs.cellspacing,
+				border = attrs.border;
+			if (node.c) {
+				this.bubble();
+				if (!padding) padding = 2;
+				if (!spacing) spacing = 2;
+			}
+			if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
+			if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
+			if (border || padding)
+				(function f(ns) {
+					for (var i = 0, n; n = ns[i]; i++) {
+						if (n.name == 'th' || n.name == 'td') {
+							if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style}`;
+							if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style}`;
+						} else f(n.children || []);
+					}
+				})(node.children)
+		}
+		this.CssHandler.pop && this.CssHandler.pop(node);
+		// 自动压缩
+		if (node.name == 'div' && !Object.keys(attrs).length) {
+			var siblings = this.siblings();
+			if (!(node.children || []).length) siblings.pop();
+			else if (node.children.length == 1 && node.children[0].name == 'div')
+				siblings[siblings.length - 1] = node.children[0];
+		}
+	}
+	// 工具函数
+	bubble() {
+		for (var i = this.STACK.length, item; item = this.STACK[--i];) {
+			if (cfg.richOnlyTags[item.name]) {
+				if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1;
+				return false;
+			}
+			item.c = 1;
+		}
+		return true;
+	}
+	getName = val => this.xml ? val : val.toLowerCase();
+	getUrl(url) {
+		if (url[0] == '/') {
+			if (url[1] == '/') url = this.protocol + ':' + url;
+			else if (this.domain) url = this.domain + url;
+		} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
+			url = this.domain + '/' + url;
+		return url;
+	}
+	isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
+	section = () => this.data.substring(this.start, this.i);
+	siblings = () => this.STACK.length ? this.STACK[this.STACK.length - 1].children : this.DOM;
+	// 状态机
+	Text(c) {
+		if (c == '<') {
+			var next = this.data[this.i + 1],
+				isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+			if (isLetter(next)) {
+				this.setText();
+				this.start = this.i + 1;
+				this.state = this.TagName;
+			} else if (next == '/') {
+				this.setText();
+				if (isLetter(this.data[++this.i + 1])) {
+					this.start = this.i + 1;
+					this.state = this.EndTag;
+				} else
+					this.Comment();
+			} else if (next == '!') {
+				this.setText();
+				this.Comment();
+			}
+		}
+	}
+	Comment() {
+		var key;
+		if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
+		else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
+		else key = '>';
+		if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
+		else this.i += key.length - 1;
+		this.start = this.i + 1;
+		this.state = this.Text;
+	}
+	TagName(c) {
+		if (blankChar[c]) {
+			this.tagName = this.section();
+			while (blankChar[this.data[this.i]]) this.i++;
+			if (this.isClose()) this.setNode();
+			else {
+				this.start = this.i;
+				this.state = this.AttrName;
+			}
+		} else if (this.isClose()) {
+			this.tagName = this.section();
+			this.setNode();
+		}
+	}
+	AttrName(c) {
+		var blank = blankChar[c];
+		if (blank) {
+			this.attrName = this.section();
+			c = this.data[this.i];
+		}
+		if (c == '=') {
+			if (!blank) this.attrName = this.section();
+			while (blankChar[this.data[++this.i]]);
+			this.start = this.i--;
+			this.state = this.AttrValue;
+		} else if (blank) this.setAttr();
+		else if (this.isClose()) {
+			this.attrName = this.section();
+			this.setAttr();
+		}
+	}
+	AttrValue(c) {
+		if (c == '"' || c == "'") {
+			this.start++;
+			if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
+			this.attrVal = this.section();
+			this.i++;
+		} else {
+			for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
+			this.attrVal = this.section();
+		}
+		this.setAttr();
+	}
+	EndTag(c) {
+		if (blankChar[c] || c == '>' || c == '/') {
+			var name = this.getName(this.section());
+			for (var i = this.STACK.length; i--;)
+				if (this.STACK[i].name == name) break;
+			if (i != -1) {
+				var node;
+				while ((node = this.STACK.pop()).name != name);
+				this.popNode(node);
+			} else if (name == 'p' || name == 'br')
+				this.siblings().push({
+					name,
+					attrs: {}
+				});
+			this.i = this.data.indexOf('>', this.i);
+			this.start = this.i + 1;
+			if (this.i == -1) this.i = this.data.length;
+			else this.state = this.Text;
+		}
+	}
+}
+module.exports = MpHtmlParser;

+ 80 - 0
sleep/components/parser/libs/config.js

@@ -0,0 +1,80 @@
+/* 配置文件 */
+// #ifdef MP-WEIXIN
+const canIUse = wx.canIUse('editor'); // 高基础库标识,用于兼容
+// #endif
+module.exports = {
+	// 过滤器函数
+	filter: null,
+	// 代码高亮函数
+	highlight: null,
+	// 文本处理函数
+	onText: null,
+	blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
+	// 块级标签,将被转为 div
+	blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,section' + (
+		// #ifdef MP-WEIXIN
+		canIUse ? '' :
+		// #endif
+		',pre')),
+	// 将被移除的标签
+	ignoreTags: makeMap(
+		'area,base,basefont,canvas,command,frame,input,isindex,keygen,link,map,meta,param,script,source,style,svg,textarea,title,track,use,wbr'
+		// #ifdef MP-WEIXIN
+		+ (canIUse ? ',rp' : '')
+		// #endif
+		// #ifndef APP-PLUS
+		+ ',embed,iframe'
+		// #endif
+	),
+	// 只能被 rich-text 显示的标签
+	richOnlyTags: makeMap('a,colgroup,fieldset,legend,picture,table'
+		// #ifdef MP-WEIXIN
+		+ (canIUse ? ',bdi,bdo,caption,rt,ruby' : '')
+		// #endif
+	),
+	// 自闭合的标签
+	selfClosingTags: makeMap(
+		'area,base,basefont,br,col,circle,ellipse,embed,frame,hr,img,input,isindex,keygen,line,link,meta,param,path,polygon,rect,source,track,use,wbr'
+	),
+	// 信任的属性
+	trustAttrs: makeMap(
+		'align,alt,app-id,author,autoplay,border,cellpadding,cellspacing,class,color,colspan,controls,data-src,dir,face,height,href,id,ignore,loop,media,muted,name,path,poster,rowspan,size,span,src,start,style,type,unit-id,width,xmlns'
+	),
+	// bool 型的属性
+	boolAttrs: makeMap('autoplay,controls,ignore,loop,muted'),
+	// 信任的标签
+	trustTags: makeMap(
+		'a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'
+		// #ifdef MP-WEIXIN
+		+ (canIUse ? ',bdi,bdo,caption,pre,rt,ruby' : '')
+		// #endif
+		// #ifdef APP-PLUS
+		+ ',embed,iframe'
+		// #endif
+	),
+	// 默认的标签样式
+	userAgentStyles: {
+		address: 'font-style:italic',
+		big: 'display:inline;font-size:1.2em',
+		blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',
+		caption: 'display:table-caption;text-align:center',
+		center: 'text-align:center',
+		cite: 'font-style:italic',
+		dd: 'margin-left:40px',
+		img: 'max-width:100%',
+		mark: 'background-color:yellow',
+		picture: 'max-width:100%',
+		pre: 'font-family:monospace;white-space:pre;overflow:scroll',
+		s: 'text-decoration:line-through',
+		small: 'display:inline;font-size:0.8em',
+		u: 'text-decoration:underline'
+	}
+}
+
+function makeMap(str) {
+	var map = {},
+		list = str.split(',');
+	for (var i = list.length; i--;)
+		map[list[i]] = true;
+	return map;
+}

+ 35 - 0
sleep/components/parser/libs/handler.sjs

@@ -0,0 +1,35 @@
+var inlineTags = {
+	abbr: 1,
+	b: 1,
+	big: 1,
+	code: 1,
+	del: 1,
+	em: 1,
+	i: 1,
+	ins: 1,
+	label: 1,
+	q: 1,
+	small: 1,
+	span: 1,
+	strong: 1
+}
+export default {
+	// 从顶层标签的样式中取出一些给 rich-text
+	getStyle: function(style) {
+		if (style) {
+			var i, j, res = '';
+			if ((i = style.indexOf('display')) != -1)
+				res = style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
+			if ((i = style.indexOf('float')) != -1)
+				res += ';' + style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
+			return res;
+		}
+	},
+	getNode: function(item) {
+		return [item];
+	},
+	// 是否通过 rich-text 显示
+	useRichText: function(item) {
+		return !item.c && !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1;
+	}
+}

+ 44 - 0
sleep/components/parser/libs/handler.wxs

@@ -0,0 +1,44 @@
+var inlineTags = {
+	abbr: 1,
+	b: 1,
+	big: 1,
+	code: 1,
+	del: 1,
+	em: 1,
+	i: 1,
+	ins: 1,
+	label: 1,
+	q: 1,
+	small: 1,
+	span: 1,
+	strong: 1
+}
+module.exports = {
+	// 从顶层标签的样式中取出一些给 rich-text
+	getStyle: function(style) {
+		if (style) {
+			var i, j, res = '';
+			if ((i = style.indexOf('display')) != -1)
+				res = style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
+			if ((i = style.indexOf('float')) != -1)
+				res += ';' + style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
+			return res;
+		}
+	},
+	// 处理懒加载
+	getNode: function(item, imgLoad) {
+		if (!imgLoad && item.attrs.i != '0') {
+			var img = {
+				name: 'img',
+				attrs: JSON.parse(JSON.stringify(item.attrs))
+			}
+			delete img.attrs.src;
+			img.attrs.style += ';width:20px;height:20px';
+			return [img];
+		} else return [item];
+	},
+	// 是否通过 rich-text 显示
+	useRichText: function(item) {
+		return !item.c && !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1;
+	}
+}

+ 485 - 0
sleep/components/parser/libs/trees.vue

@@ -0,0 +1,485 @@
+<!--
+  trees 递归显示组件
+  github:https://github.com/jin-yufeng/Parser 
+  docs:https://jin-yufeng.github.io/Parser
+  插件市场:https://ext.dcloud.net.cn/plugin?id=805
+  author:JinYufeng
+  update:2020/03/23
+-->
+<template>
+	<view class="interlayer">
+		<block v-for="(n, index) in nodes" v-bind:key="index">
+			<!--图片-->
+			<!--#ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY || APP-PLUS-->
+			<rich-text v-if="n.name=='img'" :id="n.attrs.id" class="_img" :style="''+handler.getStyle(n.attrs.style)" :nodes="handler.getNode(n,!lazyLoad||imgLoad)"
+			 :data-attrs="n.attrs" @tap="imgtap" @longpress="imglongtap" />
+			<!--#endif-->
+			<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
+			<rich-text v-if="n.name=='img'" :id="n.attrs.id" class="_img" :style="n.attrs.contain" :nodes='[n]' :data-attrs="n.attrs"
+			 @tap="imgtap" @longpress="imglongtap" />
+			<!--#endif-->
+			<!--文本-->
+			<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
+			<rich-text v-else-if="n.decode" class="_entity" :nodes="[n]"></rich-text>
+			<!--#endif-->
+			<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
+			<text v-else-if="n.name=='br'">\n</text>
+			<!--视频-->
+			<view v-else-if="n.name=='video'">
+				<view v-if="(!loadVideo||n.lazyLoad)&&!(controls[n.attrs.id]&&controls[n.attrs.id].play)" :id="n.attrs.id" :class="'_video '+(n.attrs.class||'')"
+				 :style="n.attrs.style" @tap="_loadVideo" />
+				<video v-else :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||(controls[n.attrs.id]&&controls[n.attrs.id].play)"
+				 :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[(controls[n.attrs.id]&&controls[n.attrs.id].index)||0]"
+				 :unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" data-from="video" data-source="source" @error="error" @play="play" />
+			</view>
+			<!--音频-->
+			<audio v-else-if="n.name=='audio'" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :autoplay="n.attrs.autoplay"
+			 :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.attrs.source[(controls[n.attrs.id]&&controls[n.attrs.id].index)||0]"
+			 :data-id="n.attrs.id" data-from="audio" data-source="source" @error="error" @play="play" />
+			<!--链接-->
+			<view v-else-if="n.name=='a'" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
+			 :data-attrs="n.attrs" @tap="linkpress">
+				<trees class="_span" :nodes="n.children" />
+			</view>
+			<!--广告(按需打开注释)-->
+			<!--#ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO-->
+			<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']"
+			 data-from="ad" @error="error" />-->
+			<!--#endif-->
+			<!--#ifdef MP-BAIDU-->
+			<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :appid="n.attrs.appid"
+			 :apid="n.attrs.apid" :type="n.attrs.type" data-from="ad" @error="error" />-->
+			<!--#endif-->
+			<!--#ifdef APP-PLUS-->
+			<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :adpid="n.attrs.adpid"
+			 data-from="ad" @error="error" />-->
+			<!--#endif-->
+			<!--列表-->
+			<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex'">
+				<view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
+				<view v-else class="_ul-bef">
+					<view v-if="n.floor%3==0" class="_ul-p1">█</view>
+					<view v-else-if="n.floor%3==2" class="_ul-p2" />
+					<view v-else class="_ul-p1" style="border-radius:50%">█</view>
+				</view>
+				<!--#ifdef MP-ALIPAY-->
+				<view class="_li">
+					<trees :nodes="n.children" />
+				</view>
+				<!--#endif-->
+				<!--#ifndef MP-ALIPAY-->
+				<trees class="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
+				<!--#endif-->
+			</view>
+			<!--表格-->
+			<view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'">
+				<view v-for="(tbody, i) in n.children" v-bind:key="i" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
+					<view v-for="(tr, j) in tbody.children" v-bind:key="j" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
+						<trees v-if="tr.name=='td'" :nodes="tr.children" :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
+						<block v-else>
+							<!--#ifdef MP-ALIPAY-->
+							<view v-for="(td, k) in tr.children" v-bind:key="k" :class="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')">
+								<trees :nodes="td.children" />
+							</view>
+							<!--#endif-->
+							<!--#ifndef MP-ALIPAY-->
+							<trees v-for="(td, k) in tr.children" v-bind:key="k" :class="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
+							 :nodes="td.children" :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
+							<!--#endif-->
+						</block>
+					</view>
+				</view>
+			</view>
+			<!--#ifdef APP-PLUS-->
+			<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder"
+			 :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
+			<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
+			<!--#endif-->
+			<!--富文本-->
+			<!--#ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY || APP-PLUS-->
+			<rich-text v-else-if="handler.useRichText(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
+			<!--#endif-->
+			<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
+			<rich-text v-else-if="!(n.c||n.continue)" :id="n.attrs.id" :class="_p" :style="n.attrs.contain" :nodes="[n]" />
+			<!--#endif-->
+			<!--#ifdef MP-ALIPAY-->
+			<view v-else :id="n.attrs.id" :class="'_'+n.name+' '+(n.attrs.class||'')" :style="n.attrs.style">
+				<trees :nodes="n.children" />
+			</view>
+			<!--#endif-->
+			<!--#ifndef MP-ALIPAY-->
+			<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :style="n.attrs.style" :nodes="n.children"
+			 :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
+			<!--#endif-->
+		</block>
+	</view>
+</template>
+<script module="handler" lang="wxs" src="./handler.wxs"></script>
+<script module="handler" lang="sjs" src="./handler.sjs"></script>
+<script>
+	import trees from './trees'
+	export default {
+		components: {
+			trees
+		},
+		name: 'trees',
+		data() {
+			return {
+				controls: {},
+				// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
+				imgLoad: false,
+				// #endif
+				// #ifndef APP-PLUS
+				loadVideo: true
+				// #endif
+			}
+		},
+		props: {
+			nodes: Array,
+			// #ifdef MP-WEIXIN || MP-QQ || H5 || APP-PLUS
+			lazyLoad: Boolean,
+			// #endif
+			// #ifdef APP-PLUS
+			loadVideo: Boolean
+			// #endif
+		},
+		mounted() {
+			// 获取顶层组件
+			this.top = this.$parent;
+			while (this.top.$options.name != 'parser') {
+				if (this.top.top) {
+					this.top = this.top.top;
+					break;
+				}
+				this.top = this.top.$parent;
+			}
+		},
+		// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
+		beforeDestroy() {
+			if (this.observer)
+				this.observer.disconnect();
+		},
+		// #endif
+		methods: {
+			// #ifndef MP-ALIPAY
+			play(e) {
+				if (this.top.videoContexts.length > 1 && this.top.autopause)
+					for (var i = this.top.videoContexts.length; i--;)
+						if (this.top.videoContexts[i].id != e.currentTarget.dataset.id)
+							this.top.videoContexts[i].pause();
+			},
+			// #endif
+			imgtap(e) {
+				var attrs = e.currentTarget.dataset.attrs;
+				if (!attrs.ignore) {
+					var preview = true;
+					this.top.$emit('imgtap', {
+						id: e.target.id,
+						src: attrs.src,
+						ignore: () => preview = false
+					})
+					if (preview) {
+						var urls = this.top.imgList,
+							current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
+						uni.previewImage({
+							current,
+							urls
+						})
+					}
+				}
+			},
+			imglongtap(e) {
+				var attrs = e.item.dataset.attrs;
+				if (!attrs.ignore)
+					this.top.$emit('imglongtap', {
+						id: e.target.id,
+						src: attrs.src
+					})
+			},
+			linkpress(e) {
+				var jump = true,
+					attrs = e.currentTarget.dataset.attrs;
+				attrs.ignore = () => jump = false;
+				this.top.$emit('linkpress', attrs);
+				if (jump) {
+					// #ifdef MP
+					if (attrs['app-id']) {
+						return uni.navigateToMiniProgram({
+							appId: attrs['app-id'],
+							path: attrs.path
+						})
+					}
+					// #endif
+					if (attrs.href) {
+						if (attrs.href[0] == '#') {
+							if (this.top.useAnchor)
+								this.top.navigateTo({
+									id: attrs.href.substring(1)
+								})
+						} else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
+							// #ifdef APP-PLUS
+							if (attrs.href.includes('.doc') || attrs.href.includes('.xls') || attrs.href.includes('.ppt') || attrs.href.includes(
+									'.pdf')) {
+								uni.showLoading({
+									title: '文件下载中'
+								})
+								uni.downloadFile({
+									url: attrs.href,
+									success(res) {
+										wx.openDocument({
+											filePath: res.tempFilePath
+										})
+									},
+									complete: uni.hideLoading
+								})
+							} else
+								// #endif
+								uni.setClipboardData({
+									data: attrs.href,
+									success: () =>
+										uni.showToast({
+											title: '链接已复制'
+										})
+								});
+						} else
+							uni.navigateTo({
+								url: attrs.href
+							})
+					}
+				}
+			},
+			error(e) {
+				var context, target = e.currentTarget,
+					source = target.dataset.from;
+				if (source == 'video' || source == 'audio') {
+					// 加载其他 source
+					var index = this.controls[target.id] ? this.controls[target.id].index + 1 : 1;
+					if (index < target.dataset.source.length)
+						this.$set(this.controls, target.id + '.index', index);
+					if (source == 'video') context = uni.createVideoContext(target.id, this);
+				}
+				this.top && this.top.$emit('error', {
+					source,
+					target,
+					errMsg: e.detail.errMsg,
+					errCode: e.detail.errCode,
+					context
+				});
+			},
+			_loadVideo(e) {
+				this.$set(this.controls, e.currentTarget.id, {
+					play: true,
+					index: 0
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+	/* 在这里引入自定义样式 */
+
+	/* 链接和图片效果 */
+	._a {
+		display: inline;
+		color: #366092;
+		word-break: break-all;
+		padding: 1.5px 0 1.5px 0;
+	}
+
+	._hover {
+		opacity: 0.7;
+		text-decoration: underline;
+	}
+
+	._img {
+		display: inline-block;
+		text-indent: 0;
+	}
+
+	/* #ifdef MP-WEIXIN */
+	:host {
+		display: inline;
+	}
+
+	/* #endif */
+
+	/* #ifdef MP */
+	.interlayer {
+		align-content: inherit;
+		align-items: inherit;
+		display: inherit;
+		flex-direction: inherit;
+		flex-wrap: inherit;
+		justify-content: inherit;
+		width: 100%;
+		white-space: inherit;
+	}
+
+	/* #endif */
+
+	._b,
+	._strong {
+		font-weight: bold;
+	}
+
+	._blockquote,
+	._div,
+	._p,
+	._ol,
+	._ul,
+	._li {
+		display: block;
+	}
+
+	._code {
+		font-family: monospace;
+	}
+
+	._del {
+		text-decoration: line-through;
+	}
+
+	._em,
+	._i {
+		font-style: italic;
+	}
+
+	._h1 {
+		font-size: 2em;
+	}
+
+	._h2 {
+		font-size: 1.5em;
+	}
+
+	._h3 {
+		font-size: 1.17em;
+	}
+
+	._h5 {
+		font-size: 0.83em;
+	}
+
+	._h6 {
+		font-size: 0.67em;
+	}
+
+	._h1,
+	._h2,
+	._h3,
+	._h4,
+	._h5,
+	._h6 {
+		display: block;
+		font-weight: bold;
+	}
+
+	._ins {
+		text-decoration: underline;
+	}
+
+	._li {
+		flex: 1;
+		width: 0;
+	}
+
+	._ol-bef {
+		margin-right: 5px;
+		text-align: right;
+		width: 36px;
+	}
+
+	._ul-bef {
+		line-height: normal;
+		margin: 0 12px 0 23px;
+	}
+
+	._ol-bef,
+	._ul_bef {
+		flex: none;
+		user-select: none;
+	}
+
+	._ul-p1 {
+		display: inline-block;
+		height: 0.3em;
+		line-height: 0.3em;
+		overflow: hidden;
+		width: 0.3em;
+	}
+
+	._ul-p2 {
+		border: 0.05em solid black;
+		border-radius: 50%;
+		display: inline-block;
+		height: 0.23em;
+		width: 0.23em;
+	}
+
+	._q::before {
+		content: '"';
+	}
+
+	._q::after {
+		content: '"';
+	}
+
+	._sub {
+		font-size: smaller;
+		vertical-align: sub;
+	}
+
+	._sup {
+		font-size: smaller;
+		vertical-align: super;
+	}
+
+	/* #ifndef MP-WEIXIN */
+	._abbr,
+	._b,
+	._code,
+	._del,
+	._em,
+	._i,
+	._ins,
+	._label,
+	._q,
+	._span,
+	._strong,
+	._sub,
+	._sup {
+		display: inline;
+	}
+
+	/* #endif */
+
+	/* #ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY */
+	.__bdo,
+	.__bdi,
+	.__ruby,
+	.__rt,
+	._entity {
+		display: inline-block;
+	}
+
+	/* #endif */
+	._video {
+		background-color: black;
+		display: inline-block;
+		height: 225px;
+		position: relative;
+		width: 300px;
+	}
+
+	._video::after {
+		border-left-color: white;
+		border-style: solid;
+		border-width: 15px 0 15px 30px;
+		content: '';
+		left: 50%;
+		margin: -15px 0 0 -15px;
+		position: absolute;
+		top: 50%;
+	}
+</style>

+ 676 - 0
sleep/components/parser/parser.vue

@@ -0,0 +1,676 @@
+<!--
+  parser 主模块组件
+  github:https://github.com/jin-yufeng/Parser 
+  docs:https://jin-yufeng.github.io/Parser
+  插件市场:https://ext.dcloud.net.cn/plugin?id=805
+  author:JinYufeng
+  update:2020/03/26
+-->
+<template>
+	<view style="display:inherit;">
+		<slot v-if="!nodes.length" />
+		<view class="top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')" :animation="scaleAm"
+		 @tap="_tap" @touchstart="_touchstart" @touchmove="_touchmove">
+			<!--#ifdef H5-->
+			<div :id="'rtf'+uid"></div>
+			<!--#endif-->
+			<!--#ifndef H5-->
+			<trees :nodes="nodes" :lazy-load="lazyLoad" :loadVideo="loadVideo" />
+			<!--#endif-->
+		</view>
+		<image v-for="(item, index) in imgs" v-bind:key="index" :id="index" :src="item" hidden @load="_load" />
+	</view>
+</template>
+
+<script>
+	// #ifndef H5
+	import trees from './libs/trees';
+	var cache = {},
+		// #ifdef MP-WEIXIN || MP-TOUTIAO
+		fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
+		// #endif
+		Parser = require('./libs/MpHtmlParser.js');
+	var document; // document 补丁包 https://jin-yufeng.github.io/Parser/#/instructions?id=document
+	// 计算 cache 的 key
+	function hash(str) {
+		for (var i = str.length, val = 5381; i--;)
+			val += (val << 5) + str.charCodeAt(i);
+		return val;
+	}
+	// #endif
+	// #ifdef H5
+	var rpx = uni.getSystemInfoSync().screenWidth / 750,
+		cfg = require('./libs/config.js');
+	// #endif
+	export default {
+		name: 'parser',
+		data() {
+			return {
+				// #ifdef APP-PLUS
+				loadVideo: false,
+				// #endif
+				// #ifdef H5
+				uid: this._uid,
+				// #endif
+				scaleAm: '',
+				showAm: '',
+				nodes: [],
+				imgs: []
+			}
+		},
+		// #ifndef H5
+		components: {
+			trees
+		},
+		// #endif
+		props: {
+			'html': null,
+			// #ifndef MP-ALIPAY
+			'autopause': {
+				type: Boolean,
+				default: true
+			},
+			// #endif
+			'autosetTitle': {
+				type: Boolean,
+				default: true
+			},
+			'compress': Number,
+			'domain': String,
+			// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
+			'gestureZoom': Boolean,
+			// #endif
+			// #ifdef MP-WEIXIN || MP-QQ || H5 || APP-PLUS
+			'lazyLoad': Boolean,
+			// #endif
+			'selectable': Boolean,
+			'tagStyle': Object,
+			'showWithAnimation': Boolean,
+			'useAnchor': Boolean,
+			'useCache': Boolean,
+			'xml': Boolean
+		},
+		watch: {
+			html(html) {
+				this.setContent(html);
+			}
+		},
+		mounted() {
+			// #ifdef APP-NVUE
+			console.error('本组件暂不支持 NVUE');
+			// #endif
+			// 图片数组
+			this.imgList = [];
+			this.imgList.each = function(f) {
+				for (var i = 0, len = this.length; i < len; i++)
+					this.setItem(i, f(this[i], i, this));
+			}
+			this.imgList.setItem = function(i, src) {
+				if (!i || !src) return;
+				// #ifndef MP-ALIPAY || APP-PLUS
+				// 去重
+				if (src.indexOf('http') == 0 && this.includes(src)) {
+					var newSrc = '';
+					for (var j = 0, c; c = src[j]; j++) {
+						if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
+						newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
+					}
+					newSrc += src.substr(j);
+					return this[i] = newSrc;
+				}
+				// #endif
+				this[i] = src;
+				// 暂存 data src
+				if (src.includes('data:image')) {
+					var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
+					if (!info) return;
+					// #ifdef MP-WEIXIN || MP-TOUTIAO
+					filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
+					fs && fs.writeFile({
+						filePath,
+						data: info[3],
+						encoding: info[2],
+						success: () => this[i] = filePath
+					})
+					// #endif
+					// #ifdef APP-PLUS
+					filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
+					var bitmap = new plus.nativeObj.Bitmap();
+					bitmap.loadBase64Data(src, () => {
+						bitmap.save(filePath, {}, () => {
+							bitmap.clear()
+							this[i] = filePath;
+						})
+					})
+					// #endif
+				}
+			}
+			if (!this.nodes.length) this.setContent(this.html);
+		},
+		beforeDestroy() {
+			// #ifdef H5
+			if (this._observer) this._observer.disconnect();
+			// #endif
+			this.imgList.each(src => {
+				// #ifdef APP-PLUS
+				if (src && src.includes('_doc')) {
+					plus.io.resolveLocalFileSystemURL(src, entry => {
+						entry.remove();
+					});
+				}
+				// #endif
+				// #ifdef MP-WEIXIN || MP-TOUTIAO
+				if (src && src.includes(uni.env.USER_DATA_PATH))
+					fs && fs.unlink({
+						filePath: src
+					})
+				// #endif
+			})
+			clearInterval(this._timer);
+		},
+		methods: {
+			// #ifdef H5
+			_Dom2Str(nodes) {
+				var str = '';
+				for (var node of nodes) {
+					if (node.type == 'text')
+						str += node.text;
+					else {
+						str += ('<' + node.name);
+						for (var attr in node.attrs || {})
+							str += (' ' + attr + '="' + node.attrs[attr] + '"');
+						if (!node.children || !node.children.length) str += '>';
+						else str += ('>' + this._Dom2Str(node.children) + '</' + node.name + '>');
+					}
+				}
+				return str;
+			},
+			// #endif
+			setContent(html, append) {
+				// #ifdef H5
+				if (!html) {
+					if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
+					return;
+				}
+				if (typeof html != 'string') html = this._Dom2Str(html.nodes || html);
+				// 处理 rpx
+				if (html.includes('rpx'))
+					html = html.replace(/[0-9.]*rpx/g, $ => parseFloat($) * rpx + 'px');
+				var div = document.createElement('div');
+				if (!append) {
+					// 处理 tag-style 和 userAgentStyles
+					let style = '<style>@keyframes show{0%{opacity:0}100%{opacity:1}}';
+					for (var item in cfg.userAgentStyles)
+						style += `${item}{${cfg.userAgentStyles[item]}}`;
+					for (item in this.tagStyle)
+						style += `${item}{${this.tagStyle[item]}}`;
+					style += '</style>';
+					html = style + html;
+					if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
+					this.rtf = div;
+				} else {
+					if (!this.rtf) this.rtf = div;
+					else this.rtf.appendChild(div);
+				}
+				div.innerHTML = html;
+				for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
+					style.innerHTML = style.innerHTML.replace(/\s*body/g, '#rtf' + this._uid);
+					style.setAttribute('scoped', 'true');
+				}
+				// 懒加载
+				if (!this._observer && this.lazyLoad && IntersectionObserver) {
+					this._observer = new IntersectionObserver(changes => {
+						for (let item, i = 0; item = changes[i++];) {
+							if (item.isIntersecting) {
+								item.target.src = item.target.getAttribute('data-src');
+								item.target.removeAttribute('data-src');
+								this._observer.unobserve(item.target);
+							}
+						}
+					}, {
+						rootMargin: '900px 0px 900px 0px'
+					})
+				}
+				var _ts = this;
+				// 获取标题
+				var title = this.rtf.getElementsByTagName('title');
+				if (title.length && this.autosetTitle)
+					uni.setNavigationBarTitle({
+						title: title[0].innerText
+					})
+				// 图片处理
+				this.imgList.length = 0;
+				var imgs = this.rtf.getElementsByTagName('img');
+				for (let i = 0, j = 0, img; img = imgs[i]; i++) {
+					img.style.maxWidth = '100%';
+					var src = img.getAttribute('src');
+					if (this.domain && src) {
+						if (src[0] == '/') {
+							if (src[1] == '/')
+								img.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
+							else img.src = this.domain + src;
+						} else if (!src.includes('://')) img.src = this.domain + '/' + src;
+					}
+					if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
+						img.i = j++;
+						_ts.imgList.push(img.src || img.getAttribute('data-src'));
+						img.onclick = function() {
+							var preview = true;
+							this.ignore = () => preview = false;
+							_ts.$emit('imgtap', this);
+							if (preview) {
+								uni.previewImage({
+									current: this.i,
+									urls: _ts.imgList
+								});
+							}
+						}
+					}
+					img.onerror = function() {
+						_ts.$emit('error', {
+							source: 'img',
+							target: this
+						});
+					}
+					if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
+						img.setAttribute('data-src', img.src);
+						img.removeAttribute('src');
+						this._observer.observe(img);
+					}
+				}
+				// 链接处理
+				var links = this.rtf.getElementsByTagName('a');
+				for (var link of links) {
+					link.onclick = function() {
+						var jump = true,
+							href = this.getAttribute('href');
+						_ts.$emit('linkpress', {
+							href,
+							ignore: () => jump = false
+						});
+						if (jump && href) {
+							if (href[0] == '#') {
+								if (_ts.useAnchor) {
+									_ts.navigateTo({
+										id: href.substr(1)
+									})
+								}
+							} else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
+								return true;
+							else {
+								uni.navigateTo({
+									url: href
+								})
+							}
+						}
+						return false;
+					}
+				}
+				// 视频处理
+				var videos = this.rtf.getElementsByTagName('video');
+				_ts.videoContexts = videos;
+				for (let video, i = 0; video = videos[i++];) {
+					video.style.maxWidth = '100%';
+					video.onerror = function() {
+						_ts.$emit('error', {
+							source: 'video',
+							target: this
+						});
+					}
+					video.onplay = function() {
+						if (_ts.autopause)
+							for (let item, i = 0; item = _ts.videoContexts[i++];)
+								if (item != this) item.pause();
+					}
+				}
+				// 音频处理
+				var audios = this.rtf.getElementsByTagName('audios');
+				for (var audio of audios)
+					audio.onerror = function() {
+						_ts.$emit('error', {
+							source: 'audio',
+							target: this
+						});
+					}
+				this.document = this.rtf;
+				if (!append) document.getElementById('rtf' + this._uid).appendChild(this.rtf);
+				this.$nextTick(() => {
+					this.nodes = [1];
+					this.$emit('load');
+				})
+				setTimeout(() => this.showAm = '', 500);
+				// #endif
+				// #ifndef H5
+				var nodes;
+				if (!html)
+					return this.nodes = [];
+				else if (typeof html == 'string') {
+					let parser = new Parser(html, this);
+					// 缓存读取
+					if (this.useCache) {
+						var hashVal = hash(html);
+						if (cache[hashVal])
+							nodes = cache[hashVal];
+						else {
+							nodes = parser.parse();
+							cache[hashVal] = nodes;
+						}
+					} else nodes = parser.parse();
+					this.$emit('parse', nodes);
+				} else if (Object.prototype.toString.call(html) == '[object Array]') {
+					// 非本插件产生的 array 需要进行一些转换
+					if (html.length && html[0].PoweredBy != 'Parser') {
+						let parser = new Parser(html, this);
+						(function f(ns) {
+							for (var i = 0, n; n = ns[i]; i++) {
+								if (n.type == 'text') continue;
+								n.attrs = n.attrs || {};
+								for (var item in n.attrs)
+									if (typeof n.attrs[item] != 'string') n.attrs[item] = n.attrs[item].toString();
+								parser.matchAttr(n, parser);
+								if (n.children && n.children.length) {
+									parser.STACK.push(n);
+									f(n.children);
+									parser.popNode(parser.STACK.pop());
+								} else n.children = void 0;
+							}
+						})(html);
+					}
+					nodes = html;
+				} else if (typeof html == 'object' && html.nodes) {
+					nodes = html.nodes;
+					console.warn('错误的 html 类型:object 类型已废弃');
+				} else
+					return console.warn('错误的 html 类型:' + typeof html);
+				// #ifdef APP-PLUS
+				this.loadVideo = false;
+				// #endif
+				if (document) this.document = new document(this.nodes, 'nodes', this);
+				if (append) this.nodes = this.nodes.concat(nodes);
+				else this.nodes = nodes;
+				if (nodes.length && nodes[0].title && this.autosetTitle)
+					uni.setNavigationBarTitle({
+						title: nodes[0].title
+					})
+				this.$nextTick(() => {
+					this.imgList.length = 0;
+					this.videoContexts = [];
+					// #ifdef MP-TOUTIAO
+					setTimeout(() => {
+						// #endif
+						var f = (cs) => {
+							for (let i = 0, c; c = cs[i++];) {
+								if (c.$options.name == 'trees') {
+									for (var j = c.nodes.length, item; item = c.nodes[--j];) {
+										if (item.c) continue;
+										if (item.name == 'img') {
+											this.imgList.setItem(item.attrs.i, item.attrs.src);
+											// #ifndef MP-ALIPAY
+											if (!c.observer && !c.imgLoad && item.attrs.i != '0') {
+												if (this.lazyLoad && uni.createIntersectionObserver) {
+													c.observer = uni.createIntersectionObserver(c);
+													c.observer.relativeToViewport({
+														top: 900,
+														bottom: 900
+													}).observe('._img', () => {
+														c.imgLoad = true;
+														c.observer.disconnect();
+													})
+												} else
+													c.imgLoad = true;
+											}
+											// #endif
+										}
+										// #ifndef MP-ALIPAY
+										else if (item.name == 'video') {
+											var ctx = uni.createVideoContext(item.attrs.id, c);
+											ctx.id = item.attrs.id;
+											this.videoContexts.push(ctx);
+										}
+										// #endif
+										// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
+										if (item.attrs && item.attrs.id) {
+											this.anchors = this.anchors || [];
+											this.anchors.push({
+												id: item.attrs.id,
+												node: c
+											})
+										}
+										// #endif
+									}
+								}
+								if (c.$children.length)
+									f(c.$children)
+							}
+						}
+						f(this.$children);
+						// #ifdef MP-TOUTIAO
+					}, 200)
+					this.$emit('load');
+					// #endif
+					// #ifdef APP-PLUS
+					setTimeout(() => {
+						this.loadVideo = true;
+					}, 3000);
+					// #endif
+				})
+				// #endif
+				var height;
+				clearInterval(this._timer);
+				this._timer = setInterval(() => {
+					// #ifdef H5
+					var res = [this.rtf.getBoundingClientRect()];
+					// #endif
+					// #ifndef APP-PLUS || H5
+					this.createSelectorQuery()
+					// #endif
+					// #ifdef APP-PLUS
+					uni.createSelectorQuery().in(this)
+						// #endif
+						// #ifndef H5
+						.select('.top').boundingClientRect().exec(res => {
+							// #endif
+							this.width = res[0].width;
+							if (res[0].height == height) {
+								this.$emit('ready', res[0])
+								clearInterval(this._timer);
+							}
+							height = res[0].height;
+							// #ifndef H5
+						});
+					// #endif
+				}, 350)
+				if (this.showWithAnimation && !append) this.showAm = 'animation:show .5s';
+			},
+			getText(ns = this.html || this.nodes) {
+				// #ifdef H5
+				return this.rtf.innerText;
+				// #endif
+				// #ifndef H5
+				var txt = '';
+				for (var i = 0, n; n = ns[i++];) {
+					if (n.type == 'text') txt += n.txt.replace(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
+						.replace(/&amp;/g, '&');
+					else if (n.type == 'br') txt += '\n';
+					else {
+						// 块级标签前后加换行
+						var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
+							'0' && n.name[1] < '7');
+						if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
+						if (n.children) txt += this.getText(n.children);
+						if (block && txt[txt.length - 1] != '\n') txt += '\n';
+						else if (n.name == 'td' || n.name == 'th') txt += '\t';
+					}
+				}
+				return txt;
+				// #endif
+			},
+			navigateTo(obj) {
+				if (!this.useAnchor)
+					return obj.fail && obj.fail({
+						errMsg: 'Anchor is disabled'
+					})
+				// #ifdef H5
+				if (!obj.id) {
+					window.scrollTo(0, this.rtf.offsetTop);
+					return obj.success && obj.success({
+						errMsg: 'pageScrollTo:ok'
+					});
+				}
+				var target = document.getElementById(obj.id);
+				if (!target) return obj.fail && obj.fail({
+					errMsg: 'Label not found'
+				});
+				obj.scrollTop = this.rtf.offsetTop + target.offsetTop;
+				uni.pageScrollTo(obj);
+				// #endif
+				// #ifndef H5
+				var Scroll = (selector, component) => {
+					uni.createSelectorQuery().in(component ? component : this).select(selector).boundingClientRect().selectViewport()
+						.scrollOffset()
+						.exec(res => {
+							if (!res || !res[0])
+								return obj.fail && obj.fail({
+									errMsg: 'Label not found'
+								});
+							obj.scrollTop = res[1].scrollTop + res[0].top;
+							uni.pageScrollTo(obj);
+						})
+				}
+				if (!obj.id) Scroll('.top');
+				else {
+					// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
+					Scroll('.top >>> #' + obj.id + ', .top >>> .' + obj.id);
+					// #endif
+					// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
+					for (var anchor of this.anchors)
+						if (anchor.id == obj.id)
+							Scroll('#' + obj.id + ', .' + obj.id, anchor.node);
+					// #endif
+				}
+				// #endif
+			},
+			getVideoContext(id) {
+				if (!id) return this.videoContexts;
+				else
+					for (var i = this.videoContexts.length; i--;)
+						if (this.videoContexts[i].id == id) return this.videoContexts[i];
+			},
+			// 预加载
+			preLoad(html, num) {
+				// #ifdef H5
+				if (html.constructor == Array)
+					html = this._Dom2Str(html);
+				var contain = document.createElement('div');
+				contain.innerHTML = html;
+				var imgs = contain.querySelectorAll('img');
+				for (var i = imgs.length - 1; i >= num; i--)
+					imgs[i].removeAttribute('src');
+				// #endif
+				// #ifndef H5
+				if (typeof html == 'string') {
+					var id = hash(html);
+					html = new Parser(html, this).parse();
+					cache[id] = html;
+				}
+				var wait = [];
+				(function f(ns) {
+					for (var i = 0, n; n = ns[i++];) {
+						if (n.name == 'img' && n.attrs.src && !wait.includes(n.attrs.src))
+							wait.push(n.attrs.src);
+						f(n.children || []);
+					}
+				})(html);
+				if (num) wait = wait.slice(0, num);
+				this._wait = (this._wait || []).concat(wait);
+				if (!this.imgs) this.imgs = this._wait.splice(0, 15);
+				else if (this.imgs.length < 15)
+					this.imgs = this.imgs.concat(this._wait.splice(0, 15 - this.imgs.length));
+				// #endif
+			},
+			_load(e) {
+				// #ifndef H5
+				if (this._wait.length)
+					this.$set(this.imgs, e.target.id, this._wait.shift());
+				// #endif
+			},
+			_tap(e) {
+				// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
+				if (this.gestureZoom && e.timeStamp - this._lastT < 300) {
+					var initY = e.touches[0].pageY - e.currentTarget.offsetTop;
+					if (this._zoom) {
+						this._scaleAm.translateX(0).scale(1).step();
+						uni.pageScrollTo({
+							scrollTop: (initY + this._initY) / 2 - e.touches[0].clientY,
+							duration: 400
+						})
+					} else {
+						var initX = e.touches[0].pageX - e.currentTarget.offsetLeft;
+						this._initY = initY;
+						this._scaleAm = uni.createAnimation({
+							transformOrigin: `${initX}px ${this._initY}px 0`,
+							timingFunction: 'ease-in-out'
+						});
+						// #ifdef MP-TOUTIAO
+						this._scaleAm.opacity(1);
+						// #endif
+						this._scaleAm.scale(2).step();
+						this._tMax = initX / 2;
+						this._tMin = (initX - this.width) / 2;
+						this._tX = 0;
+					}
+					this._zoom = !this._zoom;
+					this.scaleAm = this._scaleAm.export();
+				}
+				this._lastT = e.timeStamp;
+				// #endif
+			},
+			_touchstart(e) {
+				// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
+				if (e.touches.length == 1)
+					this._initX = this._lastX = e.touches[0].pageX;
+				// #endif
+			},
+			_touchmove(e) {
+				// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
+				var diff = e.touches[0].pageX - this._lastX;
+				if (this._zoom && e.touches.length == 1 && Math.abs(diff) > 20) {
+					this._lastX = e.touches[0].pageX;
+					if ((this._tX <= this._tMin && diff < 0) || (this._tX >= this._tMax && diff > 0))
+						return;
+					this._tX += (diff * Math.abs(this._lastX - this._initX) * 0.05);
+					if (this._tX < this._tMin) this._tX = this._tMin;
+					if (this._tX > this._tMax) this._tX = this._tMax;
+					this._scaleAm.translateX(this._tX).step();
+					this.scaleAm = this._scaleAm.export();
+				}
+				// #endif
+			}
+		}
+	}
+</script>
+
+<style>
+	@keyframes show {
+		0% {
+			opacity: 0
+		}
+
+		100% {
+			opacity: 1;
+		}
+	}
+
+	/* #ifdef MP-WEIXIN */
+	:host {
+		display: block;
+		overflow: scroll;
+		-webkit-overflow-scrolling: touch;
+	}
+
+	.top {
+		display: inherit;
+	}
+
+	/* #endif */
+</style>

+ 8 - 5
sleep/main.js

@@ -30,9 +30,12 @@ Vue.component('uni-section', UniSection)
 
 import {timeSlot} from '@/components/wanghexu-timeslot/wanghexu-timeslot.vue'
 Vue.component('time-slot', timeSlot)
-
-const app = new Vue({
-	...App,
-	store
-})
+
+import mixin from './utils/vue-mixin'
+Vue.mixin(mixin)
+
+const app = new Vue({
+	...App,
+	store
+})
 app.$mount()

+ 1 - 1
sleep/manifest.json

@@ -85,7 +85,7 @@
     },
     "quickapp" : {},
     "mp-weixin" : {
-        "appid" : "wx39e390ff4635fc8e",
+        "appid" : "wx97a109e5a40588dc",
         "requiredBackgroundModes" : [ "audio" ],
         "setting" : {
             "urlCheck" : true,

File diff suppressed because it is too large
+ 2325 - 9
sleep/package-lock.json


+ 18 - 19
sleep/package.json

@@ -1,21 +1,20 @@
 {
-    "id": "ccq-week-picker",
-    "name": "周选择Picker",
-    "version": "1.2.2",
-    "description": "通过dayjs获取周信息,可跨月获取周信息",
-    "keywords": [
-        "周",
-        "日期选择",
-        "日期弹出层",
-        "星期"
-    ],
-    "dcloudext": {
-        "category": [
-            "前端组件",
-            "通用组件"
-        ]
-    },
-    "dependencies": {
-        "dayjs": "^1.11.8"
-    }
+  "name": "sleep",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "dependencies": {
+    "dayjs": "^1.11.8"
+  },
+  "devDependencies": {
+    "babel-cli": "^6.26.0",
+    "js-md5": "^0.7.3",
+    "wx-promise-pro": "^2.6.0"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC"
 }

+ 8 - 1
sleep/pages.json

@@ -107,7 +107,14 @@
                 "navigationBarTitleText": "",
                 "enablePullDownRefresh": false
             }
-        }
+        },{
+			"path" : "pages/healthAdvert/careHealthAdvertLook",
+			"style" :
+			{
+				"navigationBarTitleText": "内容详情",
+				"enablePullDownRefresh": false
+			}
+		}
     ],
 	"globalStyle": {
 		"navigationStyle": "custom",

+ 22 - 4
sleep/pages/auth/auth.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="flex-col section">
 	  <cu-custom bgColor="#fff"><view slot="content" style="color: #FFFFFF">UU睡眠</view></cu-custom>
-	  
+
 	  <view class="flex-col items-center group_3 space-y-20">
 	    <text class="text_2 text_3">UU睡眠</text>
 	    <text class="font_1 text_2">AI时代,全家人的睡眠健康顾问</text>
@@ -17,17 +17,35 @@
 	      <text class="font_3 text_5">获取你的手机号码</text>
 	    </view>
 	  </view>
-	  <view class="flex-col justify-start items-center self-end button"><text class="font_2 text_6">确认授权</text></view>
+	  <view class="flex-col justify-start items-center self-end button" @click="getUserProfile"><text class="font_2 text_6">确认授权</text></view>
 	</view>
 </template>
 
 <script>
+import Storage from '@/utils/storage'
 	export default {
 		data() {
 			return {
-				
+
 			};
-		}
+		},
+    methods: {
+      getUserProfile(e) {
+        wx.getUserProfile({
+          desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
+          success: (res) => {
+            const data = {
+              gender: res.userInfo.gender,
+              nickName: res.userInfo.nickName,
+            }
+            Storage.setItem("agreeLogin", true)
+            Storage.setItem("myWeixinInfo", res.userInfo)
+            // this.hasAuth = true;
+            this.toAutoLogin()
+          }
+        })
+      }
+    }
 	}
 </script>
 

+ 67 - 0
sleep/pages/healthAdvert/careHealthAdvertLook.vue

@@ -0,0 +1,67 @@
+<template>
+  <view>
+    <cu-custom isBack="true" bgColor="#000000">
+      <view slot="content" style="color: #FFFFFF">UU睡眠</view>
+    </cu-custom>
+    <view class="bg-white">
+      <view class="solids-bottom padding-xs flex align-center">
+        <view class="flex-sub">
+          <view class="solid-bottom text-xl padding">
+            <text class="text-left text-black text-bold">{{ healthAdvert.title }}</text>
+          </view>
+          <view class="text-left padding">{{ healthAdvert.summary }}</view>
+        </view>
+
+      </view>
+      <view class="bg-white padding">
+        <w-parse v-if="healthAdvert.content" :html="healthAdvert.content"/>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import * as API_doctorAdvert from '@/api/doctor_advert'
+import wParse from '@/components/parser/parser.vue'
+
+export default {
+  components: {
+    wParse
+  },
+  data() {
+    return {
+      healthAdvert: {},
+      healthAdvertId: ''
+    }
+  },
+  mounted() {
+    this.getCareHealthAdvert()
+  },
+  onLoad(option) {
+    const {id} = option
+    this.healthAdvertId = id
+  },
+  methods: {
+    getCareHealthAdvert() {
+      let _this = this
+      API_doctorAdvert.getDoctorAdvert(this.healthAdvertId).then(res => {
+        _this.healthAdvert = res
+      })
+    },
+    getColor(index) {
+      if (index >= this.ColorList.length) {
+        index = index - this.ColorList.length
+      }
+      return 'bg-' + this.ColorList[index].name
+    }
+  },
+
+}
+</script>
+
+<style scoped>
+/deep/ video {
+  width: 100% !important;
+  height: 225px;
+}
+</style>

+ 202 - 59
sleep/pages/home/index.vue

@@ -12,7 +12,7 @@
 					<text class="font_3 text_3 home-header-subtitle">我是小U,您的健康智慧管家。</text>
 				</view>
 
-				<view class="flex-row items-center home_add_devive space-x-10">
+				<view class="flex-row items-center home_add_devive space-x-10" @click="addBindDevice">
 				  <image
 				    class="image_4"
 					src="../../static/home/homeIconAdd.png"
@@ -20,24 +20,24 @@
 				  <text class="text_5">添加设备</text>
 				</view>
 
-				<view class="home-ad flex-col group_5 space-y-40">
-					<view class="flex-col home-header space-y-6">
-						<view class="flex-row items-center space-x-8">
-							<text class="font_4 text_8">UU AI智能睡眠监测仪</text>
-							<view class="flex-row items-center section_7">
-								<text class="font_5 text_9">去看看</text>
-								<image
-									class="shrink-0 image_9"
-									src="../../static/home/homeIconGO.png"
-								/>
-							</view>
-						</view>
+<!--				<view class="home-ad flex-col group_5 space-y-40">-->
+<!--					<view class="flex-col home-header space-y-6">-->
+<!--						<view class="flex-row items-center space-x-8">-->
+<!--							<text class="font_4 text_8">UU AI智能睡眠监测仪</text>-->
+<!--							<view class="flex-row items-center section_7">-->
+<!--								<text class="font_5 text_9">去看看</text>-->
+<!--								<image-->
+<!--									class="shrink-0 image_9"-->
+<!--									src="../../static/home/homeIconGO.png"-->
+<!--								/>-->
+<!--							</view>-->
+<!--						</view>-->
 
-						<view class="flex-row items-center space-x-10">
-							<text class="font_5 text_3 text_10">AI时代,全家人的睡眠健康顾问</text>
-						</view>
-					</view>
-				</view>
+<!--						<view class="flex-row items-center space-x-10">-->
+<!--							<text class="font_5 text_3 text_10">AI时代,全家人的睡眠健康顾问</text>-->
+<!--						</view>-->
+<!--					</view>-->
+<!--				</view>-->
 
 				<view class="flex-col">
 				  <view class="flex-col group_5">
@@ -145,26 +145,26 @@
 <!--					</view>-->
 				</view>
 
-				<view class="home-report flex-col group_5 space-y-40">
-					<view class="flex-col home-header space-y-6">
-						<view class="flex-row items-center space-x-8">
-							<text class="font_4 text_8">睡眠报告</text>
-							<view class="flex-row items-center section_7">
-								<text class="font_5 text_9">去看看</text>
-								<image
-									class="shrink-0 image_9"
-									src="../../static/home/homeIconGO.png"
-								/>
-							</view>
-						</view>
+<!--				<view class="home-report flex-col group_5 space-y-40">-->
+<!--					<view class="flex-col home-header space-y-6">-->
+<!--						<view class="flex-row items-center space-x-8">-->
+<!--							<text class="font_4 text_8">睡眠报告</text>-->
+<!--							<view class="flex-row items-center section_7">-->
+<!--								<text class="font_5 text_9">去看看</text>-->
+<!--								<image-->
+<!--									class="shrink-0 image_9"-->
+<!--									src="../../static/home/homeIconGO.png"-->
+<!--								/>-->
+<!--							</view>-->
+<!--						</view>-->
 
-						<view class="flex-row items-center space-x-10">
-							<text class="font_5 text_3 text_10">小U已为您生成最后一份睡眠报告</text>
-							<text class="font_5 text_3 text_11">2021-08-24</text>
-						</view>
-					</view>
+<!--						<view class="flex-row items-center space-x-10">-->
+<!--							<text class="font_5 text_3 text_10">小U已为您生成最后一份睡眠报告</text>-->
+<!--							<text class="font_5 text_3 text_11">2021-08-24</text>-->
+<!--						</view>-->
+<!--					</view>-->
 
-				</view>
+<!--				</view>-->
 
 				<!-- <view class="flex-col space-y-40 home-data">
 					<view class="flex-row group_6 equal-division">
@@ -263,7 +263,7 @@
 				<image class="shrink-0 image_btn" src="../../static/home/homeIconHeart.png" />
 				<text class="btn-home-text">远程关爱</text>
 			</view>
-			<view @click="handleJumpSettings" class="btn-home-btns btn-home-btns2 items-center">
+			<view @click="handleAlertSetting" class="btn-home-btns btn-home-btns2 items-center">
 				<image class="shrink-0 image_btn" src="../../static/mine/myIconAlert.png" />
 				<text class="btn-home-text">报警设置</text>
 			</view>
@@ -277,7 +277,7 @@
 						/>
 						<text class="font_1">实时预警</text>
 					</view>
-          <view v-if="list_alerts.length === 0"  @click="handleAlertSetting" class="flex-row items-center home_blank_alert space-x-16">
+          <view v-if="list_alerts.length === 0"  class="flex-row items-center home_blank_alert space-x-16">
             <image
                 class="image_8"
                 src="../../static/home/homeIconGift.png"
@@ -287,7 +287,7 @@
               <text class="font_5">暂时没有预警信息</text>
             </view>
           </view>
-					<view v-else @click="handleAlertSetting" class="flex-col home-alert-list">
+					<view v-else class="flex-col home-alert-list">
 						<view class="flex-row justify-between items-center list-item" :key="i" v-for="(item, i) in list_alerts">
 							<view class="flex-row items-center space-x-6">
 								<image
@@ -307,6 +307,32 @@
 			</view>
 	  </view>
 
+    <view class="cu-modal" :class="bindShow?'show':''">
+      <view class="cu-dialog">
+        <view class="cu-bar bg-white justify-end">
+          <view style="color: black" class="content">设备绑定</view>
+          <view class="action" @tap="bindShow=false">
+            <text class="cuIcon-close text-red"></text>
+          </view>
+        </view>
+        <view class="">
+          <form>
+            <view class="cu-form-group solid-bottom">
+              <view class="title">扫描二维码</view>
+              <input type="text" v-model="bindSn" class="text-left"/>
+              <text class="cuIcon-scan text-xxl text-green" style="font-size: 50rpx;"
+                    @click="scan"></text>
+            </view>
+          </form>
+        </view>
+        <view class="cu-bar bg-white justify-end">
+          <view class="action">
+            <button class="cu-btn bg-green margin-left" @tap="bindDevice">确定</button>
+          </view>
+        </view>
+      </view>
+    </view>
+
 	  <!-- <tab-bar currentPage="home" /> -->
 
 	</view>
@@ -356,22 +382,59 @@ export default {
       },
       deviceList: [], // 设备列表
       lastMsgTime: 0, // 最后一次设备消息时间
-      memberId: 50289 // 先写死
+      memberId: '',
+      bindSn: '',
+      bindShow: false
 		};
 	},
   onLoad() {
-    this.API_getDeviceList()
+    wx.getSetting({
+      complete: (res) => {
+        if (!res.authSetting['scope.userInfo']) {
+          return
+        }
+        console.log('222222222222222222222222222')
+        const memberId = Storage.getItem('uid')
+        if (memberId) {
+          this.memberId = memberId
+          console.log('this.memberId===', this.memberId)
+          this.API_getDeviceList()
+        } else {
+          this.checkHasLogin()
+          const _this = this
+          let initTimer = setInterval(function () {
+            const memberId = Storage.getItem('uid')
+            if (memberId) {
+              _this.memberId = memberId
+              clearInterval(initTimer)
+              _this.API_getDeviceList()
+            }
+          }, 500)
+        }
+      }
+    })
+
+
   },
   onUnload() {
-    this.mySocketTask.onClose(res => {
-      this.connected = false
-      clearInterval(this.timer)
-    })
+    if (this.mySocketTask) {
+      this.mySocketTask.onClose(res => {
+        this.connected = false
+        clearInterval(this.timer)
+      })
+    }
   },
   onShow() {
     const imei = Storage.getItem('nowChangeDevice')
     if (imei) {
       this.homeFrom.nowDevice = imei
+      this.deviceAdded = true
+      console.log('1111111111111111111111111111')
+      if (!this.connected && this.memberId) {
+        this.init()
+      }
+    } else {
+      this.deviceAdded = false
     }
   },
 	methods: {
@@ -380,11 +443,6 @@ export default {
 				url: '/pages/shareUser/shareUser'
 			})
 		},
-		handleJumpSettings() {
-			uni.navigateTo({
-				url: '/pages/alertSetting/alertSetting'
-			})
-		},
     API_getDeviceList() {
       const _this = this
       API_deviceMemberBind.getDeviceByDtypeAndMemberId({dtype: '睡眠床垫', memberId: this.memberId}).then(res => {
@@ -396,7 +454,9 @@ export default {
           _this.homeFrom.nowDevice = res[0].imei
           Storage.setItem("nowChangeDevice", _this.homeFrom.nowDevice)
           Storage.setItem("myBindDevices", res)
-          _this.init()
+          if (!this.connected) {
+            _this.init()
+          }
         }
       })
     },
@@ -410,13 +470,6 @@ export default {
     },
     connect() {
       let _this = this
-      if (this.connected || this.connecting) {
-        uni.showModal({
-          content: '正在连接或者已经连接,请勿重复连接!',
-          showCancel: false
-        });
-        return false
-      }
       this.connecting = true
       // let wsUrl = api.base.replace('http', 'ws')
       this.mySocketTask = uni.connectSocket({
@@ -660,6 +713,96 @@ export default {
     },
     formatSosTime(value) {
       return value.substring(10, value.length - 3)
+    },
+    // 扫描条码
+    scan() {
+      let _this = this
+      uni.scanCode({
+        success: function (res) {
+          console.log(res, res.result.length)
+          if (res.result.length > 20) {
+            if (res.scanType === 'QR_CODE') {
+              const urlStr = res.result
+              const qs = Foundation.getQueryObject(urlStr)
+              if (qs.mac) {
+                _this.bindSn = qs.mac
+              } else {
+                uni.showToast({
+                  title: '请扫码正确的睡眠床垫二维码',
+                  icon: 'none'
+                })
+              }
+            } else {
+              _this.bindSn = res.result
+            }
+          } else {
+            _this.bindSn = res.result
+          }
+        },
+        fail: (err) => {
+          console.log(err)
+          // #ifdef MP
+          uni.getSetting({
+            success: (res) => {
+              let authStatus = res.authSetting['scope.camera'];
+              if (!authStatus) {
+                uni.showModal({
+                  title: '授权失败',
+                  content: 'app需要使用您的相机,请在设置界面打开相关权限',
+                  success: (res) => {
+                    if (res.confirm) {
+                      uni.openSetting()
+                    }
+                  }
+                })
+              }
+            }
+          })
+          // #endif
+        }
+      })
+    },
+    bindDevice() {
+      const params = {
+        imei: this.bindSn,
+        memberId: this.memberId,
+        dtype: '睡眠床垫'
+      }
+      this.bindShow = false
+      let _this = this
+      API_deviceMemberBind.bindDeviceByDtype(params).then(res => {
+        if (res.success) {
+          _this.bindSn = ''
+          uni.showToast({
+            title: '绑定成功',
+          })
+          _this.API_getDeviceList()
+        } else {
+          uni.showToast({
+            title: res.message,
+            icon: 'none'
+          })
+        }
+      })
+    },
+    addBindDevice() {
+		  if (this.memberId) {
+        this.bindShow = 'show'
+      } else {
+        uni.showModal({
+          title: '提示',
+          content: "您还没有登录,是否前往登录?",
+          confirmText: "确定",
+          showCancel: true,
+          success: function(res) {
+            if (res.confirm) {
+              uni.reLaunch({
+                url:'/pages/auth/auth'
+              })
+            }
+          }
+        })
+      }
     }
 	}
 };
@@ -1384,7 +1527,7 @@ export default {
 		font-weight: 500;
 		letter-spacing: 0px;
 		line-height: 2.6rem;
-		
+
 		.image_btn {
 			width: 1.4rem;
 			height: 1.4rem;

+ 17 - 2
sleep/pages/index/index.vue

@@ -33,10 +33,11 @@
 
     <view class="main-container">
       <view class="music-wrapper" v-if="healthAdvertList.length > 0">
-        <box-title title="睡眠小知识" buttonName="更多" iconName="kaishi2" @handlePlay="handlePlay('dayRecommendList')"></box-title>
+<!--        <box-title title="睡眠小知识" buttonName="更多" iconName="kaishi2" @handlePlay="handlePlay('dayRecommendList')"></box-title>-->
+        <box-title title="睡眠小知识"></box-title>
 
         <view class="flex-col justify-start knowledge-list">
-          <view v-for="(item, index) in healthAdvertList" :key="index" class="flex-row items-center space-x-14 knowledge-item">
+          <view v-for="(item, index) in healthAdvertList" :key="index" class="flex-row items-center space-x-14 knowledge-item" @click="goToDetails(item.id,item.html_url)">
             <view class="flex-col flex-auto space-y-8">
               <text class="self-start font_4 knowledge-item-title">{{item.title}}</text>
               <text v-if="item.summary.length>25" class="knowledge-item-desc">
@@ -98,6 +99,20 @@ export default {
         this.healthAdvertList = res.data
       })
     },
+    // 去往健康宣讲详情页
+    goToDetails(id, url) {
+      console.log('url====', url)
+      if (url === null || url === '') {
+        uni.navigateTo({
+          url: '/pages/healthAdvert/careHealthAdvertLook?id=' + id
+        })
+      } else {
+        uni.navigateTo({
+          url: '/pages/care/part/webview?url=' + url
+        })
+      }
+
+    },
 		//播放全部 猜你喜欢
 		// handlePlay(key) {
 		// 	const list = this[key].map(item => {

+ 307 - 195
sleep/pages/login/index.vue

@@ -1,208 +1,320 @@
 <template>
-	<view class="login-container">
-		<!-- <image src="../../static/loginBg.png" class="background-img"></image> -->
-		<view class="absolve-wrapper">
-			<!-- <view class="logo"><image src="../../static/logo.png" class="logo-img"></image></view> -->
-			<view class="text-title">我有故事和歌,你听吗?</view>
-			<view class="login-main">
-				<view class="input-wrapper">
-					<i class="iconfont icon-phone icon"></i>
-					<input placeholder="请输入手机号" v-model="userInfo.phone" maxlength="11" />
-				</view>
-				<view class="input-wrapper">
-					<i class="iconfont icon-password icon"></i>
-					<input v-if="isText" type="text" placeholder="请输入密码" v-model="userInfo.password" />
-					<input v-else type="password" placeholder="请输入密码" v-model="userInfo.password" />
-					<i class="iconfont eye" :class="isText ? 'icon-openEye' : 'icon-closeEye'" @click="isText = !isText"></i>
-				</view>
-				<button class="cu-btn round login-button lg cu-load" :class="{ loading: loading }" @click="getUserInfo">登录</button>
-			</view>
-			<view class="other-login">
-				<view class="other-text">第三方登录</view>
-				<view class="login-list">
-					<i class="iconfont icon-weixin other-item" @click="handleTest"></i>
-					<i class="iconfont icon-qq other-item" @click="handleTest"></i>
-					<i class="iconfont icon-weibo other-item" @click="handleTest"></i>
-				</view>
-			</view>
-		</view>
-	</view>
+  <view class="login-container">
+    <!-- <image src="../../static/loginBg.png" class="background-img"></image> -->
+    <view class="absolve-wrapper">
+      <!-- <view class="logo"><image src="../../static/logo.png" class="logo-img"></image></view> -->
+      <view class="text-title">我有故事和歌,你听吗?</view>
+      <view class="login-main">
+        <view class="input-wrapper">
+          <i class="iconfont icon-phone icon"></i>
+          <input placeholder="请输入账户名或手机号" v-model="userFrom.username"/>
+        </view>
+        <view class="input-wrapper">
+          <i class="iconfont icon-password icon"></i>
+          <input v-if="isText" type="text" placeholder="请输入密码" v-model="userFrom.password"/>
+          <input v-else type="password" placeholder="请输入密码" v-model="userFrom.password"/>
+          <i class="iconfont eye" :class="isText ? 'icon-openEye' : 'icon-closeEye'" @click="isText = !isText"></i>
+        </view>
+        <button style="background-image: linear-gradient(45deg, #39b54a, #8dc63f);"
+                class="cu-btn round login-button lg cu-load" :class="{ loading: loading }" @click="getUserInfo">登录
+        </button>
+
+        <button v-if="haslogin.loginType === 'auto'" class="cu-btn round login-button lg cu-load"
+                :class="{ loading: loading }" @click="loginByHasLogin">
+          微信快捷登录
+        </button>
+        <button v-if="haslogin.loginType === 'hand_movement'" class="cu-btn round login-button lg cu-load"
+                :class="{ loading: loading }" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
+          微信快捷登录
+        </button>
+      </view>
+      <!--			<view class="other-login">-->
+      <!--				<view class="other-text">第三方登录</view>-->
+      <!--				<view class="login-list">-->
+      <!--					<i class="iconfont icon-weixin other-item" @click="handleTest"></i>-->
+      <!--					<i class="iconfont icon-qq other-item" @click="handleTest"></i>-->
+      <!--					<i class="iconfont icon-weibo other-item" @click="handleTest"></i>-->
+      <!--				</view>-->
+      <!--			</view>-->
+    </view>
+  </view>
 </template>
 
 <script>
+import {mapActions} from 'vuex'
+import Storage from '@/utils/storage'
+import * as API_connect from '@/api/connect'
+import store from '@/store'
+
 export default {
-	data() {
-		return {
-			loading: false,
-			isText: false,
-			userInfo: {
-				phone: '100000',
-				password: '123456'
-			}
-		};
-	},
-	methods: {
-		getUserInfo() {
-			this.loading = true;
-			let that = this;
-			//测试账号,可以改成你自己的
-			const userInfo = this.userInfo;
-			that.loading = true;
-			that.$store
-				.dispatch('login', userInfo)
-				.then(res => {
-					that.loading = false;
-					uni.navigateTo({
-						url: '/pages/index/index'
-					});
-				})
-				.catch(err => {
-					that.loading = false;
-					uni.showToast({
-						title: '登录失败',
-						icon: 'none',
-						duration: 3000,
-						complete: function() {
-							setTimeout(function() {
-								uni.hideToast();
-							}, 3000);
-						}
-					});
-				});
-		},
-		handleTest(){
-			 uni.showToast({
-			     title: '功能开发中哦...',
-				 icon :'none',
-			     duration: 2000
-			 });
-		}
-	}
+  data() {
+    return {
+      loading: false,
+      isText: false,
+      userInfo: null,
+      userFrom: {
+        username: '',
+        password: '',
+      },
+      haslogin: {
+        loginType: 'hand_movement'
+      },
+    };
+  },
+  mounted() {
+    if (!this.uuid) {
+      this.uuid = Storage.getItem('uuid');
+    }
+    this.haslogin = Storage.getItem('haslogin')
+    if (!this.haslogin) {
+      uni.reLaunch({
+        url: '/pages/auth/auth'
+      })
+      return
+    }
+    const uInfo = Storage.getItem('myWeixinInfo')
+    if (uInfo) {
+      this.userInfo = uInfo
+    }
+  },
+  methods: {
+    getPhoneNumber(e) {
+      console.log('e======', e)
+      let _this = this
+      let params = {
+        encryptedData: e.detail.encryptedData,
+        iv: e.detail.iv,
+        uuid: _this.uuid,
+        gender: _this.userInfo.gender,
+        nickName: _this.userInfo.nickName
+      };
+      API_connect.accessMobile(params).then(result => {
+        _this.resultEvent(result)
+      }).catch(err => {
+        _this.removeAllItem()
+      });
+    },
+    removeAllItem() {
+      Storage.removeItem('login_result')
+      Storage.removeItem('refresh_token')
+      Storage.removeItem('access_token_timeout')
+      Storage.removeItem('refresh_token_timeout')
+      uni.showToast({
+        title: '授权已失效,请重新授权!',
+        icon: "none"
+      })
+      uni.reLaunch({
+        url: '/pages/auth/auth'
+      })
+    },
+    loginByHasLogin() {
+      let _this = this
+      if (this.haslogin && this.haslogin.uid) {
+        API_connect.loginById({memberId: this.haslogin.uid, uuid: this.uuid}).then(result => {
+          _this.resultEvent(result)
+        }).catch(err => {
+          _this.removeAllItem()
+        });
+      } else {
+        _this.removeAllItem()
+      }
+    },
+    resultEvent(result) {
+      const {access_token, access_token_timeout, refresh_token, refresh_token_timeout, uid} = result
+      // 如果登录成功 存储token(access_token refresh_token) uid 获取用户数据
+      if (access_token && refresh_token && uid) {
+        Storage.setItem('haslogin', {uid: uid, loginType: 'auto'})
+        store.dispatch('setAccessTokenAction', access_token)
+        store.dispatch('setRefreshTokenAction', refresh_token)
+        Storage.setItem('uid', uid);
+        Storage.setItem('access_token_timeout', Math.round(new Date() / 1000) + access_token_timeout - 60)
+        Storage.setItem('refresh_token_timeout', Math.round(new Date() / 1000) + refresh_token_timeout - 60)
+        Storage.removeItem('myWeixinInfo')
+        store.dispatch('getUserDataAction').then(() => {
+          uni.switchTab({
+            url: '/pages/home/index'
+          })
+        })
+      } else {
+        this.removeAllItem()
+      }
+    },
+    getUserInfo() {
+      let that = this
+      //测试账号,可以改成你自己的
+      const form = this.userFrom
+      if (!form.username || !form.password) {
+        uni.showToast({
+          title: '表单填写有误,请检查!',
+          icon: 'none'
+        })
+        return false;
+      }
+      this.loading = true
+      const login_type = 'account'
+      this.login({login_type, form})
+          .then(() => {
+            that.loading = false
+            uni.switchTab({
+              url: '/pages/home/index'
+            })
+          }).catch(err => {
+        that.loading = false
+        uni.showToast({
+          title: err.data.message,
+          icon: 'none'
+        })
+      })
+    },
+    ...mapActions({
+      login: 'loginAction'
+    })
+  }
 };
 </script>
 
 <style lang="scss" scoped>
 .login-container {
-	width: 100%;
-	height: 100vh;
-	position: relative;
-	overflow: hidden;
-	// background: url(../../static/loginBg.png) no-repeat;
-	// background-size: contain;
-	.absolve-wrapper{
-		position: absolute;
-		top: 0;
-		bottom: 0;
-		right: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
-	}
-	.background-img{
+  width: 100%;
+  height: 100vh;
+  position: relative;
+  overflow: hidden;
+  // background: url(../../static/loginBg.png) no-repeat;
+  // background-size: contain;
+  .absolve-wrapper {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+  }
+
+  .background-img {
+    width: 100%;
+    height: 100%;
+  }
+
+  .logo {
+    text-align: center;
+    margin-top: 12%;
+
+    .logo-img {
+      width: 100px;
+      height: 100px;
+    }
+  }
+
+  .text-title {
+    font-size: 48 rpx;
+    margin-top: 5%;
+    text-align: center;
+    color: #fffefe;
+  }
+
+  .login-main {
+    box-sizing: border-box;
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    left: 10%;
+    right: 10%;
+
+    .input-wrapper {
+      display: flex;
+      align-items: center;
+      margin-bottom: 30 rpx;
+      padding-bottom: 30 rpx;
+      border-bottom: 1px solid rgb(242, 242, 242);
+      position: relative;
+
+      .icon {
+        margin-right: 10px;
+        font-size: 16px;
+      }
+
+      .eye {
+        float: right;
+        position: absolute;
+        right: 5px;
+        font-size: 16px;
+      }
+    }
+
+    .login-button {
       width: 100%;
-	  height: 100%;
-	}
-	.logo {
-		text-align: center;
-		margin-top: 12%;
-		.logo-img {
-			width: 100px;
-			height: 100px;
-		}
-	}
-	.text-title {
-		font-size: 48rpx;
-		margin-top: 5%;
-		text-align: center;
-		color: #fffefe;
-	}
-	.login-main {
-		box-sizing: border-box;
-		position: absolute;
-		top: 50%;
-		transform: translateY(-50%);
-		left: 10%;
-		right: 10%;
-		.input-wrapper {
-			display: flex;
-			align-items: center;
-			margin-bottom: 30rpx;
-			padding-bottom: 30rpx;
-			border-bottom: 1px solid rgb(242, 242, 242);
-			position: relative;
-			.icon {
-				margin-right: 10px;
-				font-size: 16px;
-			}
-			.eye {
-				float: right;
-				position: absolute;
-				right: 5px;
-				font-size: 16px;
-			}
-		}
-		.login-button {
-			width: 100%;
-			color: #fff;
-			background-image: linear-gradient(to right, #ffa6b6, #ff7c93);
-			letter-spacing: 2px;
-			height: 45px;
-			margin-top: 75rpx !important;
-		}
-	}
-	.other-login {
-		position: absolute;
-		bottom: 5%;
-		left: 10%;
-		right: 10%;
-		.other-text {
-			text-align: center;
-			color: rgba(0, 0, 0, 0.4);
-			font-size: 28rpx;
-			position: relative;
-			&::before,
-			&::after {
-				position: absolute;
-				background: rgba(0, 0, 0, 0.15);
-				content: '';
-				height: 1px;
-				top: 50%;
-				width: 100px;
-			}
-			&::before {
-				left: 5px;
-			}
-			&::after {
-				right: 5px;
-			}
-		}
-		.login-list{
-			margin-top: 15px;
-			display: flex;
-			justify-content: center;
-			align-items: center;
-			.other-item{
-				flex: 1;
-				font-size: 40px;
-				text-align: center;
-				&.icon-weixin{
-					color: #17d874;
-				}
-				&.icon-qq{
-					color: #32d6f5;
-				}
-				&.icon-weibo{
-					color: rgb(234,93,92);
-				}
-
-			}
-		}
-	}
-	/deep/ {
-		.input-placeholder {
-			color: rgb(194, 194, 194);
-			font-size: 30rpx;
-		}
-	}
+      color: #fff;
+      background-image: linear-gradient(to right, #ffa6b6, #ff7c93);
+      letter-spacing: 2px;
+      height: 45px;
+      margin-top: 75 rpx !important;
+    }
+  }
+
+  .other-login {
+    position: absolute;
+    bottom: 5%;
+    left: 10%;
+    right: 10%;
+
+    .other-text {
+      text-align: center;
+      color: rgba(0, 0, 0, 0.4);
+      font-size: 28 rpx;
+      position: relative;
+
+      &::before,
+      &::after {
+        position: absolute;
+        background: rgba(0, 0, 0, 0.15);
+        content: '';
+        height: 1px;
+        top: 50%;
+        width: 100px;
+      }
+
+      &::before {
+        left: 5px;
+      }
+
+      &::after {
+        right: 5px;
+      }
+    }
+
+    .login-list {
+      margin-top: 15px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      .other-item {
+        flex: 1;
+        font-size: 40px;
+        text-align: center;
+
+        &.icon-weixin {
+          color: #17d874;
+        }
+
+        &.icon-qq {
+          color: #32d6f5;
+        }
+
+        &.icon-weibo {
+          color: rgb(234, 93, 92);
+        }
+
+      }
+    }
+  }
+
+  /deep/ {
+    .input-placeholder {
+      color: rgb(194, 194, 194);
+      font-size: 30 rpx;
+    }
+  }
 }
 </style>

+ 31 - 24
sleep/pages/my/index.vue

@@ -15,13 +15,11 @@
 
             <view class="flex-col my_device_profile">
               <view @click="handleAuth" class="flex-row space-x-19">
-                <image
-                    class="shrink-0 image_5"
-                    src="../../static/mine/myAvatar.png"
-                />
+                <image v-if="userInfo.face" class="shrink-0 image_5" :src="userInfo.face"/>
+                <image v-else class="shrink-0 image_5" src="../../static/mine/myAvatar.png"/>
                 <view class="flex-col flex-auto self-center space-y-14">
                   <view class="flex-row space-x-8">
-                    <text class="text_2">宛黛璐</text>
+                    <text class="text_2">{{ userInfo.nickname }}</text>
                     <view class="flex-row shrink-0 section_3 space-x-4">
                       <image
                           class="shrink-0 image_7"
@@ -35,7 +33,7 @@
                         class="image_8"
                         src="../../static/mine/myIconPhone.png"
                     />
-                    <text class="font_3 text_3">13926465151</text>
+                    <text class="font_3 text_3">{{ userInfo.mobile }}</text>
                   </view>
                 </view>
                 <image
@@ -267,28 +265,30 @@ import Storage from '@/utils/storage'
 import * as Foundation from '@/ui-utils/Foundation'
 import * as API_deviceMemberBind from '@/api/device_member_bind'
 import * as API_SLEEP_REPORT from '@/api/sleep_report'
-import {bindDeviceByDtype} from "../../api/device_member_bind";
 
 export default {
   data() {
     return {
       deviceList: [],
-      unionId: '6291d3bde21b840008ae7842', // 先写死
-      memberId: '50289', // 先写死
+      unionId: '',
+      memberId: '',
       bindSn: '',
-      bindShow: false
+      bindShow: false,
+      userInfo: null
     };
   },
   mounted() {
-    this.deviceList = Storage.getItem('myBindDevices')
-    if (this.deviceList) {
-      console.log('this.deviceList===', this.deviceList)
-      this.API_getNewReport()
+    this.memberId = Storage.getItem('uid')
+    if (this.memberId) {
+      this.userInfo = Storage.getItem('userInfo')
+      this.deviceList = Storage.getItem('myBindDevices')
+      this.unionId = this.userInfo.union_id
+      if (this.deviceList) {
+        console.log('this.deviceList===', this.deviceList)
+        this.API_getNewReport()
+      }
     }
   },
-  onShow() {
-
-  },
   methods: {
     API_getNewReport() {
       const _this = this
@@ -322,9 +322,13 @@ export default {
       })
     },
     handleAuth() {
-      uni.navigateTo({
-        url: '/pages/auth/auth'
-      });
+      if (!this.memberId) {
+        uni.navigateTo({
+          url: '/pages/auth/auth'
+        })
+      } else {
+        console.log('修改...')
+      }
     },
     handleShareData() {
       uni.navigateTo({
@@ -398,8 +402,6 @@ export default {
           uni.showToast({
             title: '绑定成功',
           })
-          Storage.setItem("nowChangeDevice", _this.deviceList[0].imei)
-          Storage.setItem("myBindDevices", _this.deviceList)
           _this.API_getDeviceList()
         } else {
           uni.showToast({
@@ -423,8 +425,13 @@ export default {
                 icon: 'none'
               })
               _this.deviceList.splice(index, 1)
-              Storage.setItem("nowChangeDevice", _this.deviceList[0].imei)
-              Storage.setItem("myBindDevices", _this.deviceList)
+              if (_this.deviceList.length > 0) {
+                Storage.setItem("nowChangeDevice", _this.deviceList[0].imei)
+                Storage.setItem("myBindDevices", _this.deviceList)
+              } else {
+                Storage.removeItem('nowChangeDevice')
+                Storage.removeItem('myBindDevices')
+              }
             })
           }
         }

+ 158 - 10
sleep/pages/report/report.vue

@@ -11,8 +11,8 @@
 		    />
 
 			<view class="flex-row items-center self-center bottom-btns">
-				<view class="btn_1 bottom-btn">立即体验</view>
-				<view class="btn_2 bottom-btn">添加设备</view>
+				<view class="btn_1 bottom-btn" @click="addBindDevice">立即体验</view>
+				<view class="btn_2 bottom-btn" @click="addBindDevice">添加设备</view>
 			</view>
 
 		  </view>
@@ -126,7 +126,7 @@
 									<view class="flex-row items-center space-x-12">
 										<image
 											class="image_7"
-											src="../../static/report/reportIconYellow.png"
+											src="../../static/report/reportIconGreen.png"
 										/>
 										<text class="font_3">深睡时长</text>
 									</view>
@@ -138,7 +138,7 @@
                   <view class="flex-row items-center space-x-12">
                     <image
                         class="image_7"
-                        src="../../static/report/reportIconYellow.png"
+                        src="../../static/report/reportIconGreen.png"
                     />
                     <text class="font_3">平均呼吸率</text>
                   </view>
@@ -151,7 +151,7 @@
 									<view>
 										<image
 											class="image_7"
-											src="../../static/report/reportIconRed.png"
+											src="../../static/report/reportIconGreen.png"
 										/>
 										<text class="font_3">温馨提示</text>
 									</view>
@@ -445,6 +445,37 @@
         ></uni-calendar>
       </view>
     </view>
+
+    <view class="cu-modal" :class="bindShow?'show':''">
+      <view class="cu-dialog">
+        <view class="cu-bar bg-white justify-end">
+          <view style="color: black" class="content">设备绑定</view>
+          <view class="action" @tap="bindShow=false">
+            <text class="cuIcon-close text-red"></text>
+          </view>
+        </view>
+        <view class="">
+          <form>
+            <view class="cu-form-group solid-bottom">
+              <view class="title">扫描二维码</view>
+              <input type="text" v-model="bindSn" class="text-left"/>
+              <text class="cuIcon-scan text-xxl text-green" style="font-size: 50rpx;"
+                    @click="scan"></text>
+            </view>
+          </form>
+        </view>
+        <view class="cu-bar bg-white justify-end">
+          <view class="action">
+            <button class="cu-btn bg-green margin-left" @tap="bindDevice">确定</button>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="cu-load load-modal" v-if="disabled">
+      <view class="cuIcon-emojifill text-orange"></view>
+      <view class="gray-text">正在获取</view>
+    </view>
 	</view>
 </template>
 
@@ -453,8 +484,10 @@
   import echarts from '@/static/echarts.min.js'
   import mpvueEcharts from '@/components/mpvue-echarts/src/echarts.vue'
   import uniCalendar from '@/components/uni-calendar/uni-calendar.vue'
+  import Storage from '@/utils/storage'
   import * as Foundation from '@/ui-utils/Foundation'
   import * as API_SLEEP_REPORT from '@/api/sleep_report'
+  import * as API_deviceMemberBind from '@/api/device_member_bind'
 
 	export default {
     components: {
@@ -462,7 +495,7 @@
     },
 		data() {
 			return {
-				isDeviceAdded: true,
+				isDeviceAdded: false,
         todayStr: Foundation.unixToDate(parseInt(new Date() / 1000), "yyyy-MM-dd"),
         selectDay: '',
         selectYearMonth: {
@@ -493,12 +526,25 @@
           unionId: '',
           reportTime: -1
         },
-        disabled: false
+        disabled: true,
+        bindShow: false,
+        bindSn: '',
+        memberId: '',
+        userInfo: null
 			}
 		},
-    mounted() {
-      this.params.unionId = '6291d3bde21b840008ae7842' // 先写死
-      this.getNewSleepReport()
+    onShow() {
+      this.memberId = Storage.getItem('uid')
+      if (this.memberId) {
+        if (!this.isDeviceAdded) {
+          this.user = Storage.getItem('userInfo')
+          this.params.unionId = this.user.union_id
+          console.log('user===', this.user, this.user.union_id)
+          this.getNewSleepReport()
+        }
+      } else {
+        this.isDeviceAdded = false
+      }
     },
     methods: {
       // 获取睡眠报告
@@ -509,6 +555,7 @@
           _this.disabled = false
           console.log(res)
           if (res.length > 0) {
+            this.isDeviceAdded = true
             _this.sleepReportFrom = Object.assign({}, res[0])
             _this.sleepReportFrom.report_content = JSON.parse(_this.sleepReportFrom.report_content)
             console.log(_this.sleepReportFrom)
@@ -526,6 +573,8 @@
               _this.getDaysTag()
             }
             _this.initAllChar(_this.sleepReportFrom)
+          } else {
+            this.isDeviceAdded = false
           }
         })
       },
@@ -733,6 +782,105 @@
             }
           }
         }
+      },
+      // 扫描条码
+      scan() {
+        let _this = this
+        uni.scanCode({
+          success: function (res) {
+            console.log(res, res.result.length)
+            if (res.result.length > 20) {
+              if (res.scanType === 'QR_CODE') {
+                const urlStr = res.result
+                const qs = Foundation.getQueryObject(urlStr)
+                if (qs.mac) {
+                  _this.bindSn = qs.mac
+                } else {
+                  uni.showToast({
+                    title: '请扫码正确的睡眠床垫二维码',
+                    icon: 'none'
+                  });
+                }
+              } else {
+                _this.bindSn = res.result
+              }
+            } else {
+              _this.bindSn = res.result
+            }
+          },
+          fail: (err) => {
+            console.log(err)
+            // #ifdef MP
+            uni.getSetting({
+              success: (res) => {
+                let authStatus = res.authSetting['scope.camera'];
+                if (!authStatus) {
+                  uni.showModal({
+                    title: '授权失败',
+                    content: 'app需要使用您的相机,请在设置界面打开相关权限',
+                    success: (res) => {
+                      if (res.confirm) {
+                        uni.openSetting()
+                      }
+                    }
+                  })
+                }
+              }
+            })
+            // #endif
+          }
+        })
+      },
+      bindDevice() {
+        const params = {
+          imei: this.bindSn,
+          memberId: this.memberId,
+          dtype: '睡眠床垫'
+        }
+        this.bindShow = false
+        let _this = this
+        API_deviceMemberBind.bindDeviceByDtype(params).then(res => {
+          if (res.success) {
+            _this.bindSn = ''
+            uni.showToast({
+              title: '绑定成功',
+            })
+            _this.API_getDeviceList()
+          } else {
+            uni.showToast({
+              title: res.message,
+              icon: 'none'
+            })
+          }
+        })
+      },
+      API_getDeviceList() {
+        const _this = this
+        API_deviceMemberBind.getDeviceByDtypeAndMemberId({dtype: '睡眠床垫', memberId: this.memberId}).then(res => {
+          if (res.length > 0) {
+            Storage.setItem("nowChangeDevice", res[0].imei)
+            Storage.setItem("myBindDevices", res)
+          }
+        })
+      },
+      addBindDevice() {
+        if (this.memberId) {
+          this.bindShow = 'show'
+        } else {
+          uni.showModal({
+            title: '提示',
+            content: "您还没有登录,是否前往登录?",
+            confirmText: "确定",
+            showCancel: true,
+            success: function(res) {
+              if (res.confirm) {
+                uni.reLaunch({
+                  url:'/pages/auth/auth'
+                })
+              }
+            }
+          })
+        }
       }
     }
 	}

+ 4 - 4
sleep/pages/shareUser/shareUser.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="flex-col justify-start page">
 	  <cu-custom isBack="true" bgColor="#000000"><view slot="content" style="color: #FFFFFF">UU睡眠</view></cu-custom>
-		
+
 	  <view class="flex-col justify-start section">
 	    <view class="flex-col section_2 space-y-421">
 	      <view class="flex-col">
@@ -43,11 +43,11 @@
 	export default {
 		data() {
 			return {
-				
+
 			}
 		},
 		methods: {
-			
+
 		}
 	}
 </script>
@@ -60,7 +60,7 @@
   overflow-x: hidden;
   height: 100%;
   .section {
-    background-image: url('../../static/familyBg.png');
+    background-image: url(../../static/familyBg.png);
     background-size: auto;
     background-repeat: no-repeat;
     .section_2 {

+ 58 - 63
sleep/store/index.js

@@ -1,72 +1,67 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
+import uuidv1 from 'uuid/v1'
+import Storage from '@/utils/storage'
+import * as user from './user'
 
 Vue.use(Vuex)
+if (!Storage.getItem('uuid')) {
+	Storage.setItem('uuid', uuidv1())
+}
+
+/** state */
+const state = () => ({
+	// 导航栏
+	navList: [],
+	// 分类
+	categories: [],
+	// 热搜关键词
+	hotKeywords: [],
+	// 站点信息
+	site: ''
+})
+
+
+
+/** getters */
+const getters = {
+	/**
+	 * 分类列表
+	 * @param state
+	 * @returns {*}
+	 */
+	categories: state => state.categories,
+	/**
+	 * 导航栏
+	 * @param state
+	 * @returns {*}
+	 */
+	navList: state => state.navList,
+	/**
+	 * 热搜关键词
+	 * @param state
+	 * @returns {*}
+	 */
+	hotKeywords: state => state.hotKeywords,
+	/**
+	 * 获取用户信息
+	 * @param state
+	 * @returns {*}
+	 */
+	user: state => state.user.user,
+	/**
+	 * 获取站点信息
+	 * @param state
+	 * @returns {getters.site|(function(*))|string}
+	 */
+	site: state => state.site
+}
 
 const store = new Vuex.Store({
-	state: {
-		hasLogin: false,
-		loginProvider: "",
-		openid: null,
-		testvuex:false,
-		colorIndex: 0,
-		colorList: ['#FF0000','#00FF00','#0000FF']
-	},
-	mutations: {
-		login(state, provider) {
-			state.hasLogin = true;
-			state.loginProvider = provider;
-		},
-		logout(state) {
-			state.hasLogin = false
-			state.openid = null
-		},
-		setOpenid(state, openid) {
-			state.openid = openid
-		},
-		setTestTrue(state){
-			state.testvuex = true
-		},
-		setTestFalse(state){
-			state.testvuex = false
-		},
-		setColorIndex(state,index){
-			state.colorIndex = index
-		}
-	},
-	getters:{
-		currentColor(state){
-			return state.colorList[state.colorIndex]
-		}
-	},
-	actions: {
-		// lazy loading openid
-		getUserOpenId: async function ({
-										   commit,
-										   state
-									   }) {
-			return await new Promise((resolve, reject) => {
-				if (state.openid) {
-					resolve(state.openid)
-				} else {
-					uni.login({
-						success: (data) => {
-							commit('login')
-							setTimeout(function () { //模拟异步请求服务器获取 openid
-								const openid = '123456789'
-								console.log('uni.request mock openid[' + openid + ']');
-								commit('setOpenid', openid)
-								resolve(openid)
-							}, 1000)
-						},
-						fail: (err) => {
-							console.log('uni.login 接口调用失败,将无法正常使用开放接口等服务', err)
-							reject(err)
-						}
-					})
-				}
-			})
-		}
+	state,
+	getters,
+	modules: {
+		user,
 	}
 })
 

+ 21 - 0
sleep/store/mutation-types.js

@@ -0,0 +1,21 @@
+/** UUID相关 */
+// 设置UUID
+export const SET_UUID = 'SET_UUID'
+// 移除UUID
+export const REMOVE_UUID = 'REMOVE_UUID'
+
+/** 用户信息相关 */
+// 保存用户信息
+export const SET_USER_INFO = 'SET_USER_INFO'
+// 移除用户信息
+export const REMOVE_USER_INFO = 'REMOVE_USER_INFO'
+
+/** TOKEN相关 */
+// 保存访问令牌
+export const SET_ACCESS_TOKEN = 'SET_ACCESS_TOKEN'
+// 移除访问令牌
+export const REMOVE_ACCESS_TOKEN = 'REMOVE_ACCESS_TOKEN'
+// 保存刷新令牌
+export const SET_REFRESH_TOKEN = 'SET_REFRESH_TOKEN'
+// 移除刷新令牌
+export const REMOVE_REFRESH_TOKEN = 'REMOVE_REFRESH_TOKEN'

+ 241 - 0
sleep/store/user.js

@@ -0,0 +1,241 @@
+import * as API_Members from '@/api/members'
+import * as API_Connect from '@/api/connect'
+import * as types from './mutation-types'
+import Storage from '@/utils/storage'
+import md5 from 'js-md5'
+
+export const state = () => {
+  const user = Storage.getItem('user')
+  return {
+    user: user ? JSON.parse(user) : ''
+  }
+}
+
+/** mutations */
+export const mutations = {
+  /**
+   * 保存用户信息
+   * @param state
+   * @param data
+   */
+  [types.SET_USER_INFO](state, data) {
+    state.user = data
+    Storage.setItem('user', JSON.stringify(data))
+	Storage.setItem('userInfo', data)
+  },
+  /**
+   * 移除用户信息
+   * @param state
+   * @param data
+   */
+  [types.REMOVE_USER_INFO](state, data) {
+    state.user = ''
+    Storage.removeItem('user')
+	Storage.removeItem('userInfo')
+    Storage.removeItem('uid')
+    // 主要针对第三方登录留下的数据
+    Storage.removeItem('uuid_connect')
+  },
+  /**
+   * 设置访问令牌
+   * @param state
+   * @param token
+   */
+  [types.SET_ACCESS_TOKEN](state, token) {
+    Storage.setItem('access_token', token)
+  },
+  /**
+   * 移除访问令牌
+   * @param state
+   */
+  [types.REMOVE_ACCESS_TOKEN](state) {
+    Storage.removeItem('access_token')
+  },
+
+  /**
+   * 设置访问令牌过期时间
+   * @param state
+   * @param token
+   */
+  [types.SET_ACCESS_TOKEN_TIMEOUT](state, token_out) {
+    Storage.setItem('access_token_timeout', token_out)
+  },
+
+  /**
+   * 删除访问令牌过期时间
+   * @param state
+   * @param token
+   */
+  [types.REMOVE_ACCESS_TOKEN_TIMEOUT](state) {
+    Storage.removeItem('access_token_timeout')
+  },
+
+  /**
+   * 设置刷新令牌
+   * @param state
+   * @param token
+   */
+  [types.SET_REFRESH_TOKEN](state, token) {
+    Storage.setItem('refresh_token', token)
+  },
+  /**
+   * 移除刷新令牌
+   * @param state
+   */
+  [types.REMOVE_REFRESH_TOKEN](state) {
+    Storage.removeItem('refresh_token')
+  },
+
+  /**
+   * 设置刷新令牌过期时间
+   * @param state
+   * @param token
+   */
+  [types.SET_REFRESH_TOKEN_TIMEOUT](state, token_out) {
+    Storage.setItem('refresh_token_timeout', token_out)
+  },
+  /**
+   * 移除刷新令牌过期时间
+   * @param state
+   */
+  [types.REMOVE_REFRESH_TOKEN_TIMEOUT](state) {
+    Storage.removeItem('refresh_token_timeout')
+  }
+
+}
+
+/** actions */
+export const actions = {
+  /**
+   * 获取用户数据
+   * @param commit
+   * @param params
+   */
+  getUserDataAction: ({ commit }) => {
+    return new Promise((resolve, reject) => {
+      API_Members.getUserInfo().then(response => {
+        commit(types.SET_USER_INFO, response)
+        resolve(response)
+      }).catch(error => reject(error))
+    })
+  },
+  /**
+   * 登录
+   * @param commit
+   * @param params
+   * @returns {Promise<any>}
+   */
+  loginAction: ({ commit, dispatch }, params) => {
+    return new Promise((resolve, reject) => {
+      const uuid = Storage.getItem('uuid')
+	  // console.log('uuid',uuid)
+      API_Connect.loginBindConnectByNoAccount(uuid, params.form).then(loginSccess).catch(reject)
+      function loginSccess(res) {
+        const { access_token, refresh_token,access_token_timeout,refresh_token_timeout, uid } = res
+		console.log('login',access_token_timeout)
+        commit(types.SET_ACCESS_TOKEN, access_token)
+        commit(types.SET_REFRESH_TOKEN, refresh_token)
+		// commit(types.SET_ACCESS_TOKEN_TIMEOUT,Math.round(new Date()/1000)+access_token_timeout-60)
+		// commit(types.SET_REFRESH_TOKEN_TIMEOUT,Math.round(new Date()/1000)+refresh_token_timeout-60)
+		Storage.setItem('refresh_token_timeout', Math.round(new Date()/1000)+refresh_token_timeout-60)
+		Storage.setItem('access_token_timeout', Math.round(new Date()/1000)+access_token_timeout-60)
+        Storage.setItem('uid', uid)
+        API_Members.getUserInfo().then(response => {
+          commit(types.SET_USER_INFO, response)
+          resolve(response)
+        }).catch(reject)
+      }
+    })
+  },
+  /**
+   * 登出
+   * @param commit
+   * @param dispatch
+   * @param type
+   * @returns {Promise<any>}
+   */
+  logoutAction: ({ commit, dispatch }) => {
+    return new Promise((resolve, reject) => {
+      API_Members.logout().then(() => {
+        commit(types.REMOVE_USER_INFO)
+        commit(types.REMOVE_ACCESS_TOKEN)
+        commit(types.REMOVE_REFRESH_TOKEN)
+        Storage.removeItem('login_result')
+		Storage.removeItem('access_token_timeout')
+		Storage.removeItem('refresh_token_timeout')
+        resolve()
+      }).catch(error => reject(error))
+    })
+  },
+  /**
+   * 保存用户信息【修改资料】
+   * @param commit
+   * @param params
+   * @returns {Promise<any>}
+   */
+  saveUserInfoAction: ({ commit }, params) => {
+    return new Promise((resolve, reject) => {
+      API_Members.saveUserInfo(params).then(response => {
+        commit(types.SET_USER_INFO, response)
+        resolve(response)
+      }).catch(error => reject(error))
+    })
+  },
+  /**
+   * 移除用户信息
+   * @param commit
+   */
+  removeUserAction: ({ commit }) => {
+    commit(types.REMOVE_USER_INFO)
+  },
+  /**
+   * 注册绑定【通过手机号】
+   * @param commit
+   * @param params
+   * @returns {Promise<any>}
+   */
+  registerBindConnect: ({ commit, dispatch }, params) => {
+    return new Promise((resolve, reject) => {
+      const uuid = Storage.getItem('uuid')
+      const _params = { ...params }
+      _params.password = md5(_params.password)
+      API_Connect.registerBindConnect(uuid, _params).then(res => {
+        const { access_token, refresh_token, uid } = res
+        commit(types.SET_ACCESS_TOKEN, access_token)
+        commit(types.SET_REFRESH_TOKEN, refresh_token)
+        Storage.setItem('uid', uid)
+        resolve(res)
+      })
+    })
+  },
+  /**
+   * 设置accessToken
+   * @param commit
+   * @param accessToken
+   */
+  setAccessTokenAction: ({ commit }, accessToken) => {
+    commit(types.SET_ACCESS_TOKEN, accessToken)
+  },
+  /**
+   * 移除accessToken
+   * @param commit
+   */
+  removeAccessTokenAction: ({ commit }) => {
+    commit(types.REMOVE_ACCESS_TOKEN)
+  },
+  /**
+   * 设置refreshToken
+   * @param commit
+   * @param refreshToken
+   */
+  setRefreshTokenAction: ({ commit }, refreshToken) => {
+    commit(types.SET_REFRESH_TOKEN, refreshToken)
+  },
+  /**
+   * 移除refreshToken
+   * @param commit
+   */
+  removeRefreshTokenAction: ({ commit }) => {
+    commit(types.REMOVE_REFRESH_TOKEN)
+  }
+}

+ 15 - 0
sleep/ui-utils/Foundation.js

@@ -71,6 +71,21 @@ export function countTimeDown(seconds) {
 }
 
 /**
+ * 随机生成指定长度的字符串
+ * @param length
+ * @returns {string}
+ */
+export function randomString(length = 32) {
+  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+  const maxPos = chars.length
+  let _string = ''
+  for (let i = 0; i < length; i++) {
+    _string += chars.charAt(Math.floor(Math.random() * maxPos))
+  }
+  return _string
+}
+
+/**
  * 根据url获取参数
  * @param url 链接地址
  * @returns {{obj}}

+ 120 - 0
sleep/utils/checkToken.js

@@ -0,0 +1,120 @@
+/**
+ * Created by Andste on 2018/5/7.
+ */
+
+import Vue from 'vue'
+import Storage from './storage'
+import request, { API } from '@/utils/request'
+import store from '@/store'
+/**
+ * 检查token:
+ * 1. user/accessToken/refreshToken都不存在。
+ *    表示用户没有登录,不能放行需要登录的API
+ * 2. 不存在accessToken,但是user/refreshToken存在。
+ *    表示accessToken过期,需要重新获取accessToken。
+ *    如果重新获取accessToken返回token失效错误,说明已被登出。
+ * @param options
+ * @returns {Promise<any>}
+ */
+export default function checkToken(options) {
+	// uid
+	const uid = Storage.getItem('uid')
+	// 访问Token
+	const accessToken = Storage.getItem('access_token')
+	// 刷新Token
+	const refreshToken = Storage.getItem('refresh_token')
+	//刷新token过期时间
+	const refreshTokenTimeout = Storage.getItem('refresh_token_timeout')
+	//access_token过期时间
+	const accessTokenTimeout = Storage.getItem('access_token_timeout')
+	const uuid = Storage.getItem('uuid')
+	//当前时间戳
+	const now = Math.round(new Date() / 1000)
+	// 返回异步方法
+	return new Promise((resolve, reject) => {
+		/**
+		 * 如果accessToken、uid、refreshToken都存在。
+		 * 说明必要条件都存在,可以直接通过,并且不需要后续操作。
+		 */
+		var checkLock
+
+		// console.log('accessTokenout', !accessTokenTimeout || accessTokenTimeout <= now)
+		if (accessToken && uid && refreshToken && accessTokenTimeout && now < accessTokenTimeout) {
+			resolve()
+			return
+		}
+		/**
+		 * 如果需要Token,但是refreshToken或者uid没有。
+		 * 说明登录已失效、或者cookie有问题,需要重新登录。
+		 */
+		//刷新token失效了,需要重新登陆获取
+		if (!refreshTokenTimeout || refreshTokenTimeout < now) { //刷新token失效,直接退出
+			// uni.redirectTo({
+			// 	url: '/pages/login/index'
+			// })
+			resolve()
+			return
+		}
+
+		if (!accessTokenTimeout || accessTokenTimeout <= now) //access_token 失效 刷新token
+		{
+			console.log('refreshtoken')
+			const param = {
+				'refresh_token': refreshToken
+			}
+			uni.request({
+				url: `${API.buyer}/passport/token`,
+				method: 'POST',
+				header: {
+					'Content-Type': 'application/x-www-form-urlencoded',
+					'uuid': uuid
+				},
+				data: param,
+				success(res) {
+					console.log(res)
+					const final = {}
+					const {
+						accessToken,
+						refreshToken,
+						accessTokenTimeout,
+						refreshTokenTimeout
+					} = res.data.accessToken ? res.data : final
+					// 刷新成功 更新凭证数据
+					console.log('refreshToken', accessToken)
+					if (accessToken && refreshToken && accessTokenTimeout) {
+						const now = Math.round(new Date() / 1000)
+						store.dispatch('setAccessTokenAction', accessToken)
+						store.dispatch('setRefreshTokenAction', refreshToken)
+						Storage.setItem('access_token_timeout', now + accessTokenTimeout - 60)
+						Storage.setItem('refresh_token_timeout', now + refreshTokenTimeout - 60)
+						resolve()
+					} else { //刷新返回数据错误
+						Storage.removeItem('access_token_timeout')
+						Storage.removeItem('refresh_token_timeout')
+						Storage.removeItem('access_token')
+						Storage.removeItem('refresh_token')
+						uni.redirectTo({
+							url: '/pages/login/index'
+						})
+						return
+
+					}
+				},
+				fail(err) { //刷新token失败
+					Storage.removeItem('access_token_timeout')
+					Storage.removeItem('refresh_token_timeout')
+					Storage.removeItem('access_token')
+					Storage.removeItem('refresh_token')
+					uni.redirectTo({
+						url: '/pages/login/index'
+					})
+					return
+				}
+			})
+
+		} else { //accessToken 有效
+			resolve()
+		}
+
+	})
+}

+ 77 - 5
sleep/utils/request.js

@@ -1,8 +1,23 @@
 import Vue from 'vue'
+import md5 from 'js-md5'
+import Storage from '@/utils/storage'
+import * as Foundation from '@/ui-utils/Foundation'
+import store from '@/store'
+import checkToken from '@/utils/checkToken'
+
+/** api 模式 */
+const api_mode = 'prod'
+
+export const API = {
+	// base: 'https://api.base.wdklian.com',
+	// buyer: 'https://api.buyer.wdklian.com',
+	base: 'http://wdkl.natapp1.cc',
+	buyer: 'http://192.168.1.199:7002'
+}
 
 const config = {
 	timeout: 15000,
-	baseURL: 'http://wdkl.natapp1.cc',
+	baseURL: api_mode === 'prod' ? API.buyer : API.base,
 	headers: {
 		'Content-Type':'application/x-www-form-urlencoded'
 	}
@@ -21,6 +36,8 @@ function service(options) {
 		url = options.url
 	}
 	let headers = config.headers
+	const uuid = Storage.getItem('uuid')
+	headers['uuid'] = uuid
 	if (options.config.isJson){
 		headers['Content-Type']='application/json'
 	} else {
@@ -29,6 +46,26 @@ function service(options) {
 	if (options.config.loading){
 		uni.showLoading()
 	}
+	console.log('this.api_mode===', api_mode)
+	if (options.config.needToken && api_mode === 'prod'){
+		let accessToken = Storage.getItem('access_token')
+		if (!accessToken) {
+			uni.reLaunch({
+				url: '/pages/login/index'
+			})
+			return
+		}
+		let uid = Storage.getItem('uid')
+		let nonce = Foundation.randomString(6)
+		let timestamp = parseInt(new Date().getTime() / 1000)
+		let sign = md5(uid + nonce + timestamp + accessToken)
+		if (url.includes('?')) {
+			url = `${url}&uid=${uid}&nonce=${nonce}&timestamp=${timestamp}&sign=${sign}`
+		} else {
+			url = `${url}?uid=${uid}&nonce=${nonce}&timestamp=${timestamp}&sign=${sign}`
+		}
+	}
+
 	return new Promise((resolve,reject)=>{
 		// console.log(options, config);
 		uni.request({
@@ -41,17 +78,41 @@ function service(options) {
 				uni.hideLoading()
 				if (res.statusCode == 200){
 					resolve(res.data)
+				} else if (res.statusCode == 403){
+					if (!Storage.getItem('refresh_token')) {
+						console.log('444444444444444')
+						uni.reLaunch({
+							url: '/pages/login/index'
+						})
+						resolve()
+						return
+					}
+					store.dispatch('cleanCartStoreAction')
+					store.dispatch('removeUserAction')
+					store.dispatch('removeAccessTokenAction')
+					store.dispatch('removeRefreshTokenAction')
+					Storage.removeItem('login_result')
+					Storage.removeItem('refresh_token')
+					Storage.removeItem('access_token_timeout')
+					Storage.removeItem('refresh_token_timeout')
+					Vue.prototype.$checkHasLogin()
 				} else{
 					if (url.indexOf("/wx_mobile") === -1) {
-						Vue.prototype.$message.error(res.data.message)
+						uni.showToast({
+							title: res.data.message,
+							icon: 'none'
+						})
 					}
 					reject(res)
 				}
 			},
 			fail(err){
 				uni.hideLoading()
-				console.log(err);
-				Vue.prototype.$message.error(err.data.message)
+				console.log('错误消息====', err)
+				uni.showToast({
+					title: res.data.message,
+					icon: 'none'
+				})
 				reject(err)
 			}
 		});
@@ -59,5 +120,16 @@ function service(options) {
 }
 
 export default function request(options){
-	return service(options)
+	if (api_mode === 'prod') {
+		if(/passport\/mini-program\/*|passport\/login-binder\/wap\/*|passport\/login\/*/.test(options.url)){
+			return service(options)
+		}else{
+			return checkToken(options).then(()=>{
+				return service(options)
+			})
+		}
+	} else {
+		return service(options)
+	}
+
 }

+ 150 - 0
sleep/utils/vue-mixin.js

@@ -0,0 +1,150 @@
+import Vue from 'vue'
+import Storage from '@/utils/storage'
+import * as API_Connect from '@/api/connect'
+import 'wx-promise-pro'
+import store from '@/store'
+
+const mp_type = 'miniprogram_uu_sleep'
+const mixin = {
+	data() {
+		return {
+			// 项目名称
+			projectName: 'UU睡眠',
+			// 是否已经授权 默认false
+			hasAuth: false,
+
+		}
+	},
+	onLoad(query) {
+
+	},
+	/** 解决在真机上下拉不回弹 */
+	onPullDownRefresh() {
+		wx.stopPullDownRefresh()
+	},
+	onShareAppMessage(){
+
+	},
+
+	methods: {
+
+		async checkHasLogin() {
+			const {
+				code
+			} = await wx.pro.login({
+				timeout: 8000
+			})
+			const uuid = Storage.getItem('uuid')
+			let result = await API_Connect.checkHasLogin({
+				code,
+				uuid,
+				mp_type
+			})
+			// console.log('是否登录返回=', result)
+			let _this = this
+			if (result.autologin === 'yes') {
+				_this.hasAuth = true
+				Storage.setItem('haslogin', { uid: result.uid, loginType: 'auto' })
+				_this.disposeResult(result)
+			} else {
+				uni.reLaunch({
+					url:'/pages/auth/auth'
+				})
+			}
+		},
+		// 处理result
+		disposeResult(result) {
+			const {
+				access_token,
+				refresh_token,
+				access_token_timeout,
+				refresh_token_timeout,
+				uid,
+				no_union_id
+			} = result
+			// 如果登录成功 存储token(access_token refresh_token) uid 获取用户数据
+			if (access_token && refresh_token && uid) {
+				const now=Math.round(new Date()/1000)
+				store.dispatch('setAccessTokenAction', access_token)
+				store.dispatch('setRefreshTokenAction', refresh_token)
+				Storage.setItem('uid', uid)
+				Storage.setItem('access_token_timeout',now+access_token_timeout-60)
+				Storage.setItem('refresh_token_timeout',now+refresh_token_timeout-60)
+				Storage.setItem('haslogin', { uid: result.uid, loginType: 'auto' })
+				// this.autoBindPart()
+				store.dispatch('getUserDataAction').then(()=>{
+					let page = Storage.getItem('goPage')
+					console.log('mixin-goPage===', page);
+					uni.switchTab({
+						url: '/pages/home/index'
+					})
+				})
+			} else if (no_union_id) {
+				Storage.setItem('haslogin', { loginType: 'hand_movement' })
+				uni.reLaunch({
+					url:'/pages/auth/auth'
+				})
+			} else {
+				uni.reLaunch({
+					url:'/pages/login/index'
+				})
+			}
+		},
+		async toAutoLogin() {
+			this.hasAuth = true
+			// 检测是否登录 如果已经登录 或者登录结果为账户未发现 则不再进行自动登录
+			// if (Storage.getItem('refresh_token') || Storage.getItem('login_result') === 'account_not_found') return
+			let final = {}
+			const {
+				code
+			} = await wx.pro.login({
+				timeout: 8000
+			})
+			const uuid = Storage.getItem('uuid')
+			try {
+				let result = await API_Connect.loginByAuto({
+					code,
+					uuid,
+					mp_type
+				})
+				if (result.reson === 'unionid_not_found') { // 如果已经进行过用户授权但是没有获取到unionID
+					let {
+						encryptedData,
+						iv
+					} = await wx.pro.getUserInfo({
+						withCredentials: true,
+						lang: 'zh_CN'
+					})
+					final = await API_Connect.accessUnionID({
+						code,
+						uuid,
+						encrypted_data: encryptedData,
+						iv
+					})
+				}
+				if(result.reson === 'account_not_found' || final.reson === 'account_not_found'){ //没有绑定账号 跳到登陆页面
+					uni.reLaunch({
+						url:'/pages/login/index'
+					})
+				}
+				Storage.setItem('login_result', result.reson)
+				Storage.setItem('haslogin', { loginType: 'hand_movement' })
+				this.disposeResult(result)
+			} catch (e) {
+				Storage.setItem('login_result', false)
+				if (e.response.data.code === 'E133') {
+					uni.redirectTo({
+						url: '/pages/home/index'
+					})
+					this.hasAuth = false
+					Storage.removeItem('uuid')
+				}
+			}
+		},
+	}
+}
+
+Vue.prototype.$toAutoLogin = mixin.methods.toAutoLogin
+Vue.prototype.$checkHasLogin = mixin.methods.checkHasLogin
+
+export default mixin