Browse Source

新项目

chenrui 4 years ago
commit
15cc5e124f
60 changed files with 6751 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 52 0
      App.vue
  3. 37 0
      api/coupon.js
  4. 8 0
      api/data.js
  5. 27 0
      api/goods.js
  6. 43 0
      api/login.js
  7. 61 0
      api/order.js
  8. 35 0
      api/receiveAddress.js
  9. 36 0
      api/shoppingCart.js
  10. 42 0
      api/store.js
  11. 133 0
      api/user.js
  12. 645 0
      components/jyf-parser/jyf-parser.vue
  13. 100 0
      components/jyf-parser/libs/CssHandler.js
  14. 580 0
      components/jyf-parser/libs/MpHtmlParser.js
  15. 80 0
      components/jyf-parser/libs/config.js
  16. 22 0
      components/jyf-parser/libs/handler.wxs
  17. 505 0
      components/jyf-parser/libs/trees.vue
  18. 124 0
      components/m-input.vue
  19. 40 0
      components/uni-cart-fix/uni-cart-fix.vue
  20. 121 0
      components/uni-coods/uni-coods.vue
  21. 96 0
      components/uni-icons/icons.js
  22. 10 0
      components/uni-icons/uni-icons.vue
  23. 127 0
      components/uni-popup/image-txt-popup.vue
  24. 22 0
      components/uni-popup/message.js
  25. 96 0
      components/uni-popup/popup-con.vue
  26. 25 0
      components/uni-popup/popup.js
  27. 243 0
      components/uni-popup/uni-popup-dialog.vue
  28. 116 0
      components/uni-popup/uni-popup-message.vue
  29. 165 0
      components/uni-popup/uni-popup-share.vue
  30. 294 0
      components/uni-popup/uni-popup.vue
  31. 279 0
      components/uni-transition/uni-transition.vue
  32. 44 0
      js_sdk/dc-market/market.js
  33. 104 0
      libs/axios.js
  34. 15 0
      libs/tools.js
  35. 5 0
      libs/validate.js
  36. 45 0
      main.js
  37. 85 0
      manifest.json
  38. 13 0
      package-lock.json
  39. 19 0
      package.json
  40. 76 0
      pages.json
  41. 430 0
      pages/agreement/agreement.vue
  42. 211 0
      pages/index/index.vue
  43. 261 0
      pages/login/apply-trial.vue
  44. 229 0
      pages/login/forget-pass.vue
  45. 81 0
      pages/login/login.scss
  46. 216 0
      pages/login/login.vue
  47. 66 0
      pages/login/userAuthYs.vue
  48. 65 0
      pages/userCenter/aboutUs.vue
  49. 188 0
      pages/userCenter/changePwd.vue
  50. 133 0
      pages/userCenter/index.vue
  51. BIN
      static/logo-h.png
  52. BIN
      static/tabbar/home-active.png
  53. BIN
      static/tabbar/home.png
  54. BIN
      static/tabbar/shop-active.png
  55. BIN
      static/tabbar/shop.png
  56. BIN
      static/tabbar/user-active.png
  57. BIN
      static/tabbar/user.png
  58. 27 0
      store/$u.mixin.js
  59. 176 0
      store/index.js
  60. 94 0
      uni.scss

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+node_modules/
+.project
+unpackage/
+.DS_Store

+ 52 - 0
App.vue

@@ -0,0 +1,52 @@
+<script>
+	// const uat_URL = 'https://carwash.test.zyucgj.com/gc-shop/' // 预发布
+	const uat_URL = 'http://192.168.16.105:8302/gc-shop/' // 本地
+	const pro_URL = 'https://carwash.zyucgj.com/gc-shop/'  // 生产
+	const buildType = 0 // 打包环境对应类型,1 生产 0 预发布
+	const buildURL = buildType ? pro_URL : uat_URL // 打包后实际对应的url
+	const envText = ['预发布环境','生产环境']
+	const envTips = process.env.NODE_ENV == 'development' ? envText[0] : envText[buildType]
+	console.log(process.env.NODE_ENV)
+	export default {
+		globalData: {  
+			baseUrl: process.env.NODE_ENV == 'development' ? uat_URL : buildURL,
+			token: '',
+			version: '', // 当前版本号
+			buildType: buildType,
+			envTips: envTips
+		},
+		mounted() {
+			// 非V3模式
+			if(this.$scope){
+				this.$scope.globalData.token = this.$store.state.vuex_token
+			}else{
+				// V3模式
+				getApp({allowDefault: true}).globalData.token = this.$store.state.vuex_token
+			}
+		},
+		onLaunch: function() {
+			 console.log('App onLaunch')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		},
+	}
+</script>
+
+<style lang="scss">
+	@import "uview-ui/index.scss";
+	/*每个页面公共css */
+	page {
+		min-height: 100%;
+		display: flex;
+		background-color: #F8F8F8;
+	}
+	/* 登录页是否记住密码多选框样式 */
+	uni-checkbox.login-form-checkbox .uni-checkbox-input{
+		width: 24upx !important;
+		height: 24upx !important;
+	}
+</style>

+ 37 - 0
api/coupon.js

@@ -0,0 +1,37 @@
+import axios from '@/libs/axios.js';
+// 确认领取优惠券
+export const receivesCoupon = (params) => {
+	return axios.request({
+		url: `couponReceives/receivesCoupon`,
+		method: 'post',
+		data: params
+	})
+}
+
+// 根据券码查询优惠券信息
+export const couponData = (params) => {
+	return axios.request({
+		url: `coupon/findByCode/${params.couponCode}`,
+		method: 'get'
+	})
+}
+
+// 优惠券领取明细查询
+export const couponListFind = (params) => {
+	let url = `couponReceives/findPage/${params.pageNo}/${params.pageSize}`
+	delete params.pageNo
+	delete params.pageSize
+	return axios.request({
+		url: url,
+		method: 'post',
+		data: params
+	})
+}
+// 订单可用优惠券查询
+export const couponEnable = (params) => {
+	return axios.request({
+		url: 'couponReceives/findUsableCoupon',
+		method: 'post',
+		data: params
+	})
+}

+ 8 - 0
api/data.js

@@ -0,0 +1,8 @@
+import axios from '@/libs/axios.js';
+// 根据字典code查询选项
+export const getLookUpDatas = (params) => {
+  return axios.request({
+    url: `lookup/findByLookup/${params.type}`,
+    method: 'get'
+  })
+}

+ 27 - 0
api/goods.js

@@ -0,0 +1,27 @@
+import axios from '@/libs/axios.js';
+
+// 获取商品分类
+export const getGoodsClass = params => {
+  return axios.request({
+    url: `goodsType/queryList`,
+    method: 'post',
+	data: params
+  })
+};
+
+// 获取商品列表
+export const getGoodsList = params => {
+  return axios.request({
+    url: `goods/query/${params.pageNo}/${params.pageSize}`,
+    method: 'post',
+	data: params
+  })
+};
+
+// 获取商品详情
+export const getGoodsDetail = params => {
+  return axios.request({
+    url: `goods/findById/${params.id}`,
+    method: 'get',
+  })
+};

+ 43 - 0
api/login.js

@@ -0,0 +1,43 @@
+import axios from '@/libs/axios.js';
+
+export const login = params => {
+  return axios.request({
+    url: `login`,
+    method: 'post',
+	data: params
+  },true)
+};
+export const logout = params => {
+  return axios.request({
+    url: `logout`,
+    data: params,
+    method: 'get'
+  })
+};
+
+// 验证是否登录过
+export const checkLogin = () => {
+  let token = getApp().globalData.token
+  if (token&&token!='') {
+    return axios.request({
+      url: `checkToken`,
+      method: 'get'
+    })
+  } else {
+    return new Promise(resolve => {
+      resolve({
+        status: '9001',
+        message: '系统未登录'
+      });
+    });
+  }
+};
+
+// 获取登录用户信息
+export const getUserInfo = params => {
+  return axios.request({
+    url: `customer/getDetail`,
+    data: params,
+    method: 'get'
+  })
+};

+ 61 - 0
api/order.js

@@ -0,0 +1,61 @@
+import axios from '@/libs/axios.js';
+
+// 获取网点下的服务项目
+export const getStoreItems = params => {
+  return axios.request({
+    url: `item/storeItems/?storeId=${params.storeId}`,
+    method: 'get',
+  })
+};
+
+// 下单 保存订单
+export const saveOrder = params => {
+  return axios.request({
+    url: `order/save`,
+    method: 'post',
+	data: params
+  })
+};
+
+// 下单 预支付
+export const signPay  = params => {
+  return axios.request({
+    url: `order/pay?orderId=${params.orderId}`,
+    method: 'get',
+  })
+};
+
+// 我的订单
+export const getOrderList = params => {
+	let url = `order/query/${params.pageNo}/${params.pageSize}`
+	delete params.pageNo
+	delete params.pageSize
+	return axios.request({
+		url: url,
+		method: 'post',
+		data: params
+	})
+};
+// 订单详情
+export const orderDetail = params => {
+  return axios.request({
+    url: `order/find/${params.id}`,
+    method: 'get',
+  })
+};
+
+// 查询当前是否有未完成的订单
+export const getUnFinishedOrder = params => {
+	return axios.request({
+	  url: `order/unFinishedOrderRunStatus`,
+	  method: 'get',
+	})
+}
+
+// 取消订单
+export const cancleOrder = params => {
+	return axios.request({
+	  url: `order/cancel/${params.id}`,
+	  method: 'get',
+	})
+}

+ 35 - 0
api/receiveAddress.js

@@ -0,0 +1,35 @@
+import axios from '@/libs/axios.js';
+
+// 根据客户编号查找收货地址
+export const findAddressBycustomerNo = params => {
+  return axios.request({
+    url: `receiveaddress/findBycustomerNo/${params.customerNo}`,
+    method: 'get'
+  })
+};
+
+// 收货地址删除
+export const deleteAddress = params => {
+  return axios.request({
+    url: `receiveaddress/delete/${params.id}`,
+    method: 'get'
+  })
+};
+
+// 收货地址列表查询(不分页)
+export const getAddressList = params => {
+  return axios.request({
+    url: `receiveaddress/querylist`,
+    method: 'post',
+	data: params
+  })
+};
+
+// 收货地址保存
+export const saveAddress = params => {
+  return axios.request({
+    url: `receiveaddress/save`,
+    method: 'post',
+	data: params
+  })
+};

+ 36 - 0
api/shoppingCart.js

@@ -0,0 +1,36 @@
+import axios from '@/libs/axios.js';
+
+// 添加商品到购物车
+export const addGoodsToCart = params => {
+  return axios.request({
+    url: `shoppingCart/addGoods`,
+    method: 'post',
+	data: params
+  })
+};
+
+// 从购物车删除商品
+export const deleteGoodsFormCart = params => {
+  return axios.request({
+    url: `shoppingCart/deleteGoods`,
+    method: 'post',
+	data: params
+  })
+};
+
+// 查询购物车列表
+export const getCartList = params => {
+  return axios.request({
+    url: `shoppingCart/queryList`,
+    method: 'post',
+	data: params
+  })
+};
+
+// 更新商品购买数量
+export const updateGoodsBuyQty = params => {
+  return axios.request({
+    url: `shoppingCart/updateGoodsBuyQty/${params.id}/${params.buyQty}`,
+    method: 'get'
+  })
+};

+ 42 - 0
api/store.js

@@ -0,0 +1,42 @@
+import axios from '@/libs/axios.js';
+
+// 获取首页网点列表
+export const getMapStoreList = params => {
+  return axios.request({
+    url: `store/findList`,
+    method: 'post',
+	data: params
+  },true)
+};
+
+// 获取网点列表
+export const getStoreList = params => {
+  return axios.request({
+    url: `store/findSortList`,
+    method: 'post',
+	data: params
+  })
+};
+// 获取网点详情
+export const getStoreDetail = params => {
+  return axios.request({
+    url: `store/findDetail`,
+    method: 'post',
+	data: params
+  })
+};
+// 获取当前排队情况照片
+export const getWaitPhoto = params => {
+  return axios.request({
+    url: `device/image/${params.deviceNo}`,
+    method: 'get'
+  })
+};
+
+// 查看网点营业状态
+export const getStoreStatus = params => {
+  return axios.request({
+    url: `store/validateStoreAndDevice?storeId=${params.storeId}&deviceNo=${params.deviceNo}`,
+    method: 'get'
+  })
+};

+ 133 - 0
api/user.js

@@ -0,0 +1,133 @@
+import axios from '@/libs/axios.js'
+
+//  axios.request()  第二个参数若为true则表示不传token
+
+// 登录
+export const login = (params) => {
+  return axios.request({
+    url: 'auth/login',
+    data: params,
+    method: 'post'
+  }, true)
+}
+//  校验手机号是否注册
+export const validateUser = params => {
+  return axios.request({
+    url: `org/apply/validateUser/${params.phone}`,
+    method: 'get'
+  }, true)
+}
+// 获取图片验证码
+export const getCaptcha = randomCode => {
+  return axios.request({
+    url: `org/apply/getCaptcha/${randomCode}`,
+    method: 'get',
+    responseType: 'blob',
+  }, true)
+}
+// 发送短信验证码
+export const sendVerifyCode = params => {
+  return axios.request({
+    url: 'org/apply/sendVerifyCode',
+    method: 'post',
+    data: params
+  }, true)
+}
+// 申请试用
+export const apply = params => {
+  return axios.request({
+    url: 'org/apply/apply',
+    method: 'post',
+    data: params
+  }, true)
+}
+// 获取门店名称
+export const getStore = (token) => {
+  return axios.request({
+    url: 'getStore',
+    method: 'get'
+  })
+}
+// 登出
+export const logout = () => {
+  return axios.request({
+    url: 'auth/logout',
+    method: 'get'
+  })
+}
+// 修改密码
+export const changePwd = params => {
+  return axios.request({
+    url: 'employee/changePWD',
+    method: 'post',
+    data: {
+      oldPassword: params.oldPassword,
+      password: params.password
+    }
+  }).then(res => res)
+} 
+//  忘记密码 校验手机号是否注册过
+export const memberGetByMobile = params => {
+  return axios.request({
+    url: 'member/getByMobile',
+    method: 'post',
+    data: params
+  }, true)
+}
+//  忘记密码 获取图片验证码
+export const memberCaptcha = randomCode => {
+  return axios.request({
+    url: `member/captcha/create/${randomCode}`,
+    method: 'get',
+    responseType: 'blob'
+  }, true)
+}
+//  忘记密码 发送短信验证码
+export const memberVerifyCode = params => {
+  return axios.request({
+    url: 'member/sendVerifyCode',
+    method: 'post',
+    data: params
+  }, true)
+}
+//  忘记密码 修改密码
+export const memberChangePwd = params => {
+  return axios.request({
+    url: 'member/changePwd',
+    method: 'post',
+    data: params
+  }, true)
+}
+//  忘记密码 校验短信验证码是否正确
+export const memberValidateVerifyCode = params => {
+  return axios.request({
+    url: 'member/validateVerifyCode',
+    method: 'post',
+    data: params
+  }, true)
+}
+// 获取通知消息未读数
+export const getUnreadCount = params => {
+  return axios.request({
+    url: 'noticeUser/queryNotReadCount',
+    method: 'post'
+  })
+}
+// 设置已读消息
+export const hasRead = params => {
+  return axios.request({
+    url: `noticeUser/setRead/${params.msg_id}`,
+    method: 'get'
+  })
+}
+// 查询所有消息
+export const getMessage = params => {
+  let url = `noticeUser/queryLike/${params.pageNo}/${params.pageSize}`
+  delete params.pageNo
+  delete params.pageSize
+  return axios.request({
+    url: url,
+    data: params,
+    method: 'post'
+  })
+}

+ 645 - 0
components/jyf-parser/jyf-parser.vue

@@ -0,0 +1,645 @@
+<template>
+	<view>
+		<slot v-if="!nodes.length" />
+		<!--#ifdef APP-PLUS-NVUE-->
+		<web-view id="_top" ref="web" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
+		<!--#endif-->
+		<!--#ifndef APP-PLUS-NVUE-->
+		<view id="_top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')">
+			<!--#ifdef H5 || MP-360-->
+			<div :id="'rtf'+uid"></div>
+			<!--#endif-->
+			<!--#ifndef H5 || MP-360-->
+			<trees :nodes="nodes" :lazyLoad="lazyLoad" :loading="loadingImg" />
+			<!--#endif-->
+		</view>
+		<!--#endif-->
+	</view>
+</template>
+
+<script>
+	var search;
+	// #ifndef H5 || APP-PLUS-NVUE || MP-360
+	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 dom;
+	// 计算 cache 的 key
+	function hash(str) {
+		for (var i = str.length, val = 5381; i--;)
+			val += (val << 5) + str.charCodeAt(i);
+		return val;
+	}
+	// #endif
+	// #ifdef H5 || APP-PLUS-NVUE || MP-360
+	var {
+		windowWidth,
+		platform
+	} = uni.getSystemInfoSync(),
+		cfg = require('./libs/config.js');
+	// #endif
+	// #ifdef APP-PLUS-NVUE
+	var weexDom = weex.requireModule('dom');
+	// #endif
+	/**
+	 * Parser 富文本组件
+	 * @tutorial https://github.com/jin-yufeng/Parser
+	 * @property {String} html 富文本数据
+	 * @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
+	 * @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
+	 * @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
+	 * @property {Number} compress 压缩等级
+	 * @property {String} domain 图片、视频等链接的主域名
+	 * @property {Boolean} lazyLoad 是否开启图片懒加载
+	 * @property {String} loadingImg 图片加载完成前的占位图
+	 * @property {Boolean} selectable 是否开启长按复制
+	 * @property {Object} tagStyle 标签的默认样式
+	 * @property {Boolean} showWithAnimation 是否使用渐显动画
+	 * @property {Boolean} useAnchor 是否使用锚点
+	 * @property {Boolean} useCache 是否缓存解析结果
+	 * @event {Function} parse 解析完成事件
+	 * @event {Function} load dom 加载完成事件
+	 * @event {Function} ready 所有图片加载完毕事件
+	 * @event {Function} error 错误事件
+	 * @event {Function} imgtap 图片点击事件
+	 * @event {Function} linkpress 链接点击事件
+	 * @author JinYufeng
+	 * @version 20201029
+	 * @listens MIT
+	 */
+	export default {
+		name: 'parser',
+		data() {
+			return {
+				// #ifdef H5 || MP-360
+				uid: this._uid,
+				// #endif
+				// #ifdef APP-PLUS-NVUE
+				height: 1,
+				// #endif
+				// #ifndef APP-PLUS-NVUE
+				showAm: '',
+				// #endif
+				nodes: []
+			}
+		},
+		// #ifndef H5 || APP-PLUS-NVUE || MP-360
+		components: {
+			trees
+		},
+		// #endif
+		props: {
+			html: String,
+			autopause: {
+				type: Boolean,
+				default: true
+			},
+			autoscroll: Boolean,
+			autosetTitle: {
+				type: Boolean,
+				default: true
+			},
+			// #ifndef H5 || APP-PLUS-NVUE || MP-360
+			compress: Number,
+			loadingImg: String,
+			useCache: Boolean,
+			// #endif
+			domain: String,
+			lazyLoad: Boolean,
+			selectable: Boolean,
+			tagStyle: Object,
+			showWithAnimation: Boolean,
+			useAnchor: Boolean
+		},
+		watch: {
+			html(html) {
+				this.setContent(html);
+			}
+		},
+		created() {
+			// 图片数组
+			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 == void 0 || !src) return;
+				// #ifndef MP-ALIPAY || APP-PLUS
+				// 去重
+				if (src.indexOf('http') == 0 && this.includes(src)) {
+					var newSrc = src.split('://')[0];
+					for (var j = newSrc.length, 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
+				}
+			}
+		},
+		mounted() {
+			// #ifdef H5 || MP-360
+			this.document = document.getElementById('rtf' + this._uid);
+			// #endif
+			// #ifndef H5 || APP-PLUS-NVUE || MP-360
+			if (dom) this.document = new dom(this);
+			// #endif
+			if (search) this.search = args => search(this, args);
+			// #ifdef APP-PLUS-NVUE
+			this.document = this.$refs.web;
+			setTimeout(() => {
+				// #endif
+				if (this.html) this.setContent(this.html);
+				// #ifdef APP-PLUS-NVUE
+			}, 30)
+			// #endif
+		},
+		beforeDestroy() {
+			// #ifdef H5 || MP-360
+			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: {
+			// 设置富文本内容
+			setContent(html, append) {
+				// #ifdef APP-PLUS-NVUE
+				if (!html)
+					return this.height = 1;
+				if (append)
+					this.$refs.web.evalJs("var b=document.createElement('div');b.innerHTML='" + html.replace(/'/g, "\\'") +
+						"';document.getElementById('parser').appendChild(b)");
+				else {
+					html =
+						'<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>html,body{width:100%;height:100%;overflow:hidden}body{margin:0}</style><base href="' +
+						this.domain + '"><div id="parser"' + (this.selectable ? '>' : ' style="user-select:none">') + this._handleHtml(html).replace(/\n/g, '\\n') +
+						'</div><script>"use strict";function e(e){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[e]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}document.body.onclick=function(){e({action:"click"})},' +
+						(this.showWithAnimation ? 'document.body.style.animation="_show .5s",' : '') +
+						'setTimeout(function(){e({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight})},50);\x3c/script>';
+					if (platform == 'android') html = html.replace(/%/g, '%25');
+					this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
+				}
+				this.$refs.web.evalJs(
+					'var t=document.getElementsByTagName("title");t.length&&e({action:"getTitle",title:t[0].innerText});for(var o,n=document.getElementsByTagName("style"),r=1;o=n[r++];)o.innerHTML=o.innerHTML.replace(/body/g,"#parser");for(var a,c=document.getElementsByTagName("img"),s=[],i=0==c.length,d=0,l=0,g=0;a=c[l];l++)parseInt(a.style.width||a.getAttribute("width"))>' +
+					windowWidth + '&&(a.style.height="auto"),a.onload=function(){++d==c.length&&(i=!0)},a.onerror=function(){++d==c.length&&(i=!0),' + (cfg.errorImg ? 'this.src="' + cfg.errorImg + '",' : '') +
+					'e({action:"error",source:"img",target:this})},a.hasAttribute("ignore")||"A"==a.parentElement.nodeName||(a.i=g++,s.push(a.getAttribute("original-src")||a.src||a.getAttribute("data-src")),a.onclick=function(t){t.stopPropagation(),e({action:"preview",img:{i:this.i,src:this.src}})});e({action:"getImgList",imgList:s});for(var u,m=document.getElementsByTagName("a"),f=0;u=m[f];f++)u.onclick=function(m){m.stopPropagation();var t,o=this.getAttribute("href");if("#"==o[0]){var n=document.getElementById(o.substr(1));n&&(t=n.offsetTop)}return e({action:"linkpress",href:o,offset:t}),!1};for(var h,y=document.getElementsByTagName("video"),v=0;h=y[v];v++)h.style.maxWidth="100%",h.onerror=function(){e({action:"error",source:"video",target:this})}' +
+					(this.autopause ? ',h.onplay=function(){for(var e,t=0;e=y[t];t++)e!=this&&e.pause()}' : '') +
+					';for(var _,p=document.getElementsByTagName("audio"),w=0;_=p[w];w++)_.onerror=function(){e({action:"error",source:"audio",target:this})};' +
+					(this.autoscroll ? 'for(var T,E=document.getElementsByTagName("table"),B=0;T=E[B];B++){var N=document.createElement("div");N.style.overflow="scroll",T.parentNode.replaceChild(N,T),N.appendChild(T)}' : '') +
+					'var x=document.getElementById("parser");clearInterval(window.timer),window.timer=setInterval(function(){i&&clearInterval(window.timer),e({action:"ready",ready:i,height:x.scrollHeight})},350)'
+				)
+				this.nodes = [1];
+				// #endif
+				// #ifdef H5 || MP-360
+				if (!html) {
+					if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
+					return;
+				}
+				var div = document.createElement('div');
+				if (!append) {
+					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 = this._handleHtml(html, append);
+				for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
+					style.innerHTML = style.innerHTML.replace(/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: '500px 0px 500px 0px'
+					})
+				}
+				var _ts = this;
+				// 获取标题
+				var title = this.rtf.getElementsByTagName('title');
+				if (title.length && this.autosetTitle)
+					uni.setNavigationBarTitle({
+						title: title[0].innerText
+					})
+				// 填充 domain
+				var fill = target => {
+					var src = target.getAttribute('src');
+					if (this.domain && src) {
+						if (src[0] == '/') {
+							if (src[1] == '/')
+								target.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
+							else target.src = this.domain + src;
+						} else if (!src.includes('://') && src.indexOf('data:') != 0) target.src = this.domain + '/' + src;
+					}
+				}
+				// 图片处理
+				this.imgList.length = 0;
+				var imgs = this.rtf.getElementsByTagName('img');
+				for (let i = 0, j = 0, img; img = imgs[i]; i++) {
+					if (parseInt(img.style.width || img.getAttribute('width')) > windowWidth)
+						img.style.height = 'auto';
+					fill(img);
+					if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
+						img.i = j++;
+						_ts.imgList.push(img.getAttribute('original-src') || img.src || img.getAttribute('data-src'));
+						img.onclick = function(e) {
+							e.stopPropagation();
+							var preview = true;
+							this.ignore = () => preview = false;
+							_ts.$emit('imgtap', this);
+							if (preview) {
+								uni.previewImage({
+									current: this.i,
+									urls: _ts.imgList
+								});
+							}
+						}
+					}
+					img.onerror = function() {
+						if (cfg.errorImg)
+							_ts.imgList[this.i] = this.src = cfg.errorImg;
+						_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(e) {
+						e.stopPropagation();
+						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++];) {
+					fill(video);
+					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('audio');
+				for (var audio of audios) {
+					fill(audio);
+					audio.onerror = function() {
+						_ts.$emit('error', {
+							source: 'audio',
+							target: this
+						});
+					}
+				}
+				// 表格处理
+				if (this.autoscroll) {
+					var tables = this.rtf.getElementsByTagName('table');
+					for (var table of tables) {
+						let div = document.createElement('div');
+						div.style.overflow = 'scroll';
+						table.parentNode.replaceChild(div, table);
+						div.appendChild(table);
+					}
+				}
+				if (!append) this.document.appendChild(this.rtf);
+				this.$nextTick(() => {
+					this.nodes = [1];
+					this.$emit('load');
+				});
+				setTimeout(() => this.showAm = '', 500);
+				// #endif
+				// #ifndef APP-PLUS-NVUE
+				// #ifndef H5 || MP-360
+				var nodes;
+				if (!html) return this.nodes = [];
+				var 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);
+				if (append) this.nodes = this.nodes.concat(nodes);
+				else this.nodes = nodes;
+				if (nodes.length && nodes.title && this.autosetTitle)
+					uni.setNavigationBarTitle({
+						title: nodes.title
+					})
+				if (this.imgList) this.imgList.length = 0;
+				this.videoContexts = [];
+				this.$nextTick(() => {
+					(function f(cs) {
+						for (var i = cs.length; i--;) {
+							if (cs[i].top) {
+								cs[i].controls = [];
+								cs[i].init();
+								f(cs[i].$children);
+							}
+						}
+					})(this.$children)
+					this.$emit('load');
+				})
+				// #endif
+				var height;
+				clearInterval(this._timer);
+				this._timer = setInterval(() => {
+					// #ifdef H5 || MP-360
+					this.rect = this.rtf.getBoundingClientRect();
+					// #endif
+					// #ifndef H5 || MP-360
+					uni.createSelectorQuery().in(this)
+						.select('#_top').boundingClientRect().exec(res => {
+							if (!res) return;
+							this.rect = res[0];
+							// #endif
+							if (this.rect.height == height) {
+								this.$emit('ready', this.rect)
+								clearInterval(this._timer);
+							}
+							height = this.rect.height;
+							// #ifndef H5 || MP-360
+						});
+					// #endif
+				}, 350);
+				if (this.showWithAnimation && !append) this.showAm = 'animation:_show .5s';
+				// #endif
+			},
+			// 获取文本内容
+			getText(ns = this.nodes) {
+				var txt = '';
+				// #ifdef APP-PLUS-NVUE
+				txt = this._text;
+				// #endif
+				// #ifdef H5 || MP-360
+				txt = this.rtf.innerText;
+				// #endif
+				// #ifndef H5 || APP-PLUS-NVUE || MP-360
+				for (var i = 0, n; n = ns[i++];) {
+					if (n.type == 'text') txt += n.text.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';
+					}
+				}
+				// #endif
+				return txt;
+			},
+			// 锚点跳转
+			in (obj) {
+				if (obj.page && obj.selector && obj.scrollTop) this._in = obj;
+			},
+			navigateTo(obj) {
+				if (!this.useAnchor) return obj.fail && obj.fail('Anchor is disabled');
+				// #ifdef APP-PLUS-NVUE
+				if (!obj.id)
+					weexDom.scrollToElement(this.$refs.web);
+				else
+					this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
+						'");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop+' + (obj.offset || 0) + '})');
+				obj.success && obj.success();
+				// #endif
+				// #ifndef APP-PLUS-NVUE
+				var d = ' ';
+				// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
+				d = '>>>';
+				// #endif
+				var selector = uni.createSelectorQuery().in(this._in ? this._in.page : this).select((this._in ? this._in.selector :
+					'#_top') + (obj.id ? `${d}#${obj.id},${this._in?this._in.selector:'#_top'}${d}.${obj.id}` : '')).boundingClientRect();
+				if (this._in) selector.select(this._in.selector).scrollOffset().select(this._in.selector).boundingClientRect();
+				else selector.selectViewport().scrollOffset();
+				selector.exec(res => {
+					if (!res[0]) return obj.fail && obj.fail('Label not found')
+					var scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + (obj.offset || 0);
+					if (this._in) this._in.page[this._in.scrollTop] = scrollTop;
+					else uni.pageScrollTo({
+						scrollTop,
+						duration: 300
+					})
+					obj.success && obj.success();
+				})
+				// #endif
+			},
+			// 获取视频对象
+			getVideoContext(id) {
+				// #ifndef APP-PLUS-NVUE
+				if (!id) return this.videoContexts;
+				else
+					for (var i = this.videoContexts.length; i--;)
+						if (this.videoContexts[i].id == id) return this.videoContexts[i];
+				// #endif
+			},
+			// #ifdef H5 || APP-PLUS-NVUE || MP-360
+			_handleHtml(html, append) {
+				if (!append) {
+					// 处理 tag-style 和 userAgentStyles
+					var style = '<style>@keyframes _show{0%{opacity:0}100%{opacity:1}}img{max-width:100%}';
+					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;
+				}
+				// 处理 rpx
+				if (html.includes('rpx'))
+					html = html.replace(/[0-9.]+\s*rpx/g, $ => (parseFloat($) * windowWidth / 750) + 'px');
+				return html;
+			},
+			// #endif
+			// #ifdef APP-PLUS-NVUE
+			_message(e) {
+				// 接收 web-view 消息
+				var d = e.detail.data[0];
+				switch (d.action) {
+					case 'load':
+						this.$emit('load');
+						this.height = d.height;
+						this._text = d.text;
+						break;
+					case 'getTitle':
+						if (this.autosetTitle)
+							uni.setNavigationBarTitle({
+								title: d.title
+							})
+						break;
+					case 'getImgList':
+						this.imgList.length = 0;
+						for (var i = d.imgList.length; i--;)
+							this.imgList.setItem(i, d.imgList[i]);
+						break;
+					case 'preview':
+						var preview = true;
+						d.img.ignore = () => preview = false;
+						this.$emit('imgtap', d.img);
+						if (preview)
+							uni.previewImage({
+								current: d.img.i,
+								urls: this.imgList
+							})
+						break;
+					case 'linkpress':
+						var jump = true,
+							href = d.href;
+						this.$emit('linkpress', {
+							href,
+							ignore: () => jump = false
+						})
+						if (jump && href) {
+							if (href[0] == '#') {
+								if (this.useAnchor)
+									weexDom.scrollToElement(this.$refs.web, {
+										offset: d.offset
+									})
+							} else if (href.includes('://'))
+								plus.runtime.openWeb(href);
+							else
+								uni.navigateTo({
+									url: href
+								})
+						}
+						break;
+					case 'error':
+						if (d.source == 'img' && cfg.errorImg)
+							this.imgList.setItem(d.target.i, cfg.errorImg);
+						this.$emit('error', {
+							source: d.source,
+							target: d.target
+						})
+						break;
+					case 'ready':
+						this.height = d.height;
+						if (d.ready) uni.createSelectorQuery().in(this).select('#_top').boundingClientRect().exec(res => {
+							this.rect = res[0];
+							this.$emit('ready', res[0]);
+						})
+						break;
+					case 'click':
+						this.$emit('click');
+						this.$emit('tap');
+				}
+			},
+			// #endif
+		}
+	}
+</script>
+
+<style>
+	@keyframes _show {
+		0% {
+			opacity: 0;
+		}
+
+		100% {
+			opacity: 1;
+		}
+	}
+
+	/* #ifdef MP-WEIXIN */
+	:host {
+		display: block;
+		overflow: auto;
+		-webkit-overflow-scrolling: touch;
+	}
+
+	/* #endif */
+</style>

+ 100 - 0
components/jyf-parser/libs/CssHandler.js

@@ -0,0 +1,100 @@
+const cfg = require('./config.js'),
+	isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+
+function CssHandler(tagStyle) {
+	var styles = Object.assign(Object.create(null), cfg.userAgentStyles);
+	for (var item in tagStyle)
+		styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
+	this.styles = styles;
+}
+CssHandler.prototype.getStyle = function(data) {
+	this.styles = new parser(data, this.styles).parse();
+}
+CssHandler.prototype.match = function(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;
+
+function parser(data, init) {
+	this.data = data;
+	this.floor = 0;
+	this.i = 0;
+	this.list = [];
+	this.res = init;
+	this.state = this.Space;
+}
+parser.prototype.parse = function() {
+	for (var c; c = this.data[this.i]; this.i++)
+		this.state(c);
+	return this.res;
+}
+parser.prototype.section = function() {
+	return this.data.substring(this.start, this.i);
+}
+// 状态机
+parser.prototype.Space = function(c) {
+	if (c == '.' || c == '#' || 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;
+}
+parser.prototype.Comment = function() {
+	this.i = this.data.indexOf('*/', this.i) + 1;
+	if (!this.i) this.i = this.data.length;
+	this.state = this.Space;
+}
+parser.prototype.Ignore = function(c) {
+	if (c == '{') this.floor++;
+	else if (c == '}' && !--this.floor) {
+		this.list = [];
+		this.state = this.Space;
+	}
+}
+parser.prototype.Name = function(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 (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
+		this.state = this.Ignore;
+}
+parser.prototype.NameSpace = function(c) {
+	if (c == '{') this.Content();
+	else if (c == ',') this.Comma();
+	else if (!cfg.blankChar[c]) this.state = this.Ignore;
+}
+parser.prototype.Comma = function() {
+	while (cfg.blankChar[this.data[++this.i]]);
+	if (this.data[this.i] == '{') this.Content();
+	else {
+		this.start = this.i--;
+		this.state = this.Name;
+	}
+}
+parser.prototype.Content = function() {
+	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;
+}

+ 580 - 0
components/jyf-parser/libs/MpHtmlParser.js

@@ -0,0 +1,580 @@
+/**
+ * html 解析器
+ * @tutorial https://github.com/jin-yufeng/Parser
+ * @version 20201029
+ * @author JinYufeng
+ * @listens MIT
+ */
+const cfg = require('./config.js'),
+	blankChar = cfg.blankChar,
+	CssHandler = require('./CssHandler.js'),
+	windowWidth = uni.getSystemInfoSync().windowWidth;
+var emoji;
+
+function MpHtmlParser(data, options = {}) {
+	this.attrs = {};
+	this.CssHandler = new CssHandler(options.tagStyle, windowWidth);
+	this.data = data;
+	this.domain = options.domain;
+	this.DOM = [];
+	this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
+	options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http';
+	this.options = options;
+	this.state = this.Text;
+	this.STACK = [];
+	// 工具函数
+	this.bubble = () => {
+		for (var i = this.STACK.length, item; item = this.STACK[--i];) {
+			if (cfg.richOnlyTags[item.name]) return false;
+			item.c = 1;
+		}
+		return true;
+	}
+	this.decode = (val, amp) => {
+		var i = -1,
+			j, en;
+		while (1) {
+			if ((i = val.indexOf('&', i + 1)) == -1) break;
+			if ((j = val.indexOf(';', i + 2)) == -1) break;
+			if (val[i + 1] == '#') {
+				en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j));
+				if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1);
+			} else {
+				en = val.substring(i + 1, j);
+				if (cfg.entities[en] || en == amp)
+					val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1);
+			}
+		}
+		return val;
+	}
+	this.getUrl = url => {
+		if (url[0] == '/') {
+			if (url[1] == '/') url = this.options.prot + ':' + 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;
+	}
+	this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
+	this.section = () => this.data.substring(this.start, this.i);
+	this.parent = () => this.STACK[this.STACK.length - 1];
+	this.siblings = () => this.STACK.length ? this.parent().children : this.DOM;
+}
+MpHtmlParser.prototype.parse = function() {
+	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());
+	return this.DOM;
+}
+// 设置属性
+MpHtmlParser.prototype.setAttr = function() {
+	var name = this.attrName.toLowerCase(),
+		val = this.attrVal;
+	if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
+	else if (val) {
+		if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp'));
+		else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp');
+		else if (name.substr(0, 5) != 'data-') this.attrs[name] = val;
+	}
+	this.attrVal = '';
+	while (blankChar[this.data[this.i]]) this.i++;
+	if (this.isClose()) this.setNode();
+	else {
+		this.start = this.i;
+		this.state = this.AttrName;
+	}
+}
+// 设置文本节点
+MpHtmlParser.prototype.setText = function() {
+	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 flag, tmp = [];
+		for (let i = text.length, c; c = text[--i];)
+			if (!blankChar[c]) {
+				tmp.unshift(c);
+				if (!flag) flag = 1;
+			} else {
+				if (tmp[0] != ' ') tmp.unshift(' ');
+				if (c == '\n' && flag == void 0) flag = 0;
+			}
+		if (flag == 0) return;
+		text = tmp.join('');
+	}
+	this.siblings().push({
+		type: 'text',
+		text: this.decode(text)
+	});
+}
+// 设置元素节点
+MpHtmlParser.prototype.setNode = function() {
+	var node = {
+			name: this.tagName.toLowerCase(),
+			attrs: this.attrs
+		},
+		close = cfg.selfClosingTags[node.name];
+	if (this.options.nodes.length) node.type = 'node';
+	this.attrs = {};
+	if (!cfg.ignoreTags[node.name]) {
+		// 处理属性
+		var attrs = node.attrs,
+			style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
+			styleObj = {};
+		if (attrs.id) {
+			if (this.options.compress & 1) attrs.id = void 0;
+			else if (this.options.useAnchor) this.bubble();
+		}
+		if ((this.options.compress & 2) && attrs.class) attrs.class = void 0;
+		switch (node.name) {
+			case 'a':
+			case 'ad': // #ifdef APP-PLUS
+			case 'iframe':
+				// #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 'embed':
+				// #ifndef APP-PLUS
+				var src = node.attrs.src || '',
+					type = node.attrs.type || '';
+				if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8'))
+					node.name = 'video';
+				else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes(
+						'.aac'))
+					node.name = 'audio';
+				else break;
+				if (node.attrs.autostart)
+					node.attrs.autoplay = 'T';
+				node.attrs.controls = 'T';
+				// #endif
+				// #ifdef APP-PLUS
+				this.bubble();
+				break;
+				// #endif
+			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 (this.videoNum > 3)
+						node.lazyLoad = 1;
+					if (attrs.width) {
+						styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px');
+						attrs.width = void 0;
+					}
+					if (attrs.height) {
+						styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px');
+						attrs.height = void 0;
+					}
+				}
+				if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T';
+				attrs.source = [];
+				if (attrs.src) {
+					attrs.source.push(attrs.src);
+					attrs.src = void 0;
+				}
+				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.flag = 1;
+							break;
+						}
+		}
+		if (attrs.align) {
+			if (node.name == 'table') {
+				if (attrs.align == 'center') styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto';
+				else styleObj['float'] = attrs.align;
+			} else styleObj['text-align'] = attrs.align;
+			attrs.align = void 0;
+		}
+		// 压缩 style
+		var styles = style.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[0] == '-' || value.includes('safe'))
+				style += `;${key}:${value}`;
+			else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
+				styleObj[key] = value;
+		}
+		if (node.name == 'img') {
+			if (attrs.src && !attrs.ignore) {
+				if (this.bubble())
+					attrs.i = (this.imgNum++).toString();
+				else attrs.ignore = 'T';
+			}
+			if (attrs.ignore) {
+				style += ';-webkit-touch-callout:none';
+				styleObj['max-width'] = '100%';
+			}
+			var width;
+			if (styleObj.width) width = styleObj.width;
+			else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : parseFloat(attrs.width) + 'px';
+			if (width) {
+				styleObj.width = width;
+				attrs.width = '100%';
+				if (parseInt(width) > windowWidth) {
+					styleObj.height = '';
+					if (attrs.height) attrs.height = void 0;
+				}
+			}
+			if (styleObj.height) {
+				attrs.height = styleObj.height;
+				styleObj.height = '';
+			} else if (attrs.height && !attrs.height.includes('%'))
+				attrs.height = parseFloat(attrs.height) + 'px';
+		}
+		for (var key in styleObj) {
+			var value = styleObj[key];
+			if (!value) continue;
+			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($) * windowWidth / 750 + 'px');
+			else if (key == 'white-space' && value.includes('pre') && !close)
+				this.pre = node.pre = true;
+			style += `;${key}:${value}`;
+		}
+		style = style.substr(1);
+		if (style) attrs.style = style;
+		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.parent();
+			if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src)
+				parent.attrs.source.push(node.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;
+}
+// 移除标签
+MpHtmlParser.prototype.remove = function(node) {
+	var name = node.name,
+		j = this.i;
+	// 处理 svg
+	var handleSvg = () => {
+		var src = this.data.substring(j, this.i + 1);
+		node.attrs.xmlns = 'http://www.w3.org/2000/svg';
+		for (var key in node.attrs) {
+			if (key == 'viewbox') src = ` viewBox="${node.attrs.viewbox}"` + src;
+			else if (key != 'style') src = ` ${key}="${node.attrs[key]}"` + src;
+		}
+		src = '<svg' + src;
+		var parent = this.parent();
+		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'),
+				style: node.attrs.style,
+				ignore: 'T'
+			}
+		})
+	}
+	if (node.name == 'svg' && this.data[j] == '/') return handleSvg(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.section().toLowerCase() == 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.DOM.title = this.data.substring(j + 1, this.i - 7);
+			if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
+			if (name == 'svg') handleSvg();
+			return;
+		}
+	}
+}
+// 节点出栈处理
+MpHtmlParser.prototype.popNode = function(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;
+	}
+	var siblings = this.siblings(),
+		len = siblings.length,
+		childs = node.children;
+	if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
+		return siblings.pop();
+	var attrs = node.attrs;
+	// 替换一些标签名
+	if (cfg.blockTags[node.name]) node.name = 'div';
+	else if (!cfg.trustTags[node.name]) node.name = 'span';
+	// 处理列表
+	if (node.c && (node.name == 'ul' || node.name == 'ol')) {
+		if ((node.attrs.style || '').includes('list-style:none')) {
+			for (let i = 0, child; child = childs[i++];)
+				if (child.name == 'li')
+					child.name = 'div';
+		} else 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 = childs.length; i--;)
+					childs[i].floor = floor;
+		} else {
+			for (let i = 0, num = 1, child; child = childs[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 = parseFloat(attrs.cellpadding),
+			spacing = parseFloat(attrs.cellspacing),
+			border = parseFloat(attrs.border);
+		if (node.c) {
+			if (isNaN(padding)) padding = 2;
+			if (isNaN(spacing)) spacing = 2;
+		}
+		if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
+		if (node.flag && node.c) {
+			// 有 colspan 或 rowspan 且含有链接的表格转为 grid 布局实现
+			attrs.style = `${attrs.style || ''};${spacing ? `;grid-gap:${spacing}px` : ';border-left:0;border-top:0'}`;
+			var row = 1,
+				col = 1,
+				colNum,
+				trs = [],
+				children = [],
+				map = {};
+			(function f(ns) {
+				for (var i = 0; i < ns.length; i++) {
+					if (ns[i].name == 'tr') trs.push(ns[i]);
+					else f(ns[i].children || []);
+				}
+			})(node.children)
+			for (let i = 0; i < trs.length; i++) {
+				for (let j = 0, td; td = trs[i].children[j]; j++) {
+					if (td.name == 'td' || td.name == 'th') {
+						while (map[row + '.' + col]) col++;
+						var cell = {
+							name: 'div',
+							c: 1,
+							attrs: {
+								style: (td.attrs.style || '') + (border ? `;border:${border}px solid gray` + (spacing ? '' :
+									';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '')
+							},
+							children: td.children
+						}
+						if (td.attrs.colspan) {
+							cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + parseInt(td.attrs.colspan));
+							if (!td.attrs.rowspan) cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + 1);
+							col += parseInt(td.attrs.colspan) - 1;
+						}
+						if (td.attrs.rowspan) {
+							cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + parseInt(td.attrs.rowspan));
+							if (!td.attrs.colspan) cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + 1);
+							for (var k = 1; k < td.attrs.rowspan; k++) map[(row + k) + '.' + col] = 1;
+						}
+						children.push(cell);
+						col++;
+					}
+				}
+				if (!colNum) {
+					colNum = col - 1;
+					attrs.style += `;grid-template-columns:repeat(${colNum},auto)`
+				}
+				col = 1;
+				row++;
+			}
+			node.children = children;
+		} else {
+			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 || []);
+					}
+				})(childs)
+		}
+		if (this.options.autoscroll) {
+			var table = Object.assign({}, node);
+			node.name = 'div';
+			node.attrs = {
+				style: 'overflow:scroll'
+			}
+			node.children = [table];
+		}
+	}
+	this.CssHandler.pop && this.CssHandler.pop(node);
+	// 自动压缩
+	if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div')
+		siblings[len - 1] = childs[0];
+}
+// 状态机
+MpHtmlParser.prototype.Text = function(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 == '!' || next == '?') {
+			this.setText();
+			this.Comment();
+		}
+	}
+}
+MpHtmlParser.prototype.Comment = function() {
+	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;
+}
+MpHtmlParser.prototype.TagName = function(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();
+	}
+}
+MpHtmlParser.prototype.AttrName = function(c) {
+	if (c == '=' || blankChar[c] || this.isClose()) {
+		this.attrName = this.section();
+		if (blankChar[c])
+			while (blankChar[this.data[++this.i]]);
+		if (this.data[this.i] == '=') {
+			while (blankChar[this.data[++this.i]]);
+			this.start = this.i--;
+			this.state = this.AttrValue;
+		} else this.setAttr();
+	}
+}
+MpHtmlParser.prototype.AttrValue = function(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();
+}
+MpHtmlParser.prototype.EndTag = function(c) {
+	if (blankChar[c] || c == '>' || c == '/') {
+		var name = this.section().toLowerCase();
+		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);
+			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
components/jyf-parser/libs/config.js

@@ -0,0 +1,80 @@
+/* 配置文件 */
+var cfg = {
+	// 出错占位图
+	errorImg: null,
+	// 过滤器函数
+	filter: null,
+	// 代码高亮函数
+	highlight: null,
+	// 文本处理函数
+	onText: null,
+	// 实体编码列表
+	entities: {
+		quot: '"',
+		apos: "'",
+		semi: ';',
+		nbsp: '\xA0',
+		ensp: '\u2002',
+		emsp: '\u2003',
+		ndash: '–',
+		mdash: '—',
+		middot: '·',
+		lsquo: '‘',
+		rsquo: '’',
+		ldquo: '“',
+		rdquo: '”',
+		bull: '•',
+		hellip: '…'
+	},
+	blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
+	boolAttrs: makeMap('allowfullscreen,autoplay,autostart,controls,ignore,loop,muted'),
+	// 块级标签,将被转为 div
+	blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
+	// 将被移除的标签
+	ignoreTags: makeMap('area,base,canvas,frame,iframe,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'),
+	// 只能被 rich-text 显示的标签
+	richOnlyTags: makeMap('a,colgroup,fieldset,legend'),
+	// 自闭合的标签
+	selfClosingTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
+	// 信任的标签
+	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'),
+	// 默认的标签样式
+	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',
+		mark: 'background-color:yellow',
+		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 = Object.create(null),
+		list = str.split(',');
+	for (var i = list.length; i--;)
+		map[list[i]] = true;
+	return map;
+}
+
+// #ifdef MP-WEIXIN
+if (wx.canIUse('editor')) {
+	cfg.blockTags.pre = void 0;
+	cfg.ignoreTags.rp = true;
+	Object.assign(cfg.richOnlyTags, makeMap('bdi,bdo,caption,rt,ruby'));
+	Object.assign(cfg.trustTags, makeMap('bdi,bdo,caption,pre,rt,ruby'));
+}
+// #endif
+
+// #ifdef APP-PLUS
+cfg.ignoreTags.iframe = void 0;
+Object.assign(cfg.trustTags, makeMap('embed,iframe'));
+// #endif
+
+module.exports = cfg;

+ 22 - 0
components/jyf-parser/libs/handler.wxs

@@ -0,0 +1,22 @@
+var inline = {
+	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,
+	sub: 1,
+	sup: 1
+}
+module.exports = {
+	use: function(item) {
+		return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1
+	}
+}

+ 505 - 0
components/jyf-parser/libs/trees.vue

@@ -0,0 +1,505 @@
+<template>
+	<view :class="'interlayer '+(c||'')" :style="s">
+		<block v-for="(n, i) in nodes" v-bind:key="i">
+			<!--图片-->
+			<view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap.stop="imgtap">
+				<rich-text v-if="ctrl[i]!=0" :nodes="[{attrs:{src:loading&&(ctrl[i]||0)<2?loading:(lazyLoad&&!ctrl[i]?placeholder:(ctrl[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
+				<image class="_image" :src="lazyLoad&&!ctrl[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad"
+				 :show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg"
+				 @error="error" />
+			</view>
+			<!--文本-->
+			<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
+			<!--#ifndef MP-BAIDU-->
+			<text v-else-if="n.name=='br'">\n</text>
+			<!--#endif-->
+			<!--视频-->
+			<view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&ctrl[i]==undefined" :id="n.attrs.id"
+			 :class="'_video '+(n.attrs.class||'')" :style="n.attrs.style" :data-i="i" @tap.stop="_loadVideo" />
+			<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||ctrl[i]==0"
+			 :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[ctrl[i]||0]"
+			 :unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" />
+			<!--音频-->
+			<audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :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[ctrl[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio" @error.native="error"
+			 @play.native="play" />
+			<!--链接-->
+			<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
+			 :data-attrs="n.attrs" @tap.stop="linkpress">
+				<trees class="_span" c="_span" :nodes="n.children" />
+			</view>
+			<!--广告-->
+			<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
+			<!--列表-->
+			<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex;flex-direction:row'">
+				<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>
+				<trees class="_li" c="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
+			</view>
+			<!--表格-->
+			<view v-else-if="n.name=='table'&&n.c&&n.flag" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:grid'">
+				<trees v-for="(cell,n) in n.children" v-bind:key="n" :class="cell.attrs.class" :c="cell.attrs.class" :style="cell.attrs.style"
+				 :s="cell.attrs.style" :nodes="cell.children" />
+			</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, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
+					<view v-for="(tr, p) in tbody.children" v-bind:key="p" :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" />
+						<trees v-else v-for="(td, q) in tr.children" v-bind:key="q" :class="td.attrs.class" :c="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
+						 :s="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" :nodes="td.children" />
+					</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 || APP-PLUS-->
+			<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
+			<!--#endif-->
+			<!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
+			<rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" />
+			<!--#endif-->
+			<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :c="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')"
+			 :style="n.attrs.style" :s="n.attrs.style" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
+		</block>
+	</view>
+</template>
+<script module="handler" lang="wxs" src="./handler.wxs"></script>
+<script>
+	global.Parser = {};
+	import trees from './trees'
+	const errorImg = require('../libs/config.js').errorImg;
+	export default {
+		components: {
+			trees
+		},
+		name: 'trees',
+		data() {
+			return {
+				ctrl: [],
+				placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
+				errorImg,
+				loadVideo: typeof plus == 'undefined',
+				// #ifndef MP-ALIPAY
+				c: '',
+				s: ''
+				// #endif
+			}
+		},
+		props: {
+			nodes: Array,
+			lazyLoad: Boolean,
+			loading: String,
+			// #ifdef MP-ALIPAY
+			c: String,
+			s: String
+			// #endif
+		},
+		mounted() {
+			for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent);
+			this.init();
+		},
+		// #ifdef APP-PLUS
+		beforeDestroy() {
+			this.observer && this.observer.disconnect();
+		},
+		// #endif
+		methods: {
+			init() {
+				for (var i = this.nodes.length, n; n = this.nodes[--i];) {
+					if (n.name == 'img') {
+						this.top.imgList.setItem(n.attrs.i, n.attrs['original-src'] || n.attrs.src);
+						// #ifdef APP-PLUS
+						if (this.lazyLoad && !this.observer) {
+							this.observer = uni.createIntersectionObserver(this).relativeToViewport({
+								top: 500,
+								bottom: 500
+							});
+							setTimeout(() => {
+								this.observer.observe('._img', res => {
+									if (res.intersectionRatio) {
+										for (var j = this.nodes.length; j--;)
+											if (this.nodes[j].name == 'img')
+												this.$set(this.ctrl, j, 1);
+										this.observer.disconnect();
+									}
+								})
+							}, 0)
+						}
+						// #endif
+					} else if (n.name == 'video' || n.name == 'audio') {
+						var ctx;
+						if (n.name == 'video') {
+							ctx = uni.createVideoContext(n.attrs.id
+								// #ifndef MP-BAIDU
+								, this
+								// #endif
+							);
+						} else if (this.$refs[n.attrs.id])
+							ctx = this.$refs[n.attrs.id][0];
+						if (ctx) {
+							ctx.id = n.attrs.id;
+							this.top.videoContexts.push(ctx);
+						}
+					}
+				}
+				// #ifdef APP-PLUS
+				// APP 上避免 video 错位需要延时渲染
+				setTimeout(() => {
+					this.loadVideo = true;
+				}, 1000)
+				// #endif
+			},
+			play(e) {
+				var contexts = this.top.videoContexts;
+				if (contexts.length > 1 && this.top.autopause)
+					for (var i = contexts.length; i--;)
+						if (contexts[i].id != e.currentTarget.dataset.id)
+							contexts[i].pause();
+			},
+			imgtap(e) {
+				var attrs = e.currentTarget.dataset.attrs;
+				if (!attrs.ignore) {
+					var preview = true,
+						data = {
+							id: e.target.id,
+							src: attrs.src,
+							ignore: () => preview = false
+						};
+					global.Parser.onImgtap && global.Parser.onImgtap(data);
+					this.top.$emit('imgtap', data);
+					if (preview) {
+						var urls = this.top.imgList,
+							current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
+						uni.previewImage({
+							current,
+							urls
+						})
+					}
+				}
+			},
+			loadImg(e) {
+				var i = e.currentTarget.dataset.i;
+				if (this.lazyLoad && !this.ctrl[i]) {
+					// #ifdef QUICKAPP-WEBVIEW
+					this.$set(this.ctrl, i, 0);
+					this.$nextTick(function() {
+						// #endif
+						// #ifndef APP-PLUS
+						this.$set(this.ctrl, i, 1);
+						// #endif
+						// #ifdef QUICKAPP-WEBVIEW
+					})
+					// #endif
+				} else if (this.loading && this.ctrl[i] != 2) {
+					// #ifdef QUICKAPP-WEBVIEW
+					this.$set(this.ctrl, i, 0);
+					this.$nextTick(function() {
+						// #endif
+						this.$set(this.ctrl, i, 2);
+						// #ifdef QUICKAPP-WEBVIEW
+					})
+					// #endif
+				}
+			},
+			linkpress(e) {
+				var jump = true,
+					attrs = e.currentTarget.dataset.attrs;
+				attrs.ignore = () => jump = false;
+				global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
+				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
+							plus.runtime.openWeb(attrs.href);
+							// #endif
+							// #ifndef APP-PLUS
+							uni.setClipboardData({
+								data: attrs.href,
+								success: () =>
+									uni.showToast({
+										title: '链接已复制'
+									})
+							})
+							// #endif
+						} else
+							uni.navigateTo({
+								url: attrs.href,
+								fail() {
+									uni.switchTab({
+										url: attrs.href,
+									})
+								}
+							})
+					}
+				}
+			},
+			error(e) {
+				var target = e.currentTarget,
+					source = target.dataset.source,
+					i = target.dataset.i;
+				if (source == 'video' || source == 'audio') {
+					// 加载其他 source
+					var index = this.ctrl[i] ? this.ctrl[i].i + 1 : 1;
+					if (index < this.nodes[i].attrs.source.length)
+						this.$set(this.ctrl, i, index);
+					if (e.detail.__args__)
+						e.detail = e.detail.__args__[0];
+				} else if (errorImg && source == 'img') {
+					this.top.imgList.setItem(target.dataset.index, errorImg);
+					this.$set(this.ctrl, i, 3);
+				}
+				this.top && this.top.$emit('error', {
+					source,
+					target,
+					errMsg: e.detail.errMsg
+				});
+			},
+			_loadVideo(e) {
+				this.$set(this.ctrl, e.target.dataset.i, 0);
+			}
+		}
+	}
+</script>
+
+<style>
+	/* 在这里引入自定义样式 */
+
+	/* 链接和图片效果 */
+	._a {
+		display: inline;
+		padding: 1.5px 0 1.5px 0;
+		color: #366092;
+		word-break: break-all;
+	}
+
+	._hover {
+		text-decoration: underline;
+		opacity: 0.7;
+	}
+
+	._img {
+		display: inline-block;
+		max-width: 100%;
+		overflow: hidden;
+	}
+
+	/* #ifdef MP-WEIXIN */
+	:host {
+		display: inline;
+	}
+
+	/* #endif */
+
+	/* #ifndef MP-ALIPAY || APP-PLUS */
+	.interlayer {
+		display: inherit;
+		flex-direction: inherit;
+		flex-wrap: inherit;
+		align-content: inherit;
+		align-items: inherit;
+		justify-content: inherit;
+		width: 100%;
+		white-space: inherit;
+	}
+
+	/* #endif */
+
+	._b,
+	._strong {
+		font-weight: bold;
+	}
+
+	/* #ifndef MP-ALIPAY */
+	._blockquote,
+	._div,
+	._p,
+	._ol,
+	._ul,
+	._li {
+		display: block;
+	}
+
+	/* #endif */
+
+	._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;
+	}
+
+	._image {
+		display: block;
+		width: 100%;
+		height: 360px;
+		margin-top: -360px;
+		opacity: 0;
+	}
+
+	._ins {
+		text-decoration: underline;
+	}
+
+	._li {
+		flex: 1;
+		width: 0;
+	}
+
+	._ol-bef {
+		width: 36px;
+		margin-right: 5px;
+		text-align: right;
+	}
+
+	._ul-bef {
+		display: block;
+		margin: 0 12px 0 23px;
+		line-height: normal;
+	}
+
+	._ol-bef,
+	._ul-bef {
+		flex: none;
+		user-select: none;
+	}
+
+	._ul-p1 {
+		display: inline-block;
+		width: 0.3em;
+		height: 0.3em;
+		overflow: hidden;
+		line-height: 0.3em;
+	}
+
+	._ul-p2 {
+		display: inline-block;
+		width: 0.23em;
+		height: 0.23em;
+		border: 0.05em solid black;
+		border-radius: 50%;
+	}
+
+	._q::before {
+		content: '"';
+	}
+
+	._q::after {
+		content: '"';
+	}
+
+	._sub {
+		font-size: smaller;
+		vertical-align: sub;
+	}
+
+	._sup {
+		font-size: smaller;
+		vertical-align: super;
+	}
+
+	/* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */
+	._abbr,
+	._b,
+	._code,
+	._del,
+	._em,
+	._i,
+	._ins,
+	._label,
+	._q,
+	._span,
+	._strong,
+	._sub,
+	._sup {
+		display: inline;
+	}
+
+	/* #endif */
+
+	/* #ifdef MP-WEIXIN || MP-QQ */
+	.__bdo,
+	.__bdi,
+	.__ruby,
+	.__rt {
+		display: inline-block;
+	}
+
+	/* #endif */
+	._video {
+		position: relative;
+		display: inline-block;
+		width: 300px;
+		height: 225px;
+		background-color: black;
+	}
+
+	._video::after {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		margin: -15px 0 0 -15px;
+		content: '';
+		border-color: transparent transparent transparent white;
+		border-style: solid;
+		border-width: 15px 0 15px 30px;
+	}
+</style>

+ 124 - 0
components/m-input.vue

@@ -0,0 +1,124 @@
+<template>
+	<view class="m-input-view">
+		<input :focus="focus" :type="inputType" :value="value" @input="onInput" class="m-input-input" :placeholder="placeholder"
+		 :password="type==='password'&&!showPassword" :maxlength="maxlength" @focus="onFocus" @blur="onBlur" />
+		<!-- 优先显示密码可见按钮 -->
+		<view v-if="clearable&&!displayable&&value.length" class="m-input-icon">
+			<uni-icons color="#666666" type="clear" @click="clear"></uni-icons>
+		</view>
+		<view v-if="displayable" class="m-input-icon">
+			<uni-icons :style="{color:showPassword?'#666666':'#cccccc'}" type="eye" @click="display"></uni-icons>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			/**
+			 * 输入类型
+			 */
+			type: String,
+			/**
+			 * 值
+			 */
+			value: String,
+			/**
+			 * 占位符
+			 */
+			placeholder: String,
+			/**
+			 * 是否显示清除按钮
+			 */
+			clearable: {
+				type: [Boolean, String],
+				default: false
+			},
+			/**
+			 * 是否显示密码可见按钮
+			 */
+			displayable: {
+				type: [Boolean, String],
+				default: false
+			},
+			/**
+			 * 自动获取焦点
+			 */
+			focus: {
+				type: [Boolean, String],
+				default: false
+			},
+			maxlength: {
+				type: Number,
+				default: -1
+			}
+		},
+		model: {
+			prop: 'value',
+			event: 'input'
+		},
+		data() {
+			return {
+				/**
+				 * 显示密码明文
+				 */
+				showPassword: false,
+				/**
+				 * 是否获取焦点
+				 */
+				isFocus: false
+			}
+		},
+		computed: {
+			inputType() {
+				const type = this.type
+				return type === 'password' ? 'text' : type
+			}
+		},
+		methods: {
+			clear() {
+				this.$emit('input', '')
+			},
+			display() {
+				this.showPassword = !this.showPassword
+			},
+			onFocus() {
+				this.isFocus = true
+			},
+			onBlur() {
+				this.$nextTick(() => {
+					this.isFocus = false
+				})
+			},
+			onInput(e) {
+				this.$emit('input', e.detail.value)
+			}
+		}
+	}
+</script>
+
+<style>
+	.m-input-view {
+		display: inline-flex;
+		flex-direction: row;
+		align-items: center;
+		/* width: 100%; */
+		flex: 1;
+		padding: 0 10px;
+	}
+
+	.m-input-input {
+		flex: 1;
+		width: 100%;
+		min-height: 100%;
+		line-height: inherit;
+		background-color: rgba(0, 0, 0, 0);
+	}
+
+	.m-input-icon {
+		width: 20px;
+		font-size: 20px;
+		line-height: 20px;
+		color: #666666;
+	}
+</style>

+ 40 - 0
components/uni-cart-fix/uni-cart-fix.vue

@@ -0,0 +1,40 @@
+<template>
+	 <view class="cart-fix" @click="toCart">
+	 	<u-image width="70rpx" height="70rpx" src="/static/cart.png"></u-image>
+	 	<u-badge absolute :offset="[15,10]" type="error" :count="count"></u-badge>
+	 </view>
+</template>
+	<script>
+		export default {
+			computed: {
+				count() {
+					let arr = this.$store.state.vuex_cartList
+					let count = 0
+					arr.map(item=>{
+						count = count + item.shoppingCartGoodsList.length
+					})
+					return count
+				},
+			},
+			methods: {
+				toCart() {
+					uni.navigateTo({
+						url:"/pages/cart/cart"
+					})
+				}
+			},
+		}
+	</script>
+
+<style lang="scss">
+	.cart-fix{
+		position: fixed;
+		z-index: 100000;
+		background: rgba($color: #ffffff, $alpha: 0.7);
+		padding: 10upx;
+		border-radius: 100%;
+		bottom: 40%;
+		right: 2%;
+		box-shadow: 3upx 3upx 8upx #ccc;
+	}
+</style>

+ 121 - 0
components/uni-coods/uni-coods.vue

@@ -0,0 +1,121 @@
+<template>
+	<view class="goods-list">
+		<view class="goods-item" v-for="item in list" :key="item.id" @click="toDetail(item)">
+			<view class="goods-imgs">
+				<view v-if="item.inventoryQty == 0" class="goods-staus">已售罄</view>
+				<u-lazy-load img-mode="scaleToFill" :height="imgHeight" :image="item.homeImage" :loading-img="loadingImg" :error-img="errorImg"></u-lazy-load>
+			</view>
+			<view class="name ellipsis-two">{{item.name}}</view>
+			<view class="price">
+				<view>
+					<text>{{item.sellGold}}</text>
+					<u-image mode="scaleToFill" width="40rpx" height="40rpx" src="/static/ledou.png"></u-image>
+				</view>
+				<view @click.stop="addCart(item)" v-if="item.inventoryQty > 0">
+					<u-image mode="scaleToFill" width="45rpx" height="45rpx" src="/static/addCart.png"></u-image>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			imgHeight: {
+				type: Number,
+				default: 250
+			},
+			list:{
+				type: Array,
+				default: function(){
+					return []
+				}
+			}
+		},
+		data() {
+			return {
+				loadingImg: '/static/loading.gif',
+				errorImg: '/static/imgError.png',
+				timer: null,
+				finger: {x:0,y:0},
+				linePos: null
+			}
+		},
+		methods: {
+			// 加入购物车
+			addCart(item) {
+				uni.$emit("addCart",{goodsNo:item.goodsNo,buyQty:1})
+			},
+			// 商品详情
+			toDetail(item){
+				uni.navigateTo({
+					url:"/pages/goods/goodsDetail?id="+item.id
+				})
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+.goods-list{
+	display: flex;
+	flex-wrap: wrap;
+	justify-content: space-between;
+	padding-top: 20upx;
+	.goods-item{
+		background: #FFFFFF;
+		width: 48%;
+		margin-bottom: 5upx;
+		.goods-imgs{
+			position: relative;
+			border-radius: 28upx 28upx 0 0;
+			overflow: hidden;
+			.goods-staus{
+				width: 100%;
+				height: 100%;
+				position: absolute;
+				z-index: 100;
+				border-radius: 15rpx 15rpx 0 0;
+				background: rgba($color: #000000, $alpha: 0.4);
+				text-align: center;
+				line-height: 250rpx;
+				left: 0;
+				top: 0;
+				color: #eee;
+				font-size: 30upx;
+				font-weight: bold;
+			}
+		}
+		.name{
+			padding: 10upx 20upx 0;
+			font-weight: bold;
+		}
+		.price{
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 15upx 20upx;
+			text{
+				color: #d42a26;
+				font-size: 36upx;
+				margin-right: 6upx;
+				font-weight: bold;
+			}
+			>view{
+				&:first-child{
+					font-size: 22upx;
+					display: flex;
+					align-items: center;
+				}
+				&:last-child{
+					&:active{
+						opacity: 0.5;
+					}
+				}
+			}
+		}
+	}
+}
+
+</style>

+ 96 - 0
components/uni-icons/icons.js

@@ -0,0 +1,96 @@
+export default {
+	'contact': '\ue100',
+	'person': '\ue101',
+	'personadd': '\ue102',
+	'contact-filled': '\ue130',
+	'person-filled': '\ue131',
+	'personadd-filled': '\ue132',
+	'phone': '\ue200',
+	'email': '\ue201',
+	'chatbubble': '\ue202',
+	'chatboxes': '\ue203',
+	'phone-filled': '\ue230',
+	'email-filled': '\ue231',
+	'chatbubble-filled': '\ue232',
+	'chatboxes-filled': '\ue233',
+	'weibo': '\ue260',
+	'weixin': '\ue261',
+	'pengyouquan': '\ue262',
+	'chat': '\ue263',
+	'qq': '\ue264',
+	'videocam': '\ue300',
+	'camera': '\ue301',
+	'mic': '\ue302',
+	'location': '\ue303',
+	'mic-filled': '\ue332',
+	'speech': '\ue332',
+	'location-filled': '\ue333',
+	'micoff': '\ue360',
+	'image': '\ue363',
+	'map': '\ue364',
+	'compose': '\ue400',
+	'trash': '\ue401',
+	'upload': '\ue402',
+	'download': '\ue403',
+	'close': '\ue404',
+	'redo': '\ue405',
+	'undo': '\ue406',
+	'refresh': '\ue407',
+	'star': '\ue408',
+	'plus': '\ue409',
+	'minus': '\ue410',
+	'circle': '\ue411',
+	'checkbox': '\ue411',
+	'close-filled': '\ue434',
+	'clear': '\ue434',
+	'refresh-filled': '\ue437',
+	'star-filled': '\ue438',
+	'plus-filled': '\ue439',
+	'minus-filled': '\ue440',
+	'circle-filled': '\ue441',
+	'checkbox-filled': '\ue442',
+	'closeempty': '\ue460',
+	'refreshempty': '\ue461',
+	'reload': '\ue462',
+	'starhalf': '\ue463',
+	'spinner': '\ue464',
+	'spinner-cycle': '\ue465',
+	'search': '\ue466',
+	'plusempty': '\ue468',
+	'forward': '\ue470',
+	'back': '\ue471',
+	'left-nav': '\ue471',
+	'checkmarkempty': '\ue472',
+	'home': '\ue500',
+	'navigate': '\ue501',
+	'gear': '\ue502',
+	'paperplane': '\ue503',
+	'info': '\ue504',
+	'help': '\ue505',
+	'locked': '\ue506',
+	'more': '\ue507',
+	'flag': '\ue508',
+	'home-filled': '\ue530',
+	'gear-filled': '\ue532',
+	'info-filled': '\ue534',
+	'help-filled': '\ue535',
+	'more-filled': '\ue537',
+	'settings': '\ue560',
+	'list': '\ue562',
+	'bars': '\ue563',
+	'loop': '\ue565',
+	'paperclip': '\ue567',
+	'eye': '\ue568',
+	'arrowup': '\ue580',
+	'arrowdown': '\ue581',
+	'arrowleft': '\ue582',
+	'arrowright': '\ue583',
+	'arrowthinup': '\ue584',
+	'arrowthindown': '\ue585',
+	'arrowthinleft': '\ue586',
+	'arrowthinright': '\ue587',
+	'pulldown': '\ue588',
+	'closefill': '\ue589',
+	'sound': '\ue590',
+	'scan': '\ue612'
+}

File diff suppressed because it is too large
+ 10 - 0
components/uni-icons/uni-icons.vue


+ 127 - 0
components/uni-popup/image-txt-popup.vue

@@ -0,0 +1,127 @@
+<template>
+	<view class="image-txt-wrap">
+		<image :src="captchaBase" class="image" @click="getCode"></image>
+		<text class="tip-txt">* 点击图片即可切换验证码</text>
+		<input v-model="captcha" @input="inpChange" class="item-inp" name="captcha" type="text" maxlength="4" placeholder="请输入上方的图形验证码" />
+		<button :type="btnType" size="mini" class="popup-button-yz" @click="verify">验证</button>
+	</view>
+</template>
+
+<script>
+	import { getCaptcha, memberCaptcha } from '@/api/user.js'
+	export default{
+		name: 'ImageTxtPopup',
+		props: {
+			changeImg: {
+				type: Boolean,
+				default: false
+			},
+			//  applyImageTxt申请试用   forgetImageTxt忘记密码
+			type: {
+				type: String,
+				default: ''
+			},
+		},
+		watch: {
+			changeImg(newV, oldV){
+				if(newV === true){
+					this.getCode()
+				}
+			}
+		},
+		data(){
+			return{
+				captchaBase: '',  //  图片验证码base64
+				captcha: '',  //  图文验证码
+				nowRandomCode: '',  // 当前随机码
+				btnType: 'default',  //  验证按钮type
+			}
+		},
+		created() {
+			this.getCode()
+		},
+		methods: {
+			//  获取验证码
+			getCode(){
+			  const _this = this
+			  //  6位随机数
+			  this.nowRandomCode = Math.random().toString().slice(-6)
+			  let portAddress = ''
+			  if(_this.type == 'applyImageTxt'){  //  申请试用  获取图片验证码
+				  portAddress = getCaptcha
+			  }else if(_this.type == 'forgetImageTxt'){  //  忘记密码  获取图片验证码
+				  portAddress = memberCaptcha
+			  }
+			  portAddress(this.nowRandomCode).then(res => {
+			    //  获取图片路径
+			    _this.captchaBase = 'data:image/png;base64,' + res.data
+			    // 显示图文验证码弹框
+			    _this.captcha = ''  //  清空图文输入内容
+			  })
+			},
+			//  验证码输入监听
+			inpChange(){
+				if(this.captcha.length == 4){
+					this.btnType = 'primary'
+				}else{
+					this.btnType = 'default'
+				}
+			},
+			//  验证
+			verify(){
+				if(!this.captcha){
+					uni.showToast({icon: 'none',title: '请输入上方的图形验证码'})
+					return
+				}else{
+					if(this.captcha.length != 4){
+						uni.showToast({icon: 'none',title: '请输入4位图形验证码'})
+						return
+					}
+					this.$emit('captchaImg', this.captcha, this.nowRandomCode)
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.image-txt-wrap{
+		line-height: 1.8;
+		.tip-txt{
+			display: block;
+			font-size: 24upx;
+			color: #999;
+			margin-bottom: 40upx;
+			text-align: center;
+		}
+		.item-inp{
+			background-color: #fff;
+			border: 2upx solid #dcdee2;
+			border-radius: 8upx;
+			color: #666;
+			display: inline-block;
+			font-size: 24upx;
+			height: 64upx;
+			line-height: 1.5;
+			padding: 8upx 14upx;
+			width: 100%;
+			box-sizing: border-box;
+		}
+		.image{
+			max-width: 100%;
+			width: 400upx;
+			height: 160upx;
+			margin: 30upx auto 20upx;
+			display: block;
+			img{
+				max-width: 100%;
+			}
+		}
+		.popup-button-yz{
+			display: block;
+			margin: 40upx auto 50upx;
+			width: 30%;
+			line-height: 2.8;
+		}
+	}
+</style>

+ 22 - 0
components/uni-popup/message.js

@@ -0,0 +1,22 @@
+export default {
+	created() {
+		if (this.type === 'message') {
+			// 不显示遮罩
+			this.maskShow = false 
+			// 获取子组件对象
+			this.childrenMsg = null
+		}
+	},
+	methods: {
+		customOpen() {
+			if (this.childrenMsg) {
+				this.childrenMsg.open()
+			}
+		},
+		customClose() {
+			if (this.childrenMsg) {
+				this.childrenMsg.close()
+			}
+		}
+	}
+}

+ 96 - 0
components/uni-popup/popup-con.vue

@@ -0,0 +1,96 @@
+<template>
+	<view class="popup-con">
+		<text class="popup-header">{{title}}</text>
+		<view class="popup-content">
+			<image-txt-popup :type="type" :changeImg="changeImg" @captchaImg="captchaImg"></image-txt-popup>
+		</view>
+		<view class="popup-footer">
+			<text class="popup-button" v-for="(item, index) in popBtn" :key="index" :style="{ color: item.color }">{{item.name}}</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import imageTxtPopup from '@/components/uni-popup/image-txt-popup.vue'
+	export default{
+		name: 'PopupCon',
+		props: {
+			//  applyImageTxt申请试用   forgetImageTxt忘记密码
+			type: {
+				type: String,
+				default: ''
+			},
+			title: {
+				type: String,
+				default: '标题'
+			},
+			//  操作按钮
+			popBtn: {
+				type: Array,
+				default: function (){
+				  return []
+				}
+			},
+			changeImg: {
+				type: Boolean,
+				default: false
+			}
+		},
+		components: {
+			imageTxtPopup
+		},
+		data(){
+			return{}
+		},
+		methods: {
+			captchaImg(captcha, nowRandomCode){
+				this.$emit('captchaImg', captcha, nowRandomCode)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.popup-con{
+		box-sizing: border-box;
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		box-shadow: 0 8upx 24upx rgba(0,0,0,.15);
+		display: flex;
+		flex-direction: column;
+		padding: 30upx;
+		width: 600upx;
+		background-color: #fff;
+		border-radius: 20upx;
+		.popup-header{
+			margin-bottom: 20upx;
+			text-align: center;
+			font-weight: 700;
+			font-size: 32upx;
+			color: #333;
+			line-height: 1.8;
+		}
+		.popup-content{
+			font-size: 28upx;
+			color: #666;
+		}
+		.popup-footer{
+			display: flex;
+			flex-direction: row;
+			margin-top: 40upx;
+			.popup-button{
+				flex: 1;
+				text-align: center;
+				font-size: 28upx;
+				color: #3b4144;
+				line-height: 1.8;
+			}
+			.popup-confirm{
+				color: #57a3f3;
+			}
+		}
+	}
+	
+</style>

+ 25 - 0
components/uni-popup/popup.js

@@ -0,0 +1,25 @@
+import message from './message.js';
+// 定义 type 类型:弹出类型:top/bottom/center
+const config = {
+	// 顶部弹出
+	top:'top',
+	// 底部弹出
+	bottom:'bottom',
+	// 居中弹出
+	center:'center',
+	// 消息提示
+	message:'top',
+	// 对话框
+	dialog:'center',
+	// 分享
+	share:'bottom',
+}
+
+export default {
+	data(){
+		return {
+			config:config
+		}
+	},
+	mixins: [message],
+}

+ 243 - 0
components/uni-popup/uni-popup-dialog.vue

@@ -0,0 +1,243 @@
+<template>
+	<view class="uni-popup-dialog">
+		<view class="uni-dialog-title">
+			<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{title}}</text>
+		</view>
+		<view class="uni-dialog-content">
+			<text class="uni-dialog-content-text" v-if="mode === 'base'">{{content}}</text>
+			<input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus" >
+		</view>
+		<view class="uni-dialog-button-group">
+			<view class="uni-dialog-button" @click="close">
+				<text class="uni-dialog-button-text">取消</text>
+			</view>
+			<view class="uni-dialog-button uni-border-left" @click="onOk">
+				<text class="uni-dialog-button-text uni-button-color">确定</text>
+			</view>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	/**
+	 * PopUp 弹出层-对话框样式
+	 * @description 弹出层-对话框样式
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} value input 模式下的默认值
+	 * @property {String} placeholder input 模式下输入提示
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} mode = [base|input] 模式、
+	 * 	@value base 基础对话框
+	 * 	@value input 可输入对话框
+	 * @property {String} content 对话框内容
+	 * @property {Boolean} beforeClose 是否拦截取消事件
+	 * @event {Function} confirm 点击确认按钮触发
+	 * @event {Function} close 点击取消按钮触发
+	 */
+
+	export default {
+		name: "uniPopupDialog",
+		props: {
+			value: {
+				type: [String, Number],
+				default: ''
+			},
+			placeholder: {
+				type: [String, Number],
+				default: '请输入内容'
+			},
+			/**
+			 * 对话框主题 success/warning/info/error	  默认 success
+			 */
+			type: {
+				type: String,
+				default: 'error'
+			},
+			/**
+			 * 对话框模式 base/input
+			 */
+			mode: {
+				type: String,
+				default: 'base'
+			},
+			/**
+			 * 对话框标题
+			 */
+			title: {
+				type: String,
+				default: '提示'
+			},
+			/**
+			 * 对话框内容
+			 */
+			content: {
+				type: String,
+				default: ''
+			},
+			/**
+			 * 拦截取消事件 ,如果拦截取消事件,必须监听close事件,执行 done()
+			 */
+			beforeClose: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				dialogType: 'error',
+				focus: false,
+				val: ""
+			}
+		},
+		inject: ['popup'],
+		watch: {
+			type(val) {
+				this.dialogType = val
+			},
+			mode(val) {
+				if (val === 'input') {
+					this.dialogType = 'info'
+				}
+			},
+			value(val) {
+				this.val = val
+			}
+		},
+		created() {
+			// 对话框遮罩不可点击
+			this.popup.mkclick = false
+			if (this.mode === 'input') {
+				this.dialogType = 'info'
+				this.val = this.value
+			} else {
+				this.dialogType = this.type
+			}
+		},
+		mounted() {
+			this.focus = true
+		},
+		methods: {
+			/**
+			 * 点击确认按钮
+			 */
+			onOk() {
+				this.$emit('confirm', () => {
+					this.popup.close()
+					if (this.mode === 'input') this.val = this.value
+				}, this.mode === 'input' ? this.val : '')
+			},
+			/**
+			 * 点击取消按钮
+			 */
+			close() {
+				if (this.beforeClose) {
+					this.$emit('close', () => {
+						this.popup.close()
+					})
+					return
+				}
+				this.popup.close()
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.uni-popup-dialog {
+		width: 300px;
+		border-radius: 15px;
+		background-color: #fff;
+	}
+
+	.uni-dialog-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		padding-top: 15px;
+		padding-bottom: 5px;
+	}
+
+	.uni-dialog-title-text {
+		font-size: 16px;
+		font-weight: 500;
+	}
+
+	.uni-dialog-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		padding: 5px 15px 15px 15px;
+	}
+
+	.uni-dialog-content-text {
+		font-size: 14px;
+		color: #6e6e6e;
+	}
+
+	.uni-dialog-button-group {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		border-top-color: #f5f5f5;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-dialog-button {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+
+		flex: 1;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 45px;
+	}
+
+	.uni-border-left {
+		border-left-color: #f0f0f0;
+		border-left-style: solid;
+		border-left-width: 1px;
+	}
+
+	.uni-dialog-button-text {
+		font-size: 14px;
+	}
+
+	.uni-button-color {
+		color: $uni-color-primary;
+	}
+
+	.uni-dialog-input {
+		flex: 1;
+		font-size: 14px;
+	}
+
+	.uni-popup__success {
+		color: $uni-color-success;
+	}
+
+	.uni-popup__warn {
+		color: $uni-color-warning;
+	}
+
+	.uni-popup__error {
+		color: $uni-color-error;
+	}
+
+	.uni-popup__info {
+		color: #909399;
+	}
+</style>

+ 116 - 0
components/uni-popup/uni-popup-message.vue

@@ -0,0 +1,116 @@
+<template>
+	<view class="uni-popup-message" :class="'uni-popup__'+[type]">
+		<text class="uni-popup-message-text" :class="'uni-popup__'+[type]+'-text'">{{message}}</text>
+	</view>
+</template>
+
+<script>
+	
+	/**
+	 * PopUp 弹出层-消息提示
+	 * @description 弹出层-消息提示
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} message 消息提示文字
+	 * @property {String} duration 显示时间,设置为 0 则不会自动关闭
+	 */
+	
+	export default {
+		name: 'UniPopupMessage',
+		props: {
+			/**
+			 * 主题 success/warning/info/error	  默认 success
+			 */
+			type: {
+				type: String,
+				default: 'success'
+			},
+			/**
+			 * 消息文字
+			 */
+			message: {
+				type: String,
+				default: ''
+			},
+			/**
+			 * 显示时间,设置为 0 则不会自动关闭
+			 */
+			duration: {
+				type: Number,
+				default: 3000
+			}
+		},
+		inject: ['popup'],
+		data() {
+			return {}
+		},
+		created() {
+			this.popup.childrenMsg = this
+		},
+		methods: {
+			open() {
+				if (this.duration === 0) return
+				clearTimeout(this.popuptimer)
+				this.popuptimer = setTimeout(() => {
+					this.popup.close()
+				}, this.duration)
+			},
+			close() {
+				clearTimeout(this.popuptimer)
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-popup-message {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		background-color: #e1f3d8;
+		padding: 10px 15px;
+		border-color: #eee;
+		border-style: solid;
+		border-width: 1px;
+	}
+	.uni-popup-message-text {
+		font-size: 14px;
+		padding: 0;
+	}
+
+	.uni-popup__success {
+		background-color: #e1f3d8;
+	}
+
+	.uni-popup__success-text {
+		color: #67C23A;
+	}
+
+	.uni-popup__warn {
+		background-color: #faecd8;
+	}
+
+	.uni-popup__warn-text {
+		color: #E6A23C;
+	}
+
+	.uni-popup__error {
+		background-color: #fde2e2;
+	}
+
+	.uni-popup__error-text {
+		color: #F56C6C;
+	}
+
+	.uni-popup__info {
+		background-color: #F2F6FC;
+	}
+
+	.uni-popup__info-text {
+		color: #909399;
+	}
+</style>

+ 165 - 0
components/uni-popup/uni-popup-share.vue

@@ -0,0 +1,165 @@
+<template>
+	<view class="uni-popup-share">
+		<view class="uni-share-title"><text class="uni-share-title-text">{{title}}</text></view>
+		<view class="uni-share-content">
+			<view class="uni-share-content-box">
+				<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
+					<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
+					<text class="uni-share-text">{{item.text}}</text>
+				</view>
+
+			</view>
+		</view>
+		<view class="uni-share-button-box">
+			<button class="uni-share-button" @click="close">取消</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'UniPopupShare',
+		props: {
+			title: {
+				type: String,
+				default: '分享到'
+			}
+		},
+		inject: ['popup'],
+		data() {
+			return {
+				bottomData: [{
+						text: '微信',
+						icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-2.png',
+						name: 'wx'
+					},
+					{
+						text: '支付宝',
+						icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-8.png',
+						name: 'wx'
+					},
+					{
+						text: 'QQ',
+						icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/gird-3.png',
+						name: 'qq'
+					},
+					{
+						text: '新浪',
+						icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-1.png',
+						name: 'sina'
+					},
+					{
+						text: '百度',
+						icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-7.png',
+						name: 'copy'
+					},
+					{
+						text: '其他',
+						icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-5.png',
+						name: 'more'
+					}
+				]
+			}
+		},
+		created() {},
+		methods: {
+			/**
+			 * 选择内容
+			 */
+			select(item, index) {
+				this.$emit('select', {
+					item,
+					index
+				}, () => {
+					this.popup.close()
+				})
+			},
+			/**
+			 * 关闭窗口
+			 */
+			close() {
+				this.popup.close()
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-popup-share {
+		background-color: #fff;
+	}
+	.uni-share-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		height: 40px;
+	}
+	.uni-share-title-text {
+		font-size: 14px;
+		color: #666;
+	}
+	.uni-share-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		padding-top: 10px;
+	}
+	
+	.uni-share-content-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		flex-wrap: wrap;
+		width: 360px;
+	}
+	
+	.uni-share-content-item {
+		width: 90px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		padding: 10px 0;
+		align-items: center;
+	}
+	
+	.uni-share-content-item:active {
+		background-color: #f5f5f5;
+	}
+	
+	.uni-share-image {
+		width: 30px;
+		height: 30px;
+	}
+	
+	.uni-share-text {
+		margin-top: 10px;
+		font-size: 14px;
+		color: #3B4144;
+	}
+	
+	.uni-share-button-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		padding: 10px 15px;
+	}
+	
+	.uni-share-button {
+		flex: 1;
+		border-radius: 50px;
+		color: #666;
+		font-size: 16px;
+	}
+	
+	.uni-share-button::after {
+		border-radius: 50px;
+	}
+</style>

+ 294 - 0
components/uni-popup/uni-popup.vue

@@ -0,0 +1,294 @@
+<template>
+	<view v-if="showPopup" class="uni-popup" :class="[popupstyle]" @touchmove.stop.prevent="clear">
+		<uni-transition v-if="maskShow" :mode-class="['fade']" :styles="maskClass" :duration="duration" :show="showTrans"
+		 @click="onTap" />
+		<uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap">
+			<view class="uni-popup__wrapper-box" @click.stop="clear">
+				<slot />
+			</view>
+		</uni-transition>
+	</view>
+</template>
+
+<script>
+	import uniTransition from '../uni-transition/uni-transition.vue'
+	import popup from './popup.js'
+	/**
+	 * PopUp 弹出层
+	 * @description 弹出层组件,为了解决遮罩弹层的问题
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [top|center|bottom] 弹出方式
+	 * 	@value top 顶部弹出
+	 * 	@value center 中间弹出
+	 * 	@value bottom 底部弹出
+	 * 	@value message 消息提示
+	 * 	@value dialog 对话框
+	 * 	@value share 底部分享示例
+	 * @property {Boolean} animation = [ture|false] 是否开启动画
+	 * @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
+	 * @event {Function} change 打开关闭弹窗触发,e={show: false}
+	 */
+
+	export default {
+		name: 'UniPopup',
+		components: {
+			uniTransition
+		},
+		props: {
+			// 开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+			// message: 消息提示 ; dialog : 对话框
+			type: {
+				type: String,
+				default: 'center'
+			},
+			// maskClick
+			maskClick: {
+				type: Boolean,
+				default: true
+			}
+		},
+		provide() {
+			return {
+				popup: this
+			}
+		},
+		mixins: [popup],
+		watch: {
+			/**
+			 * 监听type类型
+			 */
+			type: {
+				handler: function(newVal) {
+					this[this.config[newVal]]()
+				},
+				immediate: true
+			},
+			/**
+			 * 监听遮罩是否可点击
+			 * @param {Object} val
+			 */
+			maskClick(val) {
+				this.mkclick = val
+			}
+		},
+		data() {
+			return {
+				duration: 300,
+				ani: [],
+				showPopup: false,
+				showTrans: false,
+				maskClass: {
+					'position': 'fixed',
+					'bottom': 0,
+					'top': 0,
+					'left': 0,
+					'right': 0,
+					'backgroundColor': 'rgba(0, 0, 0, 0.4)'
+				},
+				transClass: {
+					'position': 'fixed',
+					'left': 0,
+					'right': 0,
+				},
+				maskShow: true,
+				mkclick: true,
+				popupstyle: 'top'
+			}
+		},
+		created() {
+			this.mkclick = this.maskClick
+			if (this.animation) {
+				this.duration = 300
+			} else {
+				this.duration = 0
+			}
+		},
+		methods: {
+			clear(e) {
+				// TODO nvue 取消冒泡
+				e.stopPropagation()
+			},
+			open() {
+				this.showPopup = true
+				this.$nextTick(() => {
+					new Promise(resolve => {
+						clearTimeout(this.timer)
+						this.timer = setTimeout(() => {
+							this.showTrans = true
+							// fixed by mehaotian 兼容 app 端
+							this.$nextTick(() => {
+								resolve();
+							})
+						}, 50);
+					}).then(res => {
+						// 自定义打开事件
+						clearTimeout(this.msgtimer)
+						this.msgtimer = setTimeout(() => {
+							this.customOpen && this.customOpen()
+						}, 100)
+						this.$emit('change', {
+							show: true,
+							type: this.type
+						})
+					})
+				})
+			},
+			close(type) {
+				this.showTrans = false
+				this.$nextTick(() => {
+					this.$emit('change', {
+						show: false,
+						type: this.type
+					})
+					clearTimeout(this.timer)
+					// 自定义关闭事件
+					this.customOpen && this.customClose()
+					this.timer = setTimeout(() => {
+						this.showPopup = false
+					}, 300)
+				})
+			},
+			onTap() {
+				if (!this.mkclick) return
+				this.close()
+			},
+			/**
+			 * 顶部弹出样式处理
+			 */
+			top() {
+				this.popupstyle = 'top'
+				this.ani = ['slide-top']
+				this.transClass = {
+					'position': 'fixed',
+					'left': 0,
+					'right': 0,
+				}
+			},
+			/**
+			 * 底部弹出样式处理
+			 */
+			bottom() {
+				this.popupstyle = 'bottom'
+				this.ani = ['slide-bottom']
+				this.transClass = {
+					'position': 'fixed',
+					'left': 0,
+					'right': 0,
+					'bottom': 0
+				}
+			},
+			/**
+			 * 中间弹出样式处理
+			 */
+			center() {
+				this.popupstyle = 'center'
+				this.ani = ['zoom-out', 'fade']
+				this.transClass = {
+					'position': 'fixed',
+					/* #ifndef APP-NVUE */
+					'display': 'flex',
+					'flexDirection': 'column',
+					/* #endif */
+					'bottom': 0,
+					'left': 0,
+					'right': 0,
+					'top': 0,
+					'justifyContent': 'center',
+					'alignItems': 'center'
+				}
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-popup {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+		/* #endif */
+	}
+
+	.uni-popup__mask {
+		position: absolute;
+		top: 0;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		background-color: $uni-bg-color-mask;
+		opacity: 0;
+	}
+
+	.mask-ani {
+		transition-property: opacity;
+		transition-duration: 0.2s;
+	}
+
+	.uni-top-mask {
+		opacity: 1;
+	}
+
+	.uni-bottom-mask {
+		opacity: 1;
+	}
+
+	.uni-center-mask {
+		opacity: 1;
+	}
+
+	.uni-popup__wrapper {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: absolute;
+	}
+
+	.top {
+		/* #ifdef H5 */
+		top: var(--window-top);
+		/* #endif */
+		/* #ifndef H5 */
+		top: 0;
+		/* #endif */
+	}
+
+	.bottom {
+		bottom: 0;
+	}
+
+	.uni-popup__wrapper-box {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: relative;
+		/* iphonex 等安全区设置,底部安全区适配 */
+		/* #ifndef APP-NVUE */
+		padding-bottom: constant(safe-area-inset-bottom);
+		padding-bottom: env(safe-area-inset-bottom);
+		/* #endif */
+	}
+
+	.content-ani {
+		// transition: transform 0.3s;
+		transition-property: transform, opacity;
+		transition-duration: 0.2s;
+	}
+
+
+	.uni-top-content {
+		transform: translateY(0);
+	}
+
+	.uni-bottom-content {
+		transform: translateY(0);
+	}
+
+	.uni-center-content {
+		transform: scale(1);
+		opacity: 1;
+	}
+</style>

+ 279 - 0
components/uni-transition/uni-transition.vue

@@ -0,0 +1,279 @@
+<template>
+	<view v-if="isShow" ref="ani" class="uni-transition" :class="[ani.in]" :style="'transform:' +transform+';'+stylesObject"
+	 @click="change">
+		 <slot></slot>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation');
+	// #endif
+	/**
+	 * Transition 过渡动画
+	 * @description 简单过渡动画组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=985
+	 * @property {Boolean} show = [false|true] 控制组件显示或隐藏
+     * @property {Array} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
+     *  @value fade 渐隐渐出过渡
+     *  @value slide-top 由上至下过渡
+     *  @value slide-right 由右至左过渡
+     *  @value slide-bottom 由下至上过渡
+     *  @value slide-left 由左至右过渡
+     *  @value zoom-in 由小到大过渡
+     *  @value zoom-out 由大到小过渡
+	 * @property {Number} duration 过渡动画持续时间
+	 * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
+	 */
+	export default {
+		name: 'uniTransition',
+		props: {
+			show: {
+				type: Boolean,
+				default: false
+			},
+			modeClass: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			duration: {
+				type: Number,
+				default: 300
+			},
+			styles: {
+				type: Object,
+				default () {
+					return {}
+				}
+			}
+		},
+		data() {
+			return {
+				isShow: false,
+				transform: '',
+				ani: { in: '',
+					active: ''
+				}
+			};
+		},
+		watch: {
+			show: {
+				handler(newVal) {
+					if (newVal) {
+						this.open()
+					} else {
+						this.close()
+					}
+				},
+				immediate: true
+			}
+		},
+		computed: {
+			stylesObject() {
+				let styles = {
+					...this.styles,
+					'transition-duration': this.duration / 1000 + 's'
+				}
+				let transfrom = ''
+				for (let i in styles) {
+					let line = this.toLine(i)
+					transfrom += line + ':' + styles[i] + ';'
+				}
+				return transfrom
+			}
+		},
+		created() {
+			// this.timer = null
+			// this.nextTick = (time = 50) => new Promise(resolve => {
+			// 	clearTimeout(this.timer)
+			// 	this.timer = setTimeout(resolve, time)
+			// 	return this.timer
+			// });
+		},
+		methods: {
+			change() {
+				this.$emit('click', {
+					detail: this.isShow
+				})
+			},
+			open() {
+				clearTimeout(this.timer)
+				this.isShow = true
+				this.transform = ''
+				this.ani.in = ''
+				for (let i in this.getTranfrom(false)) {
+					if (i === 'opacity') {
+						this.ani.in = 'fade-in'
+					} else {
+						this.transform += `${this.getTranfrom(false)[i]} `
+					}
+				}
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this._animation(true)
+					}, 50)
+				})
+
+			},
+			close(type) {
+				clearTimeout(this.timer)
+				this._animation(false)
+			},
+			_animation(type) {
+				let styles = this.getTranfrom(type)
+				// #ifdef APP-NVUE
+				if(!this.$refs['ani']) return
+				animation.transition(this.$refs['ani'].ref, {
+					styles,
+					duration: this.duration, //ms
+					timingFunction: 'ease',
+					needLayout: false,
+					delay: 0 //ms
+				}, () => {
+					if (!type) {
+						this.isShow = false
+					}
+					this.$emit('change', {
+						detail: this.isShow
+					})
+				})
+				// #endif
+				// #ifndef APP-NVUE
+				this.transform = ''
+				for (let i in styles) {
+					if (i === 'opacity') {
+						this.ani.in = `fade-${type?'out':'in'}`
+					} else {
+						this.transform += `${styles[i]} `
+					}
+				}
+				this.timer = setTimeout(() => {
+					if (!type) {
+						this.isShow = false
+					}
+					this.$emit('change', {
+						detail: this.isShow
+					})
+
+				}, this.duration)
+				// #endif
+
+			},
+			getTranfrom(type) {
+				let styles = {
+					transform: ''
+				}
+				this.modeClass.forEach((mode) => {
+					switch (mode) {
+						case 'fade':
+							styles.opacity = type ? 1 : 0
+							break;
+						case 'slide-top':
+							styles.transform += `translateY(${type?'0':'-100%'}) `
+							break;
+						case 'slide-right':
+							styles.transform += `translateX(${type?'0':'100%'}) `
+							break;
+						case 'slide-bottom':
+							styles.transform += `translateY(${type?'0':'100%'}) `
+							break;
+						case 'slide-left':
+							styles.transform += `translateX(${type?'0':'-100%'}) `
+							break;
+						case 'zoom-in':
+							styles.transform += `scale(${type?1:0.8}) `
+							break;
+						case 'zoom-out':
+							styles.transform += `scale(${type?1:1.2}) `
+							break;
+					}
+				})
+				return styles
+			},
+			_modeClassArr(type) {
+				let mode = this.modeClass
+				if (typeof(mode) !== "string") {
+					let modestr = ''
+					mode.forEach((item) => {
+						modestr += (item + '-' + type + ',')
+					})
+					return modestr.substr(0, modestr.length - 1)
+				} else {
+					return mode + '-' + type
+				}
+			},
+			// getEl(el) {
+			// 	console.log(el || el.ref || null);
+			// 	return el || el.ref || null
+			// },
+			toLine(name) {
+				return name.replace(/([A-Z])/g, "-$1").toLowerCase();
+			}
+		}
+	}
+</script>
+
+<style>
+	.uni-transition {
+		transition-timing-function: ease;
+		transition-duration: 0.3s;
+		transition-property: transform, opacity;
+	}
+
+	.fade-in {
+		opacity: 0;
+	}
+
+	.fade-active {
+		opacity: 1;
+	}
+
+	.slide-top-in {
+		/* transition-property: transform, opacity; */
+		transform: translateY(-100%);
+	}
+
+	.slide-top-active {
+		transform: translateY(0);
+		/* opacity: 1; */
+	}
+
+	.slide-right-in {
+		transform: translateX(100%);
+	}
+
+	.slide-right-active {
+		transform: translateX(0);
+	}
+
+	.slide-bottom-in {
+		transform: translateY(100%);
+	}
+
+	.slide-bottom-active {
+		transform: translateY(0);
+	}
+
+	.slide-left-in {
+		transform: translateX(-100%);
+	}
+
+	.slide-left-active {
+		transform: translateX(0);
+		opacity: 1;
+	}
+
+	.zoom-in-in {
+		transform: scale(0.8);
+	}
+
+	.zoom-out-active {
+		transform: scale(1);
+	}
+
+	.zoom-out-in {
+		transform: scale(1.2);
+	}
+</style>

+ 44 - 0
js_sdk/dc-market/market.js

@@ -0,0 +1,44 @@
+
+/**
+ * 打开应用市场
+ * @param {Object} options 应用市场参数
+ *     ios: appstore中应用的id标识,应用的页面地址(如https://apps.apple.com/cn/app/id682211190)的id后面的数字
+ *     android: 应用的包名,apk文件中AndroidManifest.xml中的package字段值
+ *     key: 为设置id标识或包名时,打开搜索页面使用的关键词
+ * 如果未设置参数,则打开应用市场的搜索页面
+ */
+function openMarket(options){
+	try{
+	var os = plus.os.name;
+	options = options||{};
+	if('iOS' == os){
+		if(options.ios&&''!=options.ios){
+			//itms-apps://itunes.apple.com/app/id682211190
+			plus.runtime.openURL('itms-apps://itunes.apple.com/app/id'+options.ios);
+		}else{
+			//itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/search?q=helloh5
+			plus.runtime.openURL('itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/search?q='+options.key);
+		}
+	}else{
+		if(options.android&&''!=options.android){
+			//io.dcloud.helloh5
+			var uri = plus.android.invoke('android.net.Uri', 'parse', 'market://details?id='+options.android);
+			var Intent = plus.android.importClass('android.content.Intent');
+			var intent = new Intent(Intent.ACTION_VIEW, uri);
+			intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+			var main = plus.android.runtimeMainActivity();
+			main.startActivity(intent);
+		}else{
+			//market://search?q=helloh5
+			plus.runtime.openURL('market://search?q='+options.key);
+		}
+	}
+	}catch(e){
+		console.error('error @openMarket!!');
+	}
+}
+
+
+module.exports = {
+	open: openMarket
+}

+ 104 - 0
libs/axios.js

@@ -0,0 +1,104 @@
+import { objToUrl } from '@/libs/tools.js';
+const baseUrl = getApp().globalData.baseUrl
+//带Token请求
+const request = (opts, hasToken) => {
+	const authorization = getApp().globalData.token
+	// console.log(authorization,'authorization')
+	//  hasToken是否传入token,为true则不传token
+	const header = {
+			'content-Type': 'application/json;charset=UTF-8'
+		}
+	if(!hasToken&&authorization){
+		header['X-AUTH-TOKEN'] = authorization
+	}	
+	//此token是登录成功后后台返回保存在storage中的
+	let DefaultOpts = {
+		url: baseUrl+opts.url,
+		data: opts.data,
+		method: opts.method,
+		header: opts.header ? opts.header : header
+	}
+	let promise = new Promise(function(resolve, reject) {
+		uni.request(DefaultOpts).then(
+			(res) => {
+				console.log(res)
+				// 是否超时已跳转
+				let loginTimeOut = uni.getStorageSync('loginTimeOut');
+				if(res[0]&&res[0].errMsg.indexOf('request:fail')>=0){
+					resolve({
+						status: 6000,
+						message: "网络请求失败"
+					})
+				}
+				let ret = res[1]
+				if(res[1].data.status){
+					ret = res[1].data
+				}else if(res[1].status){
+					ret = res[1]
+				}
+				if(ret.status == '200'){
+					resolve(ret)
+				}else{
+					if(ret.status == '401'){  //  未登录或登录过期
+					    // 除过查询未完成订单的请求
+						if(opts.url.indexOf("order/unFinishedOrderRunStatus")<0){
+							uni.showToast({
+								icon:'none',
+								title: '未登录或登录已过期,请重新登录',
+								duration: 5000 
+							})
+							setTimeout(function() {
+							  const currentRoute = getRoutePath();
+							  const url = `/pages/login/login?lanuch=${currentRoute.lanuch}&path=` + encodeURIComponent(currentRoute.url);
+							  uni.redirectTo({ url });
+							}, 1000);
+						}
+					}
+					else{
+						if(ret.status == 500){
+							if(opts.url.indexOf("store/validateStoreAndDevice")<0&&opts.url.indexOf("couponReceives/findUsableCoupon")<0){
+								uni.showToast({
+									icon:'none',
+									title: ret.message,
+									mask: true,
+									duration: 3000 
+								})
+							}
+						}
+					}
+					resolve(ret)
+				}
+				resolve(ret)
+			}
+		).catch(
+			(response) => {
+				reject(response)
+			}
+		)
+	})
+	return promise
+}
+function getRoutePath() {
+  // eslint-disable-next-line no-undef
+  const pages = getCurrentPages(); // 获取加载的页面
+  const currentPage = pages[pages.length - 1]; // 获取当前页面的对象
+  let url = currentPage.route; // 当前页面url
+  const options = objToUrl(currentPage.options); // 如果要获取url中所带的参数可以查看options
+
+  const lanuch = [
+    'pages/index/index'
+  ].includes(url);
+  url = '/' + url;
+  if (options.length) {
+    url += '?' + options;
+  }
+  return {
+    url,
+    lanuch
+  };
+}
+
+export default {
+	baseUrl,
+	request
+}

+ 15 - 0
libs/tools.js

@@ -0,0 +1,15 @@
+export function objToUrl(obj) {
+  let uri = '';
+  let keys = Object.keys(obj);
+  keys.forEach(item => {
+    uri += '&' + item + '=' + obj[item];
+  });
+
+  uri = uri.substr(1);
+
+  return uri;
+}
+
+
+
+  

+ 5 - 0
libs/validate.js

@@ -0,0 +1,5 @@
+//  正则有效手机号码
+export const isvalidPhone = function (str) {
+  const reg = /^1[0-9]{10}$/
+  return reg.test(str)
+}

+ 45 - 0
main.js

@@ -0,0 +1,45 @@
+import Vue from 'vue'
+import App from './App'
+import store from '@/store'
+Vue.config.productionTip = false
+
+App.mpType = 'app'
+import uView from "uview-ui"
+Vue.use(uView)
+
+let vuexStore = require("@/store/$u.mixin.js")
+Vue.mixin(vuexStore)
+/*
+* 提示信息
+*/ 
+Vue.prototype.toashMsg = function (title) {
+	title = title == undefined ? "系统繁忙" : title;
+	uni.showToast({
+		title:title,
+		icon:'none',
+		duration: 2000
+	})
+}
+/**
+ * 权限检查
+ */
+Vue.prototype.$hasPermissions = function (value) {
+ let isExist = false;
+ let permissionsStr = store.state.vuex_userData.permCodes;
+ // console.log(permissionsStr,'permissionsStr')
+ if (permissionsStr == undefined || permissionsStr == null) {
+  return false;
+ }
+ let has = permissionsStr.find(item => {
+   return item == value
+ })
+ if (has) {
+  isExist = true;
+ }
+ return isExist;
+}
+const app = new Vue({
+	store,
+    ...App
+})
+app.$mount()

+ 85 - 0
manifest.json

@@ -0,0 +1,85 @@
+{
+    "name" : "乐色助手",
+    "appid" : "wx0ac42ab03487bbae",
+    "description" : "乐色助手合作商小程序",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wx623b3d89df212494",
+        "setting" : {
+            "urlCheck" : false,
+            "es6" : true,
+            "minified" : true
+        },
+        "usingComponents" : true,
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "需要获取用户位置信息定位门店的位置"
+            }
+        }
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    }
+}

+ 13 - 0
package-lock.json

@@ -0,0 +1,13 @@
+{
+  "name": "zxyj-mini-html",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "uview-ui": {
+      "version": "1.7.8",
+      "resolved": "https://registry.npmjs.org/uview-ui/-/uview-ui-1.7.8.tgz",
+      "integrity": "sha512-9mc3FbpvN7knmmdBmlGv9eF6us16co/bvTPrFEsS8QrSo9AZyrFyET9KHIdESwURP79jv1SLtR01VEb3HGSN8A=="
+    }
+  }
+}

+ 19 - 0
package.json

@@ -0,0 +1,19 @@
+{
+  "name": "zxyj-mini-html",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://git.chelingzhu.com/chelingzhu-web/zxyj-mini-html.git"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "uview-ui": "^1.7.7"
+  }
+}

+ 76 - 0
pages.json

@@ -0,0 +1,76 @@
+{
+	"easycom": {
+		"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
+	},
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index", // 首页
+			"style": {
+				"navigationBarTitleText": "乐色助手",
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/login/login",
+			"style": {
+				"navigationBarTitleText": "登录"
+			}
+		},
+		{
+			"path": "pages/userCenter/index",
+			"style": {
+				"navigationBarTitleText": "个人中心",
+				"navigationBarBackgroundColor": "#01c9b2",
+				"navigationBarTextStyle": "white"
+			}
+		},
+		{
+			"path": "pages/userCenter/aboutUs",
+			"style": {
+				"navigationBarTitleText": "关于我们"
+			}
+		},
+		{
+			"path": "pages/agreement/agreement",
+			"style": {
+				"navigationBarTitleText": "注意事项及服务协议"
+			}
+		},
+		{
+			"path": "pages/userCenter/changePwd",		//  我的>修改密码
+			"style": {
+				"navigationBarTitleText": "修改密码"
+			}
+		}
+    ],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#FFFFFF",
+		"backgroundColor": "#f2f2f2",
+		"app-plus": {
+			// 将回弹属性关掉
+			"bounce": "none",
+			"scrollIndicator": "none"
+		}
+	},
+	"tabBar": {
+		"color": "#7A7E83",
+		"selectedColor": "#01c9b2",
+		"backgroundColor": "#ffffff",
+		"fontSize": "14px",
+		"list": [
+		{
+			"pagePath": "pages/index/index",
+			"iconPath": "static/tabbar/home.png",
+			"selectedIconPath": "static/tabbar/home-active.png",
+			"text": "交易记录"
+		},
+		{
+			"pagePath": "pages/userCenter/index",
+			"iconPath": "static/tabbar/user.png",
+			"selectedIconPath": "static/tabbar/user-active.png",
+			"text": "我的"
+		}]
+	}
+}

+ 430 - 0
pages/agreement/agreement.vue

@@ -0,0 +1,430 @@
+<template>
+	<view class="content">
+		<view class="page-content">
+			<view class="page-care" v-if="!hideZysx">
+				<u-image width="750rpx" height="1200rpx" mode="aspectFit" src="/static/img/zysx.png"></u-image>
+			</view>
+			<view class="page-care agreement">
+				<view class="title">-服务协议-</view>
+				<view class="xy-cons">
+					<view class="MsoNormal">
+						欢迎您使用智能快洗!
+					</view>
+					<view class="MsoNormal">
+						<text>本《智能快洗平台用户服务协议》(以下简称</text>“本服务协议”)是您与智能快洗平台之间就登录注册智能快洗用户账号及使用智能快洗的各项服务等相关事宜所订立的协议。为更好的体验智能快洗的服务,您应当仔细阅读并遵守本服务协议下的全部内容,特别是涉及免除或者责任限制的条款,该类条款可能以黑体加粗或加下划线的形式提示您重点注意。除非您已阅读并接受本服务协议所有条款,否则您将不能注册智能快洗账号或使用智能快洗的服务,如您不同意本服务条款的任意内容,请勿注册或使用智能快洗的服务,并应立即停止注册程序。如您对本服务协议的内容(特别是涉及免除或者责任限制的条款)有任何疑义,可随时按照本服务协议中列明的联系方式与我们联系,我们将根据您的要求为您进一步解释和说明相关内容。如您点击“同意、继续服务”等并通过登录注册程序或其他任何方式使用或接受智能快洗的任何服务,即视为您已阅读并同意本服务协议,自愿接受本服务协议的所有内容的约束。请您在决定注册或使用服务前再次确认您已知悉并完全理解本服务协议的所有内容。
+					</view>
+					<view class="MsoNormal">
+						<text>如果您未满</text>18<text>周岁,请务必在家长或其他法定监护人的陪同下阅读本服务协议,并在进行注册、下单、支付等任何行为或使用智能快洗的其他任何服务前,应事先征得您的家长或其他法定监护人的同意。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>本服务协议所述</text>“服务”指您使用智能快洗用户账号接受智能快洗平台所提供的服务。无论“服务”属于上述何种情形,本《智能快洗平台用户服务协议》都将予以适用。
+					</view>
+					<view class="MsoNormal">
+						一、协议的主体与范围
+					</view>
+					<view class="MsoNormal">
+						<text>【签约主体】</text> <text>视您所使用的具体服务而定,</text>“智能快洗”是指陕西山海高科信息技术有限公司旗下运营或提供您所使用的相关具体服务的相关主体的统称,您可在相关服务的具体服务条款或说明文件中查看上述主体的名称和信息。 “用户”是指使用智能快洗相关服务的使用人,在本服务协议中更多称呼为“您”。
+					</view>
+					<view class="MsoNormal">
+						【协议内容】本服务协议内容同时包括:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)本《智能快洗平台用户服务协议》文本;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)《智能快洗平台隐私政策》:智能快洗非常重视您的个人信息和隐私的保护,将按照《智能快洗平台隐私政策》中所公布的政策在具体服务中进行隐私及个人信息的收集、存储、使用、披露等活动。您在注册智能快洗账号前,应仔细阅读《智能快洗平台隐私政策》;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)专项商品或服务规则。为更好地提升用户体验,智能快洗在若干具体专项商品或服务中特别制定了详细的规则,供各方遵守。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>4<text>)第三方服务相关的用户授权协议。为方便您在使用智能快洗平台服务的同时,根据您自身需要,通过智能快洗平台进行跳转等方式,自愿选择接受独立第三方服务商及部分关联第三方服务商所提供的服务,您将在使用该等第三方服务前对相应的用户授权协议予以同意确认,以作为您和智能快洗平台之间就该等第三方服务的使用所涉及的相关事项的权利义务依据。</text>
+					</view>
+					<view class="MsoNormal">
+						上述各项内容之间如存在不一致之处,以时间上最新发布的内容为准,发布时间相同的,以本款【协议内容】中所包含的组成部分的序号倒序排列为准。
+					</view>
+					<view class="MsoNormal">
+						【协议遵守】您理解并同意:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)上述【协议内容】中所述的协议、政策、条款与规则构成本服务协议不可分割的组成部分,共同适用于您所使用的智能快洗平台服务。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)根据国家法律法规变化、运营需要或为提升服务质量的目的,智能快洗将在必要的时候对上述各项协议、条款与规则不时地进行修改更新,并通过在网页或移动客户端上发出公告、站内信等合理、醒目的方式向您进行提前通知,上述修改更新内容将在相关更新说明中指定的日期开始实施,通常情况下不短于发布之日后八个自然日。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)您应当及时查阅并了解相关更新修改内容,如您不同意相关更新修改内容,可停止使用相关更新修改内容所涉及的服务,此情形下,变更事项对您不产生效力;如您在上述更新修改内容实施后继续使用所涉及的服务,将视为您已同意各项更新修改内容。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>【适用平台】本服务协议适用于网页端、移动客户端(包括</text>IOS<text>、安卓及已有或未来将新增的任何其他移动客户端)等各类平台或媒介中智能快洗所提供的各项服务。您知悉并理解,为享受上述相关服务,您必须自行提供相关上网设备(如:个人电脑、手机、平板电脑或其他设备)并自行负担相关网络使用和商品服务支付所产生的有关费用。</text>
+					</view>
+					<view class="MsoNormal">
+						二、账号的注册
+					</view>
+					<view class="MsoNormal">
+						【注册资料】您应遵循诚实信用、合法善意的原则,向智能快洗提交相关注册资料,相关注册资料应当遵守法律法规、社会主义制度、国家利益、公民合法权益、公序良俗、信息真实等原则,不应提交任何违法或不良信息,相关资料如有变动,您应及时更新。如果因您所提供的注册资料不合法、不真实、不准确或未及时更新,从而导致相关法律责任或不利后果的,您将承担相应的法律责任及不利后果。
+					</view>
+					<view class="MsoNormal">
+						您已知悉,在智能快洗账号注册过程中,因安全性、内部管理等原因,部分初始注册资料可能将无法再次更改。因此,您应谨慎核查此部分注册资料,确保正确填写。
+					</view>
+					<view class="MsoNormal">
+						【民事行为能力】您知悉并承诺,您具有完全民事权利能力和行为能力或虽不具有完全民事权利能力和行为能力但已经过您的家长和其他法定监护人同意并由您的家长和其他法定监护人代理注册及使用智能快洗的各项服务。若您不具备前述相适应的民事行为能力即进行账号注册的,则您及您的家长和其他法定监护人应依照法律规定承担因此而导致的一切后果。
+					</view>
+					<view class="MsoNormal">
+						【真实身份】因国家法律法规、监管要求及特定门类的服务(如金融服务等)需要,您有可能需要在注册或使用该等服务的特定环节中填写真实的身份信息,并需通过相关验证流程后方可使用服务,如您填写的身份信息不完整、不真实或未通过验证,将导致您无法使用该服务、损害自身、他人利益或造成其他不利后果,该等后果将由您予以承担。
+					</view>
+					<view class="MsoNormal">
+						【注册信息保护】智能快洗将对您所提交的注册信息予以保护,不会将其披露给任何非关联的第三方,除非:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)相关法律法规或国家机关要求;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)智能快洗发生相关合并、分立、收购或资产转移;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)为提供相关服务所必须的</text>
+					</view>
+					<view class="MsoNormal">
+						对于您所提交的注册信息中涉及个人信息的内容,智能快洗将严格按照《智能快洗平台隐私政策》的规定进行收集、处理和使用。
+					</view>
+					<view class="MsoNormal">
+						【账号数量】除因历史原因、业务整合等智能快洗所认可的特殊情况外,智能快洗原则上只允许您使用一个智能快洗用户账号。如有证据证明或智能快洗有理由相信您存在不当注册或不当使用多个智能快洗账号的情形,智能快洗可将相关账号信息进行合并或采取其他合理措施,如因您不当注册或不当使用给智能快洗及相关方造成损失的,您还应承担相应的赔偿责任。此外,出于相关业务需要,智能快洗也可对同一用户的多个账号或相关信息进行合并,如该等合并将对您的权益产生实质影响,智能快洗将在进行上述合并前事先征得您的同意。
+					</view>
+					<view class="MsoNormal">
+						三、账号的使用
+					</view>
+					<view class="MsoNormal">
+						【账号登录】您可以使用注册账号时填写登记的并获得系统审核通过的账号密码登录智能快洗洗车平台。为了您的使用便利,智能快洗未来可能将适时提供指纹识别、人脸识别等更为安全便捷的登录方式,智能快洗可根据相关法律法规、监管要求、用户体验、风险控制等因素对登录方式予以适时调整或增减,或在具体业务门类的用户服务条款等规定中予以进一步细化或调整。
+					</view>
+					<view class="MsoNormal">
+						【账号使用】您应对您账号项下的所有行为(包括但不限于在线签署任何协议,浏览、购买、支付、点评、上传、发布、输入任何内容)所产生的一切后果负责,通过您的账号所发生的上述各项行为将视为您本人的真实意思表示。
+					</view>
+					<view class="MsoNormal">
+						智能快洗提示您,您通过自身账号在接受智能快洗的各项服务中所上传、发送的任何内容都应具有合法来源,如相关内容涉及任何第三方的合法权益,应事先获得相应的许可。如智能快洗收到涉及您的相关举报或投诉,智能快洗可采取相关法律法规所要求或允许的方式,向相关争议方、相关部门提供包括账号在内的必要的信息,以便解决纠纷争议,保护正当权利人的合法权益。
+					</view>
+					<view class="MsoNormal">
+						【账号借用】为保证相关账号安全,未经智能快洗的书面同意,您不应将智能快洗平台上所注册的账号借给他人使用,否则您应当承担由此产生的全部责任,智能快洗平台保留拒绝提供相应服务、冻结或收回注册账号或终止本服务协议的权利,并可要求您对智能快洗所承受的损失予以赔偿。
+					</view>
+					<view class="MsoNormal">
+						【安全义务】如您发现账号存在安全问题,请您立即联系智能快洗予以调查处理,否则智能快洗对潜在损失的产生或扩大不承担任何责任。
+					</view>
+					<view class="MsoNormal">
+						智能快洗特别提示您,您应该妥善保管您的账号和密码,当您使用完毕智能快洗的服务后,您应安全退出。此外,您不应轻信借款、索要密码或其他涉及财产的网络信息。涉及财产操作的,请一定先核实对方身份,并请经常留意智能快洗有关防范诈骗犯罪的提示。您知悉并同意,如您在账号信息的保管上、相关上网设备的网络安全维护上存在任何过失导致账号丢失、泄露,您应对此所产生的任何后果负责,智能快洗对此不负任何责任,如智能快洗因此产生相关支出,智能快洗将有权向您予以追偿。
+					</view>
+					<view class="MsoNormal">
+						【限制冻结】您知悉并同意,在符合法律法规的规定,或经国家机关要求的前提下,智能快洗有权对您的注册账号进行限制或冻结,在该等情况下,您可能无法继续登陆或使用您的注册账号。
+					</view>
+					<view class="MsoNormal">
+						四、服务说明、用户行为规范与责任承担
+					</view>
+					<view class="MsoNormal">
+						【服务说明】您应在享受智能洗车服务的过程中应按照下列操作进行:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)您每次使用都需严格按照要求把车停到洗车机器人指定区域,可以通过洗车机器人两侧辅助镜查看,确保车辆在黄线内,车头到达绿色区域,严禁在未按要求停好车的情况下启动智能快洗洗车机器人。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>) 您应按语言提示缓慢驶入或驶离洗车机器人,保持小心安全驾驶。并按要求停车后回正方向盘、挂</text><text>P</text><text>档、拉手刹、收后视镜、关车窗、关雨刮、收天线等超长加装件,保证车辆在洗车服务中安全。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>) 智能快洗洗车机器人仅能服务低于</text><text>2</text><text>米高的普通轿车,您不能驾驶超过高</text><text>2</text><text>米、宽</text><text>2</text><text>米、长</text><text>5.3</text><text>米的车辆和货车等特殊车辆,强行使用智能快洗洗车机器人。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>4<text>)在智能快洗洗车机器人服务过程中,用户应始终保持车辆静止,不得移动车辆,严禁上下车或伸出窗外张望,也不能在洗车机内部走动。</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>5<text>) 车龄比较老的车,因为部件老化松动等情况,清洗有可能会损坏,不建议您使用洗车机服务。</text>
+					</view>
+					<view class="MsoNormal">
+						【用户义务】您知悉并承诺,在使用智能快洗所提供的服务的过程中,您应遵守相关法律法规,不应从事如下违反法律法规的规定,影响正常服务提供或损害他人合法利益的行为:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)不应利用智能快洗平台或相关服务危害国家安全、破坏政治稳定、泄露国家秘密,不侵犯国家、社会、集体利益和第三方的合法权益,不从事违法犯罪活动,不设立用于实施诈骗,传授犯罪方法,制作或者销售违禁物品、管制物品等违法犯罪活动的网站、通讯群组,不利用网络发布涉及实施诈骗,制作或者销售违禁物品、管制物品以及其他违法犯罪活动的信息;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)不应制作、发布、复制、查阅和传播、存储、链接下列信息:反对宪法所确定的基本原则的;危害国家安全,泄露国家秘密,颠覆国家政权,推翻社会主义制度,煽动分裂国家,破坏国家统一的;损害国家荣誉和利益的;煽动民族仇恨、民族歧视,破坏民族团结的;破坏国家宗教政策,宣扬邪教和封建迷信的;煽动非法集会、结社、游行、示威、聚众扰乱社会秩序的;捏造、散布谣言,侵犯他人权利,扰乱经济、社会秩序,破坏社会稳定的;散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;侮辱或者诽谤他人,侵害他人合法权益的;宣扬恐怖主义、极端主义的;违背当地风俗习惯的;含有法律、行政法规禁止的其他内容的;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)不应从事下列危害计算机信息网络安全的活动:对网络服务及相关软硬件设施进行破解、破坏、删除、修改或者增加的;对计算机信息网络中存储或者传输的数据和应用程序进行删除、修改或者增加的;使用软件或硬件等方式窃取他人口令、非法入侵他人计算机系统;故意制作、传播计算机病毒等破坏性程序的;其他危害计算机信息网络安全的活动;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>4<text>)不应擅自复制和使用网络上未公开和未授权的文件。除相关著作权人明示同意,不应在网络中擅自破解、传播、下载或复制第三人享有著作权的软件或进行其他任何侵害他人知识产权的活动;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>5<text>)不应私自传播广告信息或以“刷单”等不正当方式帮助他人提升评价或利用评价权利对其他用户、商户实施威胁、敲诈勒索;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>6<text>)不应因对智能快洗相关服务的使用行为导致智能快洗卷入政治和公共事件;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>7<text>)不应通过侵犯第三人合法权益、作弊、扰乱系统、实施网络攻击、恶意套现、刷信誉、批量注册、用机器注册智能快洗平台账户、用机器模拟客户端等手段进行交易或参加智能快洗或其所授权的第三方发布的任何活动;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>8<text>)未经智能快洗事先书面许可,不应自行或授权、协助任何第三方非法抓取智能快洗所展示的任何内容(“非法抓取”是指采用未经智能快洗认可的程序或者非正常浏览等技术手段获取内容数据的行为)。</text>
+					</view>
+					<view class="MsoNormal">
+						【责任承担】如您在使用智能快洗服务过程中涉及违反服务说明或违反用户义务中的一项或多项,则需要对自己的行为承担法律责任。承担法律责任的形式包括但不限于:造成的损失由您自己承担,对受到侵害者(包括智能快洗)进行赔偿。特别的,若您违反用户义务导致智能快洗承担了因您的行为导致的行政处罚或侵权损害赔偿责任等损失(含诉讼费、律师费等相关法律程序费用)后,您还应立即给予智能快洗等额的赔偿。此外,根据具体违法违规情形,智能快洗有权作出独立判断,立即暂停或终止对您提供部分或全部服务,包括锁定、注销、删除帐号等措施。
+					</view>
+					<view class="MsoNormal">
+						您知悉并理解,如果智能快洗发现您的上述违法违规行为,依据相关法律法规的规定,智能快洗有权或有义务立即停止相关服务,删除或屏蔽相关违规信息、服务评价,并视情况进行调查取证,保存有关记录,或向国家有关机关举报。同时,智能快洗有权自主删除、屏蔽含有该内容的任何数据信息。智能快洗将根据国家相关法律法规要求,对依法删除、屏蔽的数据信息予以记录、保存。
+					</view>
+					<view class="MsoNormal">
+						【广告促销】您理解并同意:为向您提供更为细致、贴心的服务,在经过您的事先确认后,智能快洗或智能快洗授权、认可的第三方商家、广告商可能通过您注册时填写的手机号码或者电子邮箱向您发送您可能感兴趣的商品服务的广告宣传信息、促销优惠等商业性信息,其方式和范围可不经向您特别通知而变更;如果您不愿意接收此类信息,则您有权通过智能快洗平台提供的相应的退订方式进行退订。
+					</view>
+					<view class="MsoNormal">
+						您理解并同意,对上述的相关广告信息,您应审慎判断其真实性和可靠性,除法律法规明确规定外,您应对依该广告信息进行的交易负责。
+					</view>
+					<view class="MsoNormal">
+						【使用目的】除非在特定服务条款或规则中您与智能快洗另有约定或智能快洗另行予以书面同意,您将确保本服务协议下的服务仅为您个人非商业性质的使用。未经智能快洗书面同意,您不得使用未经智能快洗授权的任何插件、外挂或第三方工具对本服务协议下的服务进行干扰、破坏、修改或施加其他影响。
+					</view>
+					<view class="MsoNormal">
+						五、知识产权
+					</view>
+					<view class="MsoNormal">
+						【知识产权】您理解并知悉,除另有书面声明以外,以下信息和内容的知识产权(包括但不限于专利权、著作权、商标权及商业秘密)归智能快洗所有:
+					</view>
+					<view class="MsoNormal">
+						1. <text>在智能快洗相关服务中所提供的内容和信息(包括但不限于软件、技术、程序、网页、文字、图片、图像、地图、图标、音频、视频、图表、版面设计、电子文档、数据资料等);</text>
+					</view>
+					<view class="MsoNormal">
+						2. <text>智能快洗用于提供上述内容和信息的基础设施和平台(包括但不限于软件、网站、应用程序等);</text>
+					</view>
+					<view class="MsoNormal">
+						3. <text>在智能快洗提供相关服务中所使用的各项商标、商业形象、商业标识、技术诀窍、标语、文案等;</text>
+					</view>
+					<view class="MsoNormal">
+						4. <text>智能快洗平台服务的开发、运营、维护等过程中产生的所有数据和信息。</text>
+					</view>
+					<view class="MsoNormal">
+						智能快洗所拥有的上述权利及所提供服务中所包含的任何内容的知识产权均受到法律保护,未经智能快洗事先书面许可,您承诺不应且不应允许或协助任何人以任何形式(包括但不限于通过任何机器人、蜘蛛、截屏等程序或设备)进行使用、出租、出借、分发、展示、复制、修改、链接、转载、汇编、发表、出版、抓取、监视、引用或创造相关衍生作品。
+					</view>
+					<view class="MsoNormal">
+						【用户生成内容】您理解并同意,您在使用智能快洗提供的服务时上传、提交、存储或发布的内容(包括但不限于文字、图片、视频、音频、动画等)均由您原创或已获合法授权。您通过智能快洗上传、提交、存储或发布的任何内容的知识产权归属您或原始著作权人所有,您的上传、提交、存储或发布行为不会侵犯他人的知识产权或其他合法权益。
+					</view>
+					<view class="MsoNormal">
+						您知悉、理解并同意,您一旦接受本服务协议,即表明您主动将上述内容的非专属、可转让的财产性权利,如著作权(包括但不限于:复制权、发行权、出租权、展览权、表演权、放映权、广播权、信息网络传播权、摄制权、改编权、翻译权、汇编权以及应当由著作权人享有的其他可转让权利),在全世界范围内永久、免费、独家且不可撤销地授权给智能快洗及其关联公司,智能快洗及其关联公司可基于该等授权使用上述内容(包括但不限于用于商业用途)或向第三方自主进行任何必要的转授权。该等授权、转授权的使用场景包括但不限于当前或其他任何网站、应用程序、产品或移动终端设备等,且智能快洗及其关联公司或智能快洗及其关联公司所授权许可的第三方可通过对上述授权内容进行修改、复制、改编、翻译、汇编或制作,形成衍生产品。在不违反相关法律法规的强制性规定、尊重相关原始授权内容的知识产权的基础上,该等衍生产品的相关知识产权归智能快洗及其关联公司或智能快洗所授权许可的第三方所有。
+					</view>
+					<view class="MsoNormal">
+						您确认并同意授权智能快洗及其关联公司以自己的名义或委托专业第三方针对有关您上传、提交、存储或发布的内容(含衍生作品)的侵权行为进行独立自主的维权并获得全部赔偿。维权形式包括但不限于:监测侵权行为、发送维权函、提起诉讼或仲裁、调解、和解等。智能快洗及其关联公司有权对维权事宜做出独立决策并予以实施。
+					</view>
+					<view class="MsoNormal">
+						您同意,因您通过智能快洗上传、发布的任何内容导致的知识产权侵权问题,您将承担全部责任;如智能快洗及其关联公司,或智能快洗授权的其他服务提供方因第三方知识产权维权而产生损失,您将等额赔偿
+					</view>
+					<view class="MsoNormal">
+						&nbsp;
+					</view>
+					<view class="MsoNormal">
+						本服务协议已经构成《著作权法》第二十五条所规定的书面协议,其效力及于用户在智能快洗平台发布的任何受著作权法保护的作品内容,无论该内容形成于本服务协议签订前还是本服务协议签订后。智能快洗及智能快洗转授权、许可的相关方有权将您在智能快洗平台发表的产品使用体验、产品讨论或图片进行使用或者与其他人合作使用,使用范围包括但不限于网站、电子杂志、杂志、刊物、海报、公众号文章、微信小程序等。
+					</view>
+					<view class="MsoNormal">
+						【软件使用】在使用智能快洗相关客户端的过程中,您可能需要下载特定软件。为了改善用户体验、修复漏洞、保障安全性等考虑,智能快洗有权对软件进行更新,您应该将相关软件更新至最新版本,否则智能快洗将不保证您能正常使用相关软件。
+					</view>
+					<view class="MsoNormal">
+						如果您从未经智能快洗授权的第三方获取软件或与该软件名称相同的安装程序,智能快洗无法保证该软件能够正常使用,由此造成的相关损失,智能快洗不予负责。
+					</view>
+					<view class="MsoNormal">
+						【软件使用禁止性规定】除非智能快洗书面许可,您在使用智能快洗的软件时不得从事下列任一行为:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)删除、编辑或遮挡软件及其副本上关于著作权、商标或其他权利标识或标记的信息;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)复制、发布、出售或出租软件或其任何部分;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)对软件进行反向工程、反向汇编、反向编译,或者以其他方式尝试挖掘、提取软件的源代码;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>4<text>)其他对软件、软件所产生的数据所进行的任何增加、删减、变动的任何行为,包含制作、利用、授权各种第三方外挂、插件、系统进行上述行为。</text>
+					</view>
+					<view class="MsoNormal">
+						六、协议的终止
+					</view>
+					<view class="MsoNormal">
+						【协议终止情形】本服务协议于以下任一情形下终止:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)智能快洗有权依据本服务协议约定,收回、注销用户账号,此情形下,本服务协议于账号收回、注销之日终止;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)在满足智能快洗平台公示的账号注销条件时,您通过网站自助服务或智能快洗的客服注销智能快洗的用户账号,本服务协议于账号注销之日终止。您理解并同意,您已认真阅读、认可《智能快洗隐私政策》,并已了解、同意相关用户注销流程及注销后的权利义务安排;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)智能快洗有权根据自身商业安排经过合理的提前通知终止全部智能快洗平台服务,本服务协议于智能快洗平台全部服务依法定程序及方式终止之日终止。</text>
+					</view>
+					<view class="MsoNormal">
+						本服务协议终止后,智能快洗将无法继续向您提供任何服务或履行任何其他义务,包括但不限于为您保留或向您披露其原智能快洗账号中的任何信息,向您或第三方转发任何未曾阅读或发送过的信息等。
+					</view>
+					<view class="MsoNormal">
+						<text>本服务协议的终止不影响第五条知识产权条款以及其他根据其内容应继续有效</text> <text>的条款的有效性,也不影响本服务协议终止前各方的相关权利和义务,包括但不</text> <text>限于守约方依据本服务协议向违约方追究相应的违约责任。</text>
+					</view>
+					<view class="MsoNormal">
+						七、通知与送达
+					</view>
+					<view class="MsoNormal">
+						您知悉并认可,智能快洗可视情况通过下列任意一种或几种方式向您通知重要信息:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)向您注册时所提交的电子邮箱地址发送电子信息;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)向您注册时所提交的手机号码发送电子信息;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)向您提供的实际地址进行寄送纸质载体信息;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>4<text>)在网站或客户端显著位置刊登电子信息;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>5<text>)向智能快洗网站或相应客户端的账户的站内信或其他即时通信客户端发送电子信息。</text>
+					</view>
+					<view class="MsoNormal">
+						上述电子信息在发送成功或刊登完成后即视为送达。相关纸质载体的发送以相关邮寄凭证上的邮寄日期后的第五个自然日视为送达。
+					</view>
+					<view class="MsoNormal">
+						上述送达方式同样可适用于相关仲裁或司法程序(含起诉、审理、执行等各阶段)。
+					</view>
+					<view class="MsoNormal">
+						您应当保证所提供的联系方式是准确、有效的,并进行适时更新,如因提供的联系方式不准确或怠于更新等不可归责于智能快洗的原因,导致相关通知、文件、文书无法送达或及时送达,您将自行承担由此可能产生的法律后果。
+					</view>
+					<view class="MsoNormal">
+						八、不可抗力或其他免责事由
+					</view>
+					<view class="MsoNormal">
+						【不可抗力】您理解并同意,在使用本服务的过程中,可能会遇到不可抗力等风险因素,使本服务协议下的服务发生中断或终止。不可抗力是指不能预见、不能克服并不能避免且对一方或双方造成重大影响的客观事件,包括但不限于信息网络设备维护、信息网络连接故障、电脑、通讯或其他系统的故障、电力故障、罢工、劳动争议、暴乱、起义、骚乱、生产力或生产资料不足、火灾、洪水、风暴、爆炸、战争、政府行为、法律法规变动、司法行政机关的命令、其他不可抗力或第三方的不作为而造成的不能服务或延迟服务等行为。出现上述情况时,智能快洗将努力在第一时间与相关部门配合,及时进行修复,但是由此给您造成的损失,智能快洗在法律允许的范围内免责。
+					</view>
+					<view class="MsoNormal">
+						【其他免责事由】您理解并同意,在法律允许的范围内,智能快洗对以下事由所导致的服务中断或终止不承担责任:
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>1<text>)受到计算机病毒、木马或其他恶意程序、黑客攻击的破坏;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>2<text>)用户或智能快洗的电脑软件、系统、硬件和通信线路出现故障;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>3<text>)用户操作不当;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>4<text>)用户通过非智能快洗授权的方式使用本服务;</text>
+					</view>
+					<view class="MsoNormal">
+						<text>(</text>5<text>)其他智能快洗无法控制或合理预见的情形。</text>
+					</view>
+					<view class="MsoNormal">
+						【信息真实性】智能快洗提示您,在使用本服务的过程中,您可能会遇到网络信息或其他用户行为带来的风险,该等风险包含他人匿名或冒名的含有威胁、诽谤、令人反感、非法、误导、欺骗等任何信息或行为所造成任何心理、生理上的伤害或经济损失。请您仔细甄别,并在遇到上述相关不法行为时及时向智能快洗或有关机关举报或投诉。智能快洗将对相关内容开展日常检查并在收到举报或投诉时进行相应处理,但请您注意,智能快洗不对非智能快洗所发出或刊登的任何信息的真实性、适用性、合法性承担责任,也不对因第三方侵权行为给您造成的损害负责。
+					</view>
+					<view class="MsoNormal">
+						【责任限制】除非另有明确书面说明,在符合法律法规规定的情况下,智能快洗不对其网站和客户端上的信息、内容、材料、产品或服务做任何形式的明示或默示的声明或担保。
+					</view>
+					<view class="MsoNormal">
+						九、管辖、法律适用与争议解决
+					</view>
+					<view class="MsoNormal">
+						本服务协议的成立、生效、履行、解释与纠纷解决,适用中华人民共和国大陆地区法律法规,并且排除一切冲突法规定的适用。
+					</view>
+					<view class="MsoNormal">
+						如因某项具体服务中的产品或服务问题导致您与智能快洗间出现纠纷,您同意,该等纠纷将由适用于该项具体服务的服务条款或规则中所规定的争议解决地的有管辖权人民法院受理。就本服务协议而言,如您因本服务协议文本与智能快洗产生争议纠纷,您同意交由本服务协议签订地有管辖权人民法院受理。本服务协议签订地为上海市杨浦区。
+					</view>
+					<view class="MsoNormal">
+						十、其他
+					</view>
+					<view class="MsoNormal">
+						【关键词】本服务协议各条款前所列关键词仅供帮助理解条款主旨及快速定位查询条款所用,不能代替条款的任何内容,亦不作为解释条款的依据,智能快洗建议您仔细阅读各条款的具体表述,以维护您的合法权益。
+					</view>
+					<view class="MsoNormal">
+						【可分性】如果本服务协议中任何一条被视为废止、无效或因任何理由不可执行,该条应视为可分的且并不影响任何其余条款的有效性和可执行性。
+					</view>
+					<view class="MsoNormal">
+						再次感谢您的耐心阅读!
+					</view>
+					<view style="text-align: right;">
+						陕西山海高科信息技术有限公司
+					</view>
+					<view style="text-align: right;">
+						2020年8月
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="footer" @click="goBack" v-if="!hideZysx">
+			我已阅读并同意无人洗车《注意事项及服务协议》
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		components: {
+		},
+		data() {
+			return {
+				hideZysx: false
+			}
+		},
+		onLoad(option) {
+			// 从个人中心打开的
+			if(option.fromPage == 'usercenter'){
+				this.hideZysx = true
+				uni.setNavigationBarTitle({
+					title: "服务协议"
+				})
+			}
+		},
+		methods: {
+			// 点击已阅读返回上一页
+			goBack () {
+				// 出发已阅读事件
+				uni.$emit('read',{msg:'read'})
+				uni.navigateBack()
+			}
+			
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		width: 100%;
+		height: 100%;
+	}
+	.content{
+		padding: 0;
+		width: 100%;
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		.page-content{
+			flex: 1;
+			overflow-y: scroll;
+		}
+		.title{
+			font-size: 30rpx;
+			font-weight: 600;
+		}
+		.xy-cons{
+			padding: 30rpx;
+			line-height: 42rpx;
+			.MsoNormal{
+				text-indent: 52rpx;
+				margin-bottom: 14rpx;
+			}
+		}
+		.page-care{
+			width: 100%;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			padding-top: 20rpx;
+		}
+		.footer{
+			width: 100%;
+			text-align: center;
+			line-height: 100rpx;
+			background-color: rgb(255,0,0);
+			color: #fff;
+		}
+	}
+</style>

+ 211 - 0
pages/index/index.vue

@@ -0,0 +1,211 @@
+<template>
+	<view class="homePage-container">
+		<!-- 筛选菜单 -->
+		<u-dropdown ref="uDropdown" class="filter-dropdown">
+			<u-dropdown-item v-model="queryParam.recordType" :title="titType" :options="optionsType" @change="searchHandle(1)"></u-dropdown-item>
+			<u-dropdown-item v-model="queryParam.recordScope" :title="titScope" :options="optionsScope" @change="searchHandle(1)"></u-dropdown-item>
+		</u-dropdown>
+		<scroll-view class="scroll-con" scroll-y @scrolltolower="onreachBottom">
+			<!-- 列表数据 -->
+			<view class="cell-item-con">
+				<view class="cell-item" v-for="(item, index) in listdata" :key="index">
+					<text class="cell-item-month">{{item.month}}</text>
+					<view class="cell-item-main" v-for="(subItem, ind) in item.list" :key="ind">
+						<text :class="['cell-item-pic', subItem.type == 1?'orange':'']">{{subItem.type == 1 ? '收' : '支'}}</text>
+						<view class="cell-item-c">
+							<text class="cell-item-des">{{subItem.dec}}</text>
+							<text class="cell-item-date">{{subItem.creatDate}}</text>
+						</view>
+						<text :class="['cell-item-score', Number(subItem.score) > 0 ? 'red' : 'black']">{{Number(subItem.score)>0 ? '+'+subItem.score : subItem.score}}</text>
+					</view>
+				</view>
+			</view>
+			<u-empty class="nodata" :text="noDataText" v-if="listdata.length==0 && status!='loading'" mode="list"></u-empty>
+			<view class="loadmore">
+				<u-loadmore v-if="total>pageSize || status=='loading'" :status="status" />
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				listdata: [
+					{
+						month: '本月',
+						list: [
+							{ type: 1, dec: '投递可回收物获得乐豆', creatDate: '2020-11-04 14:08', score: +25 },
+							{ type: 2, dec: '投递可回收物获得乐豆', creatDate: '2020-11-04 14:08', score: -30 },
+							{ type: 3, dec: '投递可回收物获得乐豆', creatDate: '2020-11-04 14:08', score: -15 },
+						]
+					},
+					{
+						month: '10月',
+						list: [
+							{ type: 1, dec: '投递可回收物获得乐豆', creatDate: '2020-11-04 14:08', score: +25 },
+							{ type: 2, dec: '投递可回收物获得乐豆', creatDate: '2020-11-04 14:08', score: -30 },
+							{ type: 3, dec: '投递可回收物获得乐豆', creatDate: '2020-11-04 14:08', score: -15 },
+						]
+					}
+				],
+				pageNo: 1,  //  当前页码
+				pageSize: 10,  //  每页条数
+				total: 0,  //  数据总条数
+				noDataText: '暂无数据',  //  列表请求状态提示语
+				status: 'loadmore',  //  加载中状态
+				queryParam: {  //  过滤条件
+					recordType: '',
+					recordScope: ''
+				},
+				optionsType: [
+					{ label: '所有记录', value: 1, },
+					{ label: '获取记录', value: 2, },
+					{ label: '使用记录', value: 3, }
+				]
+			}
+		},
+		onShow() {
+			this.searchHandle(1)
+		},
+		onLoad() {
+			
+		},
+		computed: {
+			optionsScope(){  //  过滤条件  近3个月
+				let arr = [
+					{ label: '近3个月', value: 1 },
+					{ label: '本月', value: 2 }
+				]
+				arr[2] = { label: new Date().getMonth() + '月', value: 3 }
+				arr[3] = { label: new Date().getMonth()-1 + '月', value: 4 }
+				return arr
+			},
+			titType(){  //  过滤条件 所有记录 title
+				const row = this.optionsType.find(item => item.value == this.queryParam.recordType)
+				return row ? row.label : this.optionsType[0].label
+			},
+			titScope(){  //  过滤条件 近3个月 title
+				const row = this.optionsScope.find(item => item.value == this.queryParam.recordScope)
+				return row ? row.label : this.optionsScope[0].label
+			}
+		},
+		methods:{
+			// 搜索查询
+			searchHandle (pageNo) {
+				this.status = "loading"
+				pageNo ? this.pageNo = pageNo : null
+				// searchSuppliers({
+				// 	pageNo: this.pageNo,
+				// 	pageSize: this.pageSize,
+				// 	queryWord: this.queryValue
+				// }).then(res => {
+				// 	if (res.status == 200) {
+				// 		if(this.pageNo>1){
+				// 			this.listdata = this.listdata.concat(res.data.list || [])
+				// 		}else{
+				// 			this.listdata = res.data.list || []
+				// 		}
+				// 		this.total = res.data.count || 0
+				// 		this.listdata.length == 0 && this.queryValue != '' ? this.noDataText = '没有搜索到相关结果' : this.noDataText = '暂无数据'
+				// 	} else {
+				// 		this.noDataText = res.message
+				// 		this.listdata = []
+				// 		this.total = 0
+				// 	}
+					this.status = "loadmore"
+				// })
+			},
+			// scroll-view到底部加载更多
+			onreachBottom() {
+				if(this.listdata.length < this.total){
+					this.pageNo += 1
+					this.searchHandle()
+				}else{
+					uni.showToast({ title: '已经到底了', icon: 'none' })
+					this.status = "nomore"
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.homePage-container {
+		width: 100%;
+		// 筛选菜单
+		.filter-dropdown{
+			background-color: #fff!important;
+			.u-dropdown__menu__item{
+				background-color: #fff!important;
+			}
+		}
+		//  列表
+		.scroll-con{
+			height: calc(100% - 40);
+			overflow: auto;
+		}
+		//  列表样式
+		.cell-item-con{
+			.cell-item{
+				background-color: #fff;
+				color: #606266;
+				.cell-item-month{
+					display: block;
+					color: #909399;
+					font-size: 26rpx;
+					padding: 16rpx 30rpx;
+					background-color: #f8f8f8;
+				}
+				.cell-item-main{
+					display: flex;
+					align-items: center;
+					margin: 0 30rpx;
+					padding: 20rpx 0;
+					border-bottom: 1rpx solid #e9e9e9;
+					.cell-item-pic{
+						flex-shrink: 0;
+						margin-right: 20rpx;
+						width: 90rpx;
+						height: 90rpx;
+						line-height: 90rpx;
+						background-color: #f3f4f6;
+						border-radius: 50%;
+						text-align: center;
+					}
+					.cell-item-c{
+						flex-grow: 1;
+						.cell-item-des{
+							display: block;
+							color: #606266;
+							font-size: 26rpx;
+							margin-bottom: 18rpx;
+						}
+						.cell-item-date{
+							display: block;
+							color: #909399;
+							font-size: 24rpx;
+						}
+					}
+					.cell-item-score{
+						flex-shrink: 0;
+					}
+					.black{
+						color: #000;
+					}
+					.red{
+						color: #fa3534;
+					}
+					.orange{
+						background-color: #fcbd71;
+						color: #fff;
+					}
+				}
+				.cell-item-main:last-child{
+					border: none;
+				}
+			}
+		}
+	}
+</style>

+ 261 - 0
pages/login/apply-trial.vue

@@ -0,0 +1,261 @@
+<template>
+	<view class="apply-wrap">
+		<form class="login-form" @submit="formSubmit">
+			<view class="form-item">
+				<text class="require-tip">*</text>
+				<input v-model="form.name" class="item-inp" name="name" type="text" placeholder="请输入企业名称" />
+			</view>
+			<view class="form-item">
+				<text class="require-tip">*</text>
+				<input v-model="areaInfo.label" class="item-inp" type="text" :disabled="true" placeholder="请选择省市区" @tap="openAddress" />
+				<Address ref="applyAddress" @onConfirm="areaConfirm"></Address>
+			</view>
+			<view class="form-item">
+				<input v-model="form.addrDetail" class="item-inp" name="addrDetail" type="text" placeholder="请输入详细地址" />
+			</view>
+			<view class="form-item">
+				<text class="require-tip">*</text>
+				<input v-model="form.contactPhone" class="item-inp" name="contactPhone" type="number" maxlength="11" placeholder="请输入手机号,该号码会作为系统登录账号" />
+			</view>
+			<view class="form-item">
+				<text class="require-tip">*</text>
+				<input v-model="form.verifyCode" class="item-inp" name="verifyCode" type="number" maxlength="6" placeholder="请输入手机验证码" />
+				<button class="getcode-btn" :disabled="isDisable" @click="getCodeValidate">{{nowVal}}</button>
+			</view>
+			<view class="form-item">
+				<input v-model="form.introPhone" class="item-inp" name="introPhone" type="number" maxlength="11" placeholder="请输入推荐人手机" />
+			</view>
+			<view class="form-btn-con">
+				<button class="form-btn" form-type="submit">提交</button>
+				<text class="apply-btn" @click="goLogin">已有账号?去登录</text>
+			</view>
+		</form>
+		<!-- 图文验证码 -->
+		<uni-popup ref="imageTxtPopup" type="center">
+			<!-- type值 用于区分applyImageTxt申请试用   forgetImageTxt忘记密码 -->
+			<popup-con type="applyImageTxt" title="图文验证码" :popBtn="popBtn" :changeImg="changeImg" @captchaImg="captchaImg"></popup-con>
+		</uni-popup>
+		<view class="authUserYs">
+			<checkbox-group @change="authuserChange">
+				<label>
+					<checkbox value="true" :checked="isAuthuserYs" class="login-form-checkbox" />
+					注册代表同意<text @click="toYsPage()">章鱼车管家用户隐私政策</text>
+				</label>
+			</checkbox-group>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { isvalidPhone } from '@/libs/validate.js'
+	import Address from '@/components/address.vue'
+	import uniPopup from '@/components/uni-popup/uni-popup.vue'
+	import popupCon from '@/components/uni-popup/popup-con.vue'
+	import { clzConfirm } from '@/libs/tools.js'
+	import { sendVerifyCode, apply, validateUser } from '@/api/user.js'
+	
+	export default{
+		components: {
+			Address,
+			uniPopup,
+			popupCon
+		},
+		data(){
+			return{
+				form: {
+					name: '',
+					orgType: '',
+					addrProvince: '',
+					addrCity: '',
+					addrDistrict: '',
+					addrDetail: '',
+					contactPhone: '',
+					introPhone: '',
+					verifyCode: ''
+				},
+				nowVal: '获取验证码',  //  按钮显示内容
+				count: '',  //  当前秒数
+				timer: null,  //  倒计时
+				isDisable: false,  //  是否禁止获取验证码
+				areaInfo: '',  //  省市区
+				areaIdArr: [],  //  省市区id数组
+				popBtn: [],  //  name为操作按钮名称,color为操作按钮颜色值
+				changeImg: false,  //  是否重新更换图形验证码
+				randomCode: '',  //  图片验证码随机码
+				isAuthuserYs: false // 是否同意隐私政策
+			}
+		},
+		mounted() {
+			this.isAuthuserYs = this.$store.state.vuex_isUsrAuthYs
+		},
+		methods: {
+			//  表单提交
+			formSubmit(){
+				//  表单校验
+				if(this.form.name == ''){
+					uni.showToast({icon: 'none',title: '请输入企业名称'})
+					return
+				}
+				//  省市区
+				if(this.areaIdArr.length == 0){
+					uni.showToast({icon: 'none',title: '请选择省市区'})
+					return
+				}
+				if(this.form.contactPhone == ''){
+					uni.showToast({icon: 'none',title: '请输入手机号码'})
+					return
+				}
+				if(!isvalidPhone(this.form.contactPhone)){
+				  uni.showToast({icon: 'none',title: '请输入正确的手机号码'})
+				  return
+				}
+				if(this.form.verifyCode == ''){
+					uni.showToast({icon: 'none',title: '请输入验证码'})
+					return
+				}
+				if(this.randomCode == ''){
+					uni.showToast({icon: 'none',title: '请输入正确的手机验证码'})
+					return
+				}
+				if(!this.isAuthuserYs){
+					uni.showToast({icon: 'none',title: '请同意并阅读用户隐私政策'})
+					return
+				}
+				let obj = this.form
+				obj.addrProvince = this.areaIdArr[0]  //  省id
+				obj.addrCity = this.areaIdArr[1]  //  市id
+				obj.addrDistrict = this.areaIdArr[2]  //  区id
+				obj.random = this.randomCode  //  图片验证码随机码
+				//  申请试用
+				apply(obj).then(res => {
+					console.log(res)
+					if(res.status == 200){
+						clzConfirm({
+						    title: '提示',
+						    content: '资料已提交成功,客服会在3个工作日内同您联系。请保持电话畅通,谢谢!',
+							showCancel: false,
+							success: function (ret) {
+						        if (ret.confirm||ret.index==0) {
+									uni.reLaunch({
+										url: '/pages/index'
+									})
+						        }
+						    }
+						})
+					}else{
+						uni.showToast({icon: 'none',title: res.message})
+					}
+				})
+			},
+			//  获取验证码
+			getCodeValidate(){
+				if(this.form.contactPhone){
+					if(!isvalidPhone(this.form.contactPhone)){
+						uni.showToast({icon: 'none',title: '请输入正确的手机号码'})
+						return
+					}else{
+						//  校验手机号是否注册过
+						validateUser({phone: this.form.contactPhone}).then(res => {
+							if(res.status == 200){
+								//  图文验证码
+								this.$refs.imageTxtPopup.open()
+							}else{
+								uni.showToast({icon: 'none',title: res.message})
+							}
+						})
+					}
+				}else{
+					uni.showToast({icon: 'none',title: '请先输入手机号'})
+				}
+			},
+			//  图文验证码
+			captchaImg(code, nowRandomCode){
+				this.captchaVerify(code, nowRandomCode)
+			},
+			//  验证图片验证码
+			captchaVerify(code, nowRandomCode){
+				const _this = this
+				let obj = {}
+				obj.contactPhone = this.form.contactPhone
+				obj.random = nowRandomCode
+				obj.captcha = code
+				//  短信验证码
+				sendVerifyCode(obj).then(res => {
+					console.log(JSON.stringify(res))
+					if(res.status == 200){  //  验证码输入正确
+						_this.randomCode = nowRandomCode  //  图片验证码随机码
+						//  关闭   图形验证码 弹框
+						_this.$refs.imageTxtPopup.close()
+						//  开启倒计时
+						this.getCodeFun()
+						uni.showToast({icon: 'none',title: '验证码发送成功'})
+					}else {  //  验证码输入错误
+						_this.randomCode = ''
+						//  切换验证码重新校验
+						_this.changeImg = false
+						_this.$nextTick(function(){
+							_this.changeImg = true
+						})
+						uni.showToast({icon: 'none',title: res.message})
+					}
+				})
+			},
+			//  开始倒计时
+			getCodeFun(){
+				const TIME_COUNT = 60
+				if (!this.timer) {
+				  this.count = TIME_COUNT
+				  this.timer = setInterval(() => {
+				    if (this.count > 0 && this.count <= TIME_COUNT) {
+				      this.count--
+				      this.nowVal = this.count + 's后重发'
+				      this.isDisable = true
+				    } else {
+				      this.nowVal = '重新获取验证码'
+				      clearInterval(this.timer)
+				      this.timer = null
+				      this.isDisable = false
+				    }
+				  }, 1000)
+				}
+			},
+			//  选择省区市
+			areaConfirm(e) {
+				console.log('已选择的省市区', e)
+				this.areaInfo = e
+				this.areaIdArr = [ e.provinceCode, e.cityCode, e.areaCode ]
+			},
+			openAddress(){
+				//  省市区  回显     参数为省市区id数组
+				this.$refs.applyAddress.open(this.areaIdArr)
+			},
+			//  去登录
+			goLogin(){
+				uni.navigateTo({
+				    url: '/pages/login/login'
+				})
+			},
+			// 隐私
+			authuserChange(e){
+				this.isAuthuserYs = e.detail.value.length > 0
+				this.$u.vuex('vuex_isUsrAuthYs', this.isAuthuserYs)
+			},
+			toYsPage(){
+				uni.navigateTo({
+					url:'/pages/login/userAuthYs'
+				})
+			}
+		}
+	}
+</script>
+<style scoped lang="scss">
+	@import './login.scss';
+	.apply-wrap .login-form{
+		padding-top: 80upx;
+	}
+	.getcode-btn {
+		background-color: #298bf2 !important;
+		color: #fff !important;
+		border: none !important;
+	}
+</style>

+ 229 - 0
pages/login/forget-pass.vue

@@ -0,0 +1,229 @@
+<template>
+	<view class="forget-wrap">
+		<form class="login-form" @submit="formSubmit">
+			<view v-show="state == 0">
+				<view class="form-item">
+					<input v-model="form.phone" class="item-inp" name="phone" type="number" maxlength="11" placeholder="请输入登录手机号" />
+				</view>
+				<view class="form-item">
+					<input v-model="form.verifyCode" class="item-inp" name="verifyCode" type="number" maxlength="6" placeholder="请输入短信验证码" />
+					<button class="getcode-btn" :disabled="isDisable" @click="getCodeValidate">{{nowVal}}</button>
+				</view>
+				<view class="form-btn-con">
+					<button class="form-btn" @click="goStep">下一步</button>
+				</view>
+			</view>
+			<view v-show="state == 1">
+				<view class="form-item">
+					<input v-model="form.password" class="item-inp" name="password" password type="text" maxlength="12" placeholder="请输入新密码" />
+				</view>
+				<view class="form-item">
+					<input v-model="form.passwords" class="item-inp" name="passwords" password type="text" maxlength="12" placeholder="请再次输入新密码" />
+				</view>
+				<view class="form-btn-con">
+					<button class="form-btn" form-type="submit">提交</button>
+				</view>
+			</view>
+		</form>
+		<!-- 图文验证码 -->
+		<uni-popup ref="imageTxtPopup" type="center">
+			<popup-con type="forgetImageTxt" title="图文验证码" :popBtn="popBtn" :changeImg="changeImg" @captchaImg="captchaImg"></popup-con>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+	import { isvalidPhone } from '@/libs/validate.js'
+	import uniPopup from '@/components/uni-popup/uni-popup.vue'
+	import popupCon from '@/components/uni-popup/popup-con.vue'
+	import { memberVerifyCode, memberChangePwd, memberValidateVerifyCode, memberGetByMobile } from '@/api/user.js'
+	import { clzConfirm } from '@/libs/tools.js'
+	export default{
+		components: {
+			uniPopup,
+			popupCon
+		},
+		data(){
+			return{
+				state: 0,  //  忘记密码当前状态
+				form: {
+					phone: '',
+					verifyCode: '',
+					password: '',
+					passwords: ''
+				},
+				nowVal: '获取验证码',  //  按钮显示内容
+				count: '',  //  当前秒数
+				timer: null,  //  倒计时
+				isDisable: false,  //  是否禁止获取验证码
+				popBtn: [],  //  name为操作按钮名称,color为操作按钮颜色值
+				changeImg: false,  //  是否重新更换图形验证码
+				randomCode: '',  //  图片验证码随机码
+			}
+		},
+		onLoad(opts) {
+			if(opts.username){
+				this.form.phone = opts.username
+			}
+		},
+		methods: {
+			//  下一步
+			goStep(){
+				const _this = this
+				//  表单校验
+				if(this.form.phone == ''){
+					uni.showToast({icon: 'none',title: '请输入登录手机号'})
+					return
+				}
+				if(!isvalidPhone(this.form.phone)){
+				  uni.showToast({icon: 'none',title: '请输入正确的手机号码'})
+				  return
+				}
+				if(this.form.verifyCode == ''){
+					uni.showToast({icon: 'none',title: '请输入验证码'})
+					return
+				}
+				//  校验短信验证码是否正确
+				const obj = {}
+				obj.phone = _this.form.phone,
+				obj.random = _this.randomCode,  // 当前随机码
+				obj.verifyCode = _this.form.verifyCode
+				memberValidateVerifyCode(obj).then(res => {
+				  if(res.status == 200){
+				    _this.state = 1
+				  }else{
+					  uni.showToast({icon: 'none',title: res.message})
+				  }
+				})
+			},
+			//  表单提交
+			formSubmit(){
+				const _this = this
+				if(this.form.password == ''){
+					uni.showToast({icon: 'none',title: '请输入新密码'})
+					return
+				}
+				if(this.form.password.length < 6 || this.form.password.length > 12){
+					uni.showToast({icon: 'none',title: '请输入6-12位新密码'})
+					return
+				}
+				if(this.form.passwords == ''){
+					uni.showToast({icon: 'none',title: '请再次输入新密码'})
+					return
+				}
+				if(this.form.password != this.form.passwords){
+					uni.showToast({icon: 'none',title: '两次密码输入不一致'})
+					return
+				}
+				const obj = {}
+				obj.phone = _this.form.phone,
+				obj.random = _this.randomCode,  // 当前随机码
+				obj.verifyCode = _this.form.verifyCode,
+				obj.password = _this.form.password
+				memberChangePwd(obj).then(res => {
+					if(res.status == 200){
+						//  将修改后的账号密码  重新存储
+						_this.$u.vuex('vuex_user.account', _this.form.phone)
+						_this.$u.vuex('vuex_user.password', '')
+						clzConfirm({
+						    title: '提示',
+						    content: '修改成功',
+							confirmText: '去登录',
+							showCancel: false,
+							success: function (ret) {
+						        if (ret.confirm||ret.index==0) {
+									uni.reLaunch({
+										url: '/pages/login/login'
+									})
+						        }
+						    }
+						})
+					}
+				})
+			},
+			//  获取验证码
+			getCodeValidate(){
+				if(this.form.phone){
+					if(!isvalidPhone(this.form.phone)){
+						uni.showToast({icon: 'none',title: '请输入正确的手机号码'})
+						return
+					}else{
+						//  校验手机号是否注册过
+						memberGetByMobile({phone: this.form.phone}).then(res => {
+							if(res.status == 200){
+								//  图文验证码
+								this.$refs.imageTxtPopup.open()
+							}else{
+								uni.showToast({icon: 'none',title: res.message})
+							}
+						})
+					}
+				}else{
+					uni.showToast({icon: 'none',title: '请先输入手机号'})
+				}
+			},
+			//  开始倒计时
+			getCodeFun(){
+				const TIME_COUNT = 60
+				if (!this.timer) {
+				  this.count = TIME_COUNT
+				  this.timer = setInterval(() => {
+				    if (this.count > 0 && this.count <= TIME_COUNT) {
+				      this.count--
+				      this.nowVal = this.count + 's后重发'
+				      this.isDisable = true
+				    } else {
+				      this.nowVal = '重新获取验证码'
+				      clearInterval(this.timer)
+				      this.timer = null
+				      this.isDisable = false
+				    }
+				  }, 1000)
+				}
+			},
+			//  图文验证码
+			captchaImg(code, nowRandomCode){
+				this.captchaVerify(code, nowRandomCode)
+			},
+			//  验证图片验证码
+			captchaVerify(code, nowRandomCode){
+				const _this = this
+				let obj = {}
+				obj.phone = this.form.phone
+				obj.random = nowRandomCode
+				obj.captcha = code
+				//  短信验证码
+				memberVerifyCode(obj).then(res => {
+					console.log(JSON.stringify(res))
+					if(res.status == 200){  //  验证码输入正确
+						_this.randomCode = nowRandomCode  //  图片验证码随机码
+						//  关闭   图形验证码 弹框
+						_this.$refs.imageTxtPopup.close()
+						//  开启倒计时
+						this.getCodeFun()
+						uni.showToast({icon: 'none',title: '验证码发送成功'})
+					}else {  //  验证码输入错误
+						_this.randomCode = ''
+						//  切换验证码重新校验
+						_this.changeImg = false
+						_this.$nextTick(function(){
+							_this.changeImg = true
+						})
+						uni.showToast({icon: 'none',title: res.message})
+					}
+				})
+			},
+		}
+	}
+</script>
+<style scoped lang="scss">
+	@import './login.scss';
+	.forget-wrap .login-form{
+		padding-top: 300upx;
+	}
+	.getcode-btn {
+		background-color: #298bf2 !important;
+		color: #fff !important;
+		border: none !important;
+	}
+</style>

+ 81 - 0
pages/login/login.scss

@@ -0,0 +1,81 @@
+.login-wrap, .forget-wrap, .apply-wrap{
+	background-color: #FFFFFF;
+	width: 100%;
+	height: 100vh;
+	box-sizing: border-box;
+	.logo{
+		display: block;
+		width: 300upx;
+		height: 106upx;
+		padding-top: 200upx;
+		margin: 0 auto;
+	}
+	.login-form{
+		display: block;
+		width: 80%;
+		margin: 0 auto;
+		padding-top: 110upx;
+		.form-item{
+			margin-bottom: 20upx;
+			position: relative;
+			display: flex;
+			align-items: center;
+			border-bottom: 1upx solid #dcdee2;
+			.require-tip{
+				position: absolute;
+				left: 0;
+				top: 26upx;
+				color: red;
+			}
+			.item-inp{
+				flex-grow: 1;
+			}
+			.item-inp, .item-picker{
+				font-size: 30upx;
+				padding: 20upx 20upx;
+			}
+			.getcode-btn{
+				position: absolute;
+				right: 20upx;
+				top: 0;
+				font-size: 24upx;
+				margin-top: 14upx;
+			}
+		}
+		.flex-box{
+			display: flex;
+			justify-content: space-between;
+			font-size: 26upx;
+		}
+		.form-btn-con{
+			margin-top: 100upx;
+			.form-btn{
+				font-size: 32upx;
+				display: block;
+				line-height: 84upx;
+				margin: 0 auto;
+				background-color: #298bf2;
+				color: #fff;
+			}
+			uni-button.form-btn:after{
+				border: none;
+			}
+			.apply-btn{
+				display: block;
+				text-align: center;
+				margin-top: 50upx;
+			}
+		}
+	}
+	
+	.authUserYs{
+		text-align: center;
+		position: absolute;
+		bottom: 5%;
+		width: 100%;
+		font-size: 24upx;
+		text{
+			color: #ff0000;
+		}
+	}
+}

+ 216 - 0
pages/login/login.vue

@@ -0,0 +1,216 @@
+<template>
+	<view class="login-wrap">
+		<view v-if="!buildType" style="text-align: center;color: #999999;padding-top: 40px;">{{ envTips }}</view>
+		<image src="../../static/logo-h.png" class="logo"></image>
+		<form class="login-form" @submit="formSubmit">
+			<view class="form-item">
+				<u-icon size="34" color="#999" name="account"></u-icon>
+				<input v-model="form.username" class="item-inp" name="username" type="number" maxlength="11" placeholder="请输入用户名" />
+			</view>
+			<view class="form-item">
+				<u-icon size="34" color="#999" name="lock"></u-icon>
+				<input v-model="form.password" class="item-inp" name="password" password type="text" maxlength="12" placeholder="请输入密码" />
+			</view>
+			<view class="flex-box" style="margin-top: 60upx;">
+				<view class="remember-pass">
+					<checkbox-group @change="rememberChange">
+						<label>
+							<checkbox value="true" :checked="isRemember" class="login-form-checkbox" />
+							<text>记住密码</text>
+						</label>
+					</checkbox-group>
+				</view>
+				<!-- <view class="forget-pass" @click="forgetPass">忘记密码</view> -->
+			</view>
+			<view class="form-btn-con">
+				<button class="form-btn" form-type="submit">登录</button>
+				<!-- <text class="apply-btn" @click="applyTrial">还没有账号?马上申请试用</text> -->
+			</view>
+		</form>
+		<view class="authUserYs">
+			<checkbox-group @change="authuserChange">
+				<label>
+					<checkbox value="true" :checked="isAuthuserYs" class="login-form-checkbox" />
+					登录代表同意
+					<text @click="toYsPage()">章鱼车管家用户协议及隐私保护政策</text>
+				</label>
+			</checkbox-group>
+		</view>
+	</view>
+</template>
+
+<script>
+import market from "@/js_sdk/dc-market/market.js"
+import { checkVersionToLoadUpdate, setStorageForAppVersion,clzConfirm } from '@/libs/tools'
+import { isvalidPhone } from '@/libs/validate.js';
+import { login } from '@/api/user.js';
+import { getSysVersion } from '@/api/data.js'
+export default {
+	data() {
+		return {
+			form: {
+				username: this.$store.state.vuex_user.account,
+				password: this.$store.state.vuex_user.password
+			},
+			isRemember: true, //  是否记住密码
+			isAuthuserYs: false, // 是否同意隐私政策
+			envTips: '',
+			buildType: ''
+		};
+	},
+	mounted() {
+		this.isRemember = this.$store.state.vuex_isRemember;
+		this.isAuthuserYs = this.$store.state.vuex_isUsrAuthYs;
+		this.envTips = getApp().globalData.envTips;
+		this.buildType = getApp().globalData.buildType;
+	},
+	onShow() {
+		//#ifdef APP-PLUS
+		  uni.getSystemInfo({
+			  success:(res) => {
+				  console.log(res)
+				  //检测当前平台,如果是安卓则启动安卓更新  
+				  if(res.platform=="android"){  
+					  // 获取最新版本信息
+					  plus.runtime.getProperty(plus.runtime.appid,(wgtinfo)=>{
+					  	getApp().globalData.version = wgtinfo
+					  	this.AndroidCheckUpdate()
+					  })
+				  }  
+			  }  
+		  })
+		//#endif
+	},
+	methods: {
+		// 安卓版本更新
+		AndroidCheckUpdate(){
+			let _this = this
+			getSysVersion().then(ret => {
+				if(ret.status == 200){
+					this.$store.state.vuex_versionInfo = ret.data
+					let currTimeStamp = new Date(ret.data.publicDate).getTime();
+					// 判断缓存时间
+					uni.getStorage({
+						key: 'tip_version_update_time',
+						success: function (res) {
+							let lastTimeStamp = res.data;
+							//定义提醒的时间间隔,避免烦人的一直提示,一个小时:36000;一天:60*60*1000*24
+							let tipTimeLength = 60*60*1000*3;
+							// 强制更新
+							if(ret.data.forceUpgrade == 1){
+								tipTimeLength = 0
+								//进行版本型号的比对 以及下载更新请求
+								checkVersionToLoadUpdate(ret.data,0,market);
+							}else{
+								if((lastTimeStamp+tipTimeLength) <= currTimeStamp){
+									//重新设置时间戳
+									setStorageForAppVersion(currTimeStamp);
+									//进行版本型号的比对 以及下载更新请求
+									checkVersionToLoadUpdate(ret.data,0,market);
+								}
+							}
+						},
+						fail:function(res){
+							setStorageForAppVersion(currTimeStamp);
+						}
+					});
+				}
+			})
+		},
+		// 过滤权限code
+		fiterAuthCode(data) {
+			data.permCodes = data.permCodes.filter(item => item.indexOf('_mobile') > 0);
+			return data;
+		},
+		//  表单提交
+		formSubmit() {
+			const _this = this;
+			//  表单校验
+			if (_this.form.username == '') {
+				uni.showToast({ icon: 'none', title: '请输入用户名' });
+				return;
+			}
+			if (!isvalidPhone(_this.form.username)) {
+				uni.showToast({ icon: 'none', title: '请输入正确的手机号码' });
+				return;
+			}
+			if (_this.form.password == '') {
+				uni.showToast({ icon: 'none', title: '请输入密码' });
+				return;
+			}
+			if (_this.form.password.length < 6 || _this.form.password.length > 12) {
+				uni.showToast({ icon: 'none', title: '请输入6-12位密码' });
+				return;
+			}
+			if (!_this.isAuthuserYs) {
+				uni.showToast({ icon: 'none', title: '请同意并阅读用户隐私政策' });
+				return;
+			}
+			uni.showLoading({
+				title: '正在登录...'
+			});
+			//  登录
+			login(_this.form).then(ret => {
+				uni.hideLoading();
+				let res = ret.data ? ret.data : ret;
+				if (res.status == 200) {
+					_this.$u.vuex('vuex_token', 'Bearer ' + res.access_token);
+					_this.$u.vuex('vuex_userData', _this.fiterAuthCode(res.auth_user));
+					getApp().globalData.token = 'Bearer ' + res.access_token;
+					//登录成功将用户名密码存储到用户本地
+					if (_this.isRemember) {
+						//用户勾选“记住密码”
+						_this.$u.vuex('vuex_user.account', _this.form.username);
+						_this.$u.vuex('vuex_user.password', _this.form.password);
+					} else {
+						//用户没有勾选“记住密码”
+						_this.$u.vuex('vuex_user.account', '');
+						_this.$u.vuex('vuex_user.password', '');
+						_this.form.username = '';
+						_this.form.password = '';
+					}
+					uni.setStorageSync('loginTimeOut', 'NO');
+					_this.toMain();
+				} else {
+					uni.showToast({ icon: 'none', title: res.message });
+				}
+			});
+		},
+		toMain() {
+			// 跳转到首页
+			uni.redirectTo({
+				url: '/pages/index'
+			});
+		},
+		//  申请试用
+		applyTrial() {
+			uni.navigateTo({
+				url: '/pages/login/apply-trial'
+			});
+		},
+		//  忘记密码
+		forgetPass() {
+			uni.navigateTo({
+				url: '/pages/login/forget-pass?username=' + this.form.username
+			});
+		},
+		//  判断是否记住密码
+		rememberChange(e) {
+			this.isRemember = e.detail.value.length > 0;
+		},
+		// 隐私
+		authuserChange(e) {
+			this.isAuthuserYs = e.detail.value.length > 0;
+			this.$u.vuex('vuex_isUsrAuthYs', this.isAuthuserYs);
+		},
+		toYsPage() {
+			uni.navigateTo({
+				url: '/pages/login/userAuthYs'
+			});
+		}
+	}
+};
+</script>
+<style scoped lang="scss">
+@import './login.scss';
+</style>

+ 66 - 0
pages/login/userAuthYs.vue

@@ -0,0 +1,66 @@
+<template>
+	<view class="article">
+		<view>章鱼车管家尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务,本应用会按照本隐私权政策的规定使用和披露您的个人信息。但本应用将以高度的勤勉、审慎义务对待这些信息。除本隐私权政策另有规定外,在未征得您事先许可的情况下,本应用不会将这些信息对外披露或向第三方提供。</view>
+		<view>本应用会不时更新本隐私权政策。 您在同意本应用服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本应用服务使用协议不可分割的一部分。</view>
+		<view>
+			<p>适用范围</p>
+			<p>(a) 在您注册本应用帐号时,您根据本应用要求提供的个人注册信息;</p>
+			<p>(b) 在您使用本应用网络服务,或访问本应用平台网页时,本应用自动接收并记录的您的浏览器和计算机上的信息,包括但不限于您的IP地址、浏览器的类型、使用的语言、访问日期和时间、软硬件特征信息及您需求的网页记录等数据;</p>
+			<p>(c) 本应用通过合法途径从商业伙伴处取得的用户个人数据。您了解并同意,以下信息不适用本隐私权政策:(a) 您在使用本应用平台提供的搜索服务时输入的关键字信息;</p>
+			<p>(b) 本应用收集到的您在本应用发布的有关信息数据,包括但不限于参与活动、成交信息及评价详情;</p>
+			<p>(c) 违反法律规定或违反本应用规则行为及本应用已对您采取的措施。</p>
+		</view>
+		<view>
+			<p>信息使用</p>
+			<p>(a) 本应用不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可,或该第三方和本应用(含本应用关联公司)单独或共同为您提供服务,且在该服务结束后,其将被禁止访问包括其以前能够访问的所有这些资料。</p>
+			<p>(b) 本应用亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。任何本应用平台用户如从事上述活动,一经发现,本应用有权立即终止与该用户的服务协议。</p>
+			<p>(c) 为服务用户的目的,本应用可能通过使用您的个人信息,向您提供您感兴趣的信息,包括但不限于向您发出产品和服务信息,或者与本应用合作伙伴共享信息以便他们向您发送有关其产品和服务的信息(后者需要您的事先同意)。</p>
+		</view>
+		<view>
+			<p>信息披露在如下情况下,本应用将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息:</p>
+			<p>(a) 经您事先同意,向第三方披露;</p>
+			<p>(b) 为提供您所要求的产品和服务,而必须和第三方分享您的个人信息;</p>
+			<p>(c) 根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;</p>
+			<p>(d) 如您出现违反中国有关法律、法规或者本应用服务协议或相关规则的情况,需要向第三方披露;</p>
+			<p>(e) 如您是适格的知识产权投诉人并已提起投诉,应被投诉人要求,向被投诉人披露,以便双方处理可能的权利纠纷;</p>
+			<p>(f) 在本应用平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出信息披露请求的,本应用有权决定向该用户提供其交易对方的联络方式等必要信息,以促成交易的完成或纠纷的解决。</p>
+			<p>(g) 其它本应用根据法律、法规或者网站政策认为合适的披露。</p>
+		</view>
+		<view><p>信息存储和交换本应用收集的有关您的信息和资料将保存在本应用及(或)其关联公司的服务器上,这些信息和资料可能传送至您所在国家、地区或本应用收集信息和资料所在地的境外并在境外被访问、存储和展示。</p></view>
+		<view>
+			<p>Cookie的使用</p>
+			<p>(a) 在您未拒绝接受cookies的情况下,本应用会在您的计算机上设定或取用cookies ,以便您能登录或使用依赖于cookies的本应用平台服务或功能。本应用使用cookies可为您提供更加周到的个性化服务,包括推广服务。</p>
+			<p>(b) 您有权选择接受或拒绝接受cookies。您可以通过修改浏览器设置的方式拒绝接受cookies。但如果您选择拒绝接受cookies,则您可能无法登录或使用依赖于cookies的本应用网络服务或功能。</p>
+			<p>(c) 通过本应用所设cookies所取得的有关信息,将适用本政策。</p>
+		</view>
+		<view>
+			<p>信息安全</p>
+			<p>(a) 本应用帐号均有安全保护功能,请妥善保管您的用户名及密码信息。本应用将通过对用户密码进行加密等安全措施确保您的信息不丢失,不被滥用和变造。尽管有前述安全措施,但同时也请您注意在信息网络上不存在“完善的安全措施”。</p>
+			<p>(b) 在使用本软件网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对方披露自己的个人信息,如联络方式或者邮政地址。请您妥善保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息泄密,请您立即联络本软件客服,以便本软件采取相应措施。</p>
+		</view>
+		<view>
+			<p>本隐私政策的更改</p>
+			<p>(a) 如果决定更改隐私政策,我们会在本政策中、本公司网站中以及我们认为适当的位置发布这些更改,以便您了解我们如何收集、使用您的个人信息,哪些人可以访问这些信息,以及在什么情况下我们会透露这些信息。</p>
+			<p>(b) 本公司保留随时修改本政策的权利,因此请经常查看。如对本政策作出重大更改,本公司会通过网站通知的形式告知。</p>
+		</view>
+    </view>
+</template>
+
+<script>
+</script>
+
+<style lang="scss">
+	.article{
+		 padding: 35upx 40upx;
+		 line-height: 46upx;
+		 >view{
+			 margin: 30upx 0;
+			 p{
+				 margin: 20upx 0;
+				 &:first-child{
+					 font-weight: bold;
+				 }
+			 }
+		 }
+	}
+</style>

+ 65 - 0
pages/userCenter/aboutUs.vue

@@ -0,0 +1,65 @@
+<template>
+	<view class="container">
+		<view class="logoContent">
+			<view class="logoBorder">
+				<!-- <image class="logoImg" src="/static/logo-us.png"></image> -->
+			</view>
+			<view>章鱼车管家信息管理平台</view>
+		</view>
+		<view class="textContent">
+			<view>陕西山海高科信息技术有限公司专注于汽车服务行业,成立于古城西安,是一家以先进云端互联网技术驱动的公司,致力于通过saas云门店管理系统为汽车综修厂、社区店以及大量的中小门店赋能。以解决汽车维修、美容、保养等业务中发生的一系列问题。</view>
+			<view>章鱼车管家设计了一套完善的降本增效方案,在引流获客、员工提成、营销活动、财务报表、数据分析等方面获得客户一致认可。</view>
+			<view>山海高科将通过持续的科技成果转化为推动汽后市场发展提供动力,帮助更多服务终端、供应链企业、主机厂商、银行以及保险等汽车后市场参与方转型升级。</view>
+		</view>
+	</view>
+</template>
+
+<script>
+</script>
+
+<style lang="less">
+	page{
+		background: #FFFFFF;
+	}
+	.container{
+		font-size: 32rpx;
+		padding:0 50rpx;
+		text-align: center;
+		color:#666;
+		.logoContent{
+			.logoBorder{
+				width:200rpx;
+				height:200rpx;
+				border:2px solid #eee;
+				border-radius: 20rpx;
+				margin:50rpx 0 15rpx 0;
+				display: inline-block;
+				position: relative;
+				.logoImg{
+					width:135rpx;
+					height:180rpx;
+					position: absolute;
+					top:0;
+					left:0;
+					right:0;
+					bottom:0;
+					margin:auto;
+				}
+			}
+		}
+		.textContent{
+			text-align: left;
+			margin-top: 30rpx;
+			font-size: 32rpx;
+			.tetxTitle{
+				margin-bottom: 10rpx;
+				font-size: 32rpx;
+			}
+			> view{
+				padding: 16upx 0;
+				line-height: 50upx;
+				text-indent: 60rpx;
+			}
+		}
+	}
+</style>

+ 188 - 0
pages/userCenter/changePwd.vue

@@ -0,0 +1,188 @@
+<template>
+	<view class="changePwd-container">
+		<view class="content">
+			<view class="tishi">请先输入原密码,按步骤操作</view>
+			<view class="input-group">
+				<view class="input-row border">
+					<text class="title">原密码:</text>
+					<m-input class="m-input"  type="password"  displayable v-model="formCustom.oldPwd" placeholder="请输入原密码"></m-input>
+				</view>
+				<view class="input-row">
+					<text class="title">新密码:</text>
+					<m-input class="m-input" :maxlength="12" type="password" displayable v-model="formCustom.passwd" placeholder="请输入6-12位新密码"></m-input>
+				</view>
+				<view class="input-row">
+					<text class="title">再次输入:</text>
+					<m-input class="m-input" :maxlength="12" type="password" displayable v-model="formCustom.passwdCheck" placeholder="请输入6-12位新密码"></m-input>
+				</view>
+			</view>
+		</view>
+		<!-- 消息提醒弹窗 -->
+		<view class="form-footer-btn">
+			<button @click="handleReset" type="info">
+				重置
+			</button>
+			<button @click="handleSubmit" :loading="loading" type="primary">
+				保存
+			</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import mInput from '../../components/m-input.vue'
+	import { changePwd, logout } from '@/api/user.js'
+	export default{
+		name:'changePwd',
+		components: {
+			mInput
+		},
+		data(){
+			return{
+				formCustom: {
+				  passwd: '',
+				  passwdCheck: '',
+				  oldPwd: ''
+				},
+				loading: false
+			}
+		},
+		methods:{
+			messageInfo(info){
+				uni.showToast({
+					icon:'none',
+					title: info
+				})
+			},
+			handleSubmit(){
+				let _this = this
+				//非空判断密码长度判断
+				if (this.formCustom.oldPwd === '') {
+				   this.messageInfo("请输入原密码")
+				   return 
+				}  
+				if (this.formCustom.passwd === '') {
+				   this.messageInfo("请输入新密码")
+				   return 
+				}
+				if (this.formCustom.passwd.length < 6) {
+				   this.messageInfo("新密码最少6位")
+				   return 
+				}
+				if (this.formCustom.passwdCheck === '') {
+				   this.messageInfo("请再次输入新密码")
+				   return 
+				}
+				if (this.formCustom.passwdCheck.length < 6) {
+				   this.messageInfo("再次输入新密码最少6位")
+				   return 
+				}
+				if (this.formCustom.passwdCheck !== this.formCustom.passwd) {
+				   this.messageInfo("再次输入的密码和新密码不一样")
+				   return 
+				}
+				// 保存
+				this.loading = true
+				changePwd({
+				  oldPassword: this.formCustom.oldPwd,
+				  password: this.formCustom.passwd
+				}).then(res => {
+				  if (res.status == '200') {
+				    this.messageInfo('修改成功, 请重新登录')
+				    setTimeout(function(){
+						_this.logout()
+					},800)
+				  } else {
+				    this.messageInfo(res.message)
+					this.loading = false
+				  }
+				})
+			},
+			logout(){
+				logout().then(res => {
+					console.log(res)
+					this.messageInfo(res.message)
+					if(res.status == 200){
+						this.$store.commit("$closeWebsocket")
+						this.$store.state.vuex_token = ''
+						this.$store.state.vuex_userData = ''
+						uni.setStorageSync('setStatusIndexFunc', 0)
+						uni.reLaunch({
+							url: "/pages/login/login"
+						})
+					}
+					this.loading = false
+				})
+			},
+			handleReset (name) {
+				this.formCustom = {
+				  passwd: '',
+				  passwdCheck: '',
+				  oldPwd: ''
+				}
+			}
+		}
+	}
+	
+</script>
+
+<style lang="less">
+	page{
+		background: #FFFFFF;
+	}
+	.changePwd-container{
+		width: 100%;
+		font-size: 28rpx;
+		padding: 20rpx 40rpx ;
+		.content{
+			.tishi{
+				text-align: center;
+				line-height: 130rpx;
+				font-size: 30rpx;
+				color: #999;
+			}
+			.input-group{
+				margin-top:20rpx;
+			}
+			.input-row{
+				padding: 10rpx 20rpx;
+				background-color: #F8F8F8;
+				margin-bottom: 20rpx;
+				border-radius: 10rpx;
+				line-height: 32px;
+				display: flex;
+				.title{
+					width:140rpx;
+					display: inline-block;
+					text-align: left;
+					color:#808080;
+				}
+			}
+			.uni-input-placeholder{
+				font-size: 26rpx;
+				color:#989898;
+			}
+		}
+		.u-size-default[data-v-1f520a08]{
+			height: 92rpx;
+		}
+		.m-input-input[data-v-5a85b703]{
+			position: relative;
+			bottom: 4rpx;
+		}
+		.m-input-icon[data-v-5a85b703]{
+			position: relative;
+			bottom: 10rpx;
+		}
+		.form-footer-btn{
+			margin: 50upx;
+			display: flex;
+			justify-content: center;
+		}
+		.form-footer-btn uni-button{
+			width: 50%;
+			margin: 0 50upx;
+			font-size: 30upx;
+		}
+	}
+</style>

+ 133 - 0
pages/userCenter/index.vue

@@ -0,0 +1,133 @@
+<template>
+	<view class="user-content">
+		<!-- 头部 -->
+		<view class="user-head">
+			<view class="user-photo">
+				<u-image width="130rpx" height="130rpx" shape="circle" :src="userAvatarUrl"></u-image>
+			</view>
+			<view class="user-info">
+				<text class="user-name">userName</text>
+				<text class="user-phone">15778787878</text>
+			</view>
+		</view>
+		<!-- 列表 -->
+		<view class="user-list">
+			<u-cell-group>
+				<u-cell-item icon="file-text" icon-size="40" :icon-style="{color:'#ffaa00'}" index="0" @click="toPage" title="服务协议及隐私政策"></u-cell-item>
+				<u-cell-item icon="info-circle" icon-size="38" :icon-style="{color:'#55aa00'}" index="1" @click="toPage" title="关于我们"></u-cell-item>
+			</u-cell-group>
+		</view>
+		<!-- 退出 -->
+		<view class="user-btn">
+			<u-button type="success" :custom-style="quitButton" @click="quitSys" shape="circle">退出登录</u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {logout} from '@/api/login.js'
+	export default {
+		data() {
+			return {
+				quitButton: {
+					borderRadius:'100rpx',
+					fontSize:'30rpx',
+					background: '#01c9b2'
+				},
+				userAvatarUrl: ''  //  用户头像
+			};
+		},
+		onShow() {
+		},
+		onLoad() {
+		},
+		methods:{
+			// 打开对应的页面
+			toPage(index){
+				let pageUrl=[
+					'/pages/agreement/agreement?fromPage=usercenter',
+					'/pages/userCenter/aboutUs'
+					]
+				uni.navigateTo({
+					url: pageUrl[index]
+				})
+			},
+			//  跳转
+			toUserPage(){
+				//  未登录跳转登录,已登录跳转用户信息页
+				uni.navigateTo({
+					url: '/pages/userCenter/userInfo/userInfo'
+				})
+			},
+			//  乐豆明细
+			toLdPage(){
+				uni.navigateTo({
+					url: '/pages/userCenter/ldDetailed'
+				})
+			},
+			bindGetUserInfo(res){
+				console.log(res)
+			},
+			// 退出登录
+			quitSys(){
+				let _this = this
+				uni.showModal({
+				  title: '提示',
+				  content: '您确定要退出登录吗?',
+				  success(res) {
+				    if (res.confirm) {
+				      logout({}).then(res => {
+				       getApp().globalData.token = ''
+				       _this.$u.vuex('vuex_token','')
+					   // 关闭socket
+					   _this.$store.commit("$closeWebsocket")
+				        uni.reLaunch({
+				        	url: '/pages/index/index'
+				        })
+				      })
+				    }
+				  }
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.user-content{
+		width: 100%;
+		.user-head{
+			background: #01c9b2;
+			display: flex;
+			align-items: center;
+			padding: 35rpx 55rpx;
+			.user-photo{
+				flex-shrink: 0;
+				margin-right: 5rpx;
+			}
+			.user-info{
+				flex-grow: 1;
+				padding: 0 20rpx;
+				color: #FFFFFF;
+				.user-name{
+					display: block;
+					font-size: 30rpx;
+					margin-bottom: 20rpx;
+				}
+				.user-phone{
+					display: block;
+					font-size: 26rpx;
+				}
+			}
+		}
+		.user-list{
+			border-radius: 15upx;
+			margin: 20upx;
+			overflow: hidden;
+			box-shadow: 0upx 3upx 6upx #eee;
+		}
+		.user-btn{
+			padding: 85rpx 200rpx;
+		}
+	}
+</style>

BIN
static/logo-h.png


BIN
static/tabbar/home-active.png


BIN
static/tabbar/home.png


BIN
static/tabbar/shop-active.png


BIN
static/tabbar/shop.png


BIN
static/tabbar/user-active.png


BIN
static/tabbar/user.png


+ 27 - 0
store/$u.mixin.js

@@ -0,0 +1,27 @@
+import { mapState } from 'vuex'
+import store from "@/store"
+
+// 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中
+let $uStoreKey = [];
+try{
+	$uStoreKey = store.state ? Object.keys(store.state) : [];
+}catch(e){
+	
+}
+
+module.exports = {
+	created() {
+		// 将vuex方法挂在到$u中
+		// 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗')
+		// 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1')
+		this.$u.vuex = (name, value) => {
+			this.$store.commit('$uStore', {
+				name,value
+			})
+		}
+	},
+	computed: {
+		// 将vuex的state中的所有变量,解构到全局混入的mixin中
+		...mapState($uStoreKey)
+	}
+}

+ 176 - 0
store/index.js

@@ -0,0 +1,176 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+Vue.use(Vuex)
+
+let lifeData = {};
+
+try {
+	// 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的
+	lifeData = uni.getStorageSync('lifeData');
+} catch (e) {
+
+}
+
+// 需要永久存储,且下次APP启动需要取出的,在state中的变量名
+let saveStateKeys = [
+	'vuex_user',
+	'vuex_token',
+	'vuex_userData',
+	'vuex_payType',
+	'vuex_payStatus',
+	'vuex_storeId',
+	'vuex_bizId',
+	'vuex_orderNo',
+	'vuex_orderStatus',
+	'vuex_cartNums'
+];
+
+// 保存变量到本地存储中
+const saveLifeData = function(key, value) {
+	// 判断变量名是否在需要存储的数组中
+	if (saveStateKeys.indexOf(key) != -1) {
+		// 获取本地存储的lifeData对象,将变量添加到对象中
+		let tmp = uni.getStorageSync('lifeData');
+		// 第一次打开APP,不存在lifeData变量,故放一个{}空对象
+		tmp = tmp ? tmp : {};
+		tmp[key] = value;
+		// 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中
+		uni.setStorageSync('lifeData', tmp);
+	}
+}
+
+// websocket 连接
+const SocketInit = function($store,state,wsUrl){
+	let token = getApp().globalData.token
+	// 开始连接
+	state.vuex_socket = uni.connectSocket({
+		header: {
+			'X-AUTH-TOKEN': token
+		},
+		url: wsUrl, // ws 请求地址
+		complete: () => {
+			console.log('连接complete回调!')
+		}
+	});
+	// 打开连接
+	state.vuex_socket.onOpen(function(e) {
+		console.log('连接成功!')
+		uni.$emit('wsMessage', 'connectOpen')
+	})
+	// 连接关闭
+	state.vuex_socket.onClose(function(e) {
+		console.log('ws关闭!')
+	})
+	// 连接错误
+	state.vuex_socket.onError(function(e) {
+		console.log('ws错误!', JSON.stringify(e))
+	})
+	// 接受消息
+	state.vuex_socket.onMessage(function(e) {
+		console.log(e,'onmessage')
+		if (e.data == 'pong' || e.data == 'connected') {
+			uni.$emit('wsMessage', e.data)
+		}else{
+			uni.$emit('wsMessage', JSON.parse(e.data))
+		}
+	})
+	// 发送心跳包
+	state.vuex_heartInterId = setInterval(function() {
+		// console.log(state.vuex_socket.readyState)
+		if (state.vuex_socket.readyState != 1) {
+			 // 停止心跳
+			 clearInterval(state.vuex_heartInterId)
+			 SocketInit($store,state,wsUrl)
+			 uni.$emit('wsMessage','reconnect')
+		} else {
+			$store.commit("$sendWebsocket", "ping")
+		}
+	}, 20000)
+}
+
+// store 对象
+const store = new Vuex.Store({
+	// 下面这些值仅为示例,使用过程中请删除
+	state: {
+		// 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量
+		// 加上vuex_前缀,是防止变量名冲突,也让人一目了然
+		vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {
+			account: '',
+			password: ''
+		},
+		vuex_token: lifeData.vuex_token ? lifeData.vuex_token : '',
+		vuex_userData: lifeData.vuex_userData ? lifeData.vuex_userData : '',
+		vuex_payType: lifeData.vuex_payType ? lifeData.vuex_payType : [], // 支付方式数据字典
+		vuex_payStatus: lifeData.vuex_payStatus ? lifeData.vuex_payStatus : [], // 支付状态数据字典
+		// 订单信息
+		vuex_storeId: lifeData.vuex_storeId ? lifeData.vuex_storeId : '',// 网点id
+		vuex_bizId: lifeData.vuex_bizId ? lifeData.vuex_bizId : '',// 设备编号
+		vuex_orderNo: lifeData.vuex_orderNo ? lifeData.vuex_orderNo : '',// 订单编号
+		vuex_orderStatus: lifeData.vuex_orderStatus ? lifeData.vuex_orderStatus : '',// 订单状态
+		// 购物车及下单
+		vuex_cartList: [],
+		vuex_toOrderList: [],
+		// 如果无需保存到本地永久存储,无需lifeData.xx方式
+		vuex_socket: null,
+		vuex_wsMessageData: '',
+		vuex_heartInterId: null,
+		// 当前网络连接状态
+		vuex_netStatus: '',
+		// 客服电话
+		vuex_kfMobile: ''
+	},
+	mutations: {
+		$uStore(state, payload) {
+			// 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1
+			let nameArr = payload.name.split('.');
+			let saveKey = '';
+			let len = nameArr.length;
+			if (nameArr.length >= 2) {
+				let obj = state[nameArr[0]];
+				for (let i = 1; i < len - 1; i++) {
+					obj = obj[nameArr[i]];
+				}
+				obj[nameArr[len - 1]] = payload.value;
+				saveKey = nameArr[0];
+			} else {
+				// 单层级变量,在state就是一个普通变量的情况
+				state[payload.name] = payload.value;
+				saveKey = payload.name;
+			}
+			// 保存变量到本地,见顶部函数定义
+			saveLifeData(saveKey, state[saveKey])
+		},
+		// 创建通知消息websocket
+		$webSocket(state, params) {
+			let _this = this
+			let url = getApp().globalData.baseUrl
+			let wsBaseUrl = url.indexOf("https:") >= 0 ? url.replace("https:", "wss:") : url.replace("http:", "ws:")
+			let wsUrl = wsBaseUrl + 'websocket/weixin/' + state.vuex_bizId
+			console.log(wsUrl,'wsUrl')
+			SocketInit(_this,state,wsUrl)
+		},
+		// 关闭websocket
+		$closeWebsocket(state) {
+			if (state.vuex_socket) {
+				state.vuex_socket.close({
+					complete: function(e) {
+						console.log('关闭websocket完成', e)
+						// 停止心跳
+						clearInterval(state.vuex_heartInterId)
+					}
+				})
+			}
+		},
+		// 发送消息
+		$sendWebsocket(state, msg) {
+			state.vuex_socket.send({
+				data: msg,
+				complete: function(e) {
+					console.log('ws发送完成', msg)
+				}
+			})
+		},
+	}
+})
+
+export default store

+ 94 - 0
uni.scss

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

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