zhangdan 4 سال پیش
کامیت
e09bc771f0
79فایلهای تغییر یافته به همراه9064 افزوده شده و 0 حذف شده
  1. 4 0
      .gitignore
  2. 11 0
      .hbuilderx/launch.json
  3. 53 0
      App.vue
  4. 37 0
      api/data.js
  5. 35 0
      api/index.js
  6. 49 0
      api/login.js
  7. 38 0
      api/user.js
  8. 443 0
      components/address.vue
  9. 721 0
      components/almost-lottery/almost-lottery.vue
  10. 124 0
      components/m-input.vue
  11. 96 0
      components/uni-icons/icons.js
  12. 10 0
      components/uni-icons/uni-icons.vue
  13. 178 0
      components/uni-numbers-box/uni-numbers-box.vue
  14. 122 0
      components/uni-popup/image-txt-popup.vue
  15. 22 0
      components/uni-popup/message.js
  16. 96 0
      components/uni-popup/popup-con.vue
  17. 25 0
      components/uni-popup/popup.js
  18. 244 0
      components/uni-popup/uni-popup-dialog.vue
  19. 116 0
      components/uni-popup/uni-popup-message.vue
  20. 165 0
      components/uni-popup/uni-popup-share.vue
  21. 295 0
      components/uni-popup/uni-popup.vue
  22. 279 0
      components/uni-transition/uni-transition.vue
  23. 0 0
      components/w-picker/areadata/areadata.js
  24. 742 0
      components/w-picker/date-picker.vue
  25. 345 0
      components/w-picker/half-picker.vue
  26. 274 0
      components/w-picker/linkage-picker.vue
  27. 344 0
      components/w-picker/range-picker.vue
  28. 183 0
      components/w-picker/region-picker.vue
  29. 129 0
      components/w-picker/selector-picker.vue
  30. 250 0
      components/w-picker/shortterm-picker.vue
  31. 218 0
      components/w-picker/time-picker.vue
  32. 26 0
      components/w-picker/w-picker.css
  33. 341 0
      components/w-picker/w-picker.vue
  34. 82 0
      libs/axios.js
  35. 95 0
      libs/getDate.js
  36. 70 0
      libs/tools.js
  37. 5 0
      libs/validate.js
  38. 17 0
      main.js
  39. 85 0
      manifest.json
  40. 56 0
      package-lock.json
  41. 23 0
      package.json
  42. 88 0
      pages.json
  43. 223 0
      pages/agreement/agreement.vue
  44. 326 0
      pages/index/addData.vue
  45. 398 0
      pages/index/index.vue
  46. 196 0
      pages/index/searchModal.vue
  47. 85 0
      pages/login/login.scss
  48. 235 0
      pages/login/login.vue
  49. 79 0
      pages/login/loginPw.scss
  50. 166 0
      pages/login/phoneLogin.vue
  51. 62 0
      pages/userCenter/aboutUs.vue
  52. 214 0
      pages/userCenter/changePwd.vue
  53. 231 0
      pages/userCenter/index.vue
  54. BIN
      static/CLOTHES.png
  55. BIN
      static/camera.png
  56. BIN
      static/edit.png
  57. BIN
      static/filter.png
  58. BIN
      static/imgError.png
  59. BIN
      static/index/chuyu.png
  60. BIN
      static/index/jianzhu.png
  61. BIN
      static/index/logo-active.png
  62. BIN
      static/index/logo.png
  63. BIN
      static/index/other.png
  64. BIN
      static/loading.gif
  65. BIN
      static/login_icon.png
  66. BIN
      static/login_slogan.png
  67. BIN
      static/logo.jpg
  68. BIN
      static/logo.png
  69. BIN
      static/tabbar/home-active.png
  70. BIN
      static/tabbar/home.png
  71. BIN
      static/tabbar/shop-active.png
  72. BIN
      static/tabbar/shop.png
  73. BIN
      static/tabbar/user-active.png
  74. BIN
      static/tabbar/user.png
  75. BIN
      static/user_icon_number.png
  76. BIN
      static/user_icon_password.png
  77. 27 0
      store/$u.mixin.js
  78. 178 0
      store/index.js
  79. 108 0
      uni.scss

+ 4 - 0
.gitignore

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

+ 11 - 0
.hbuilderx/launch.json

@@ -0,0 +1,11 @@
+{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
+  // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
+    "version": "0.0",
+    "configurations": [{
+            "type": "uniCloud",
+            "default": {
+                "launchtype": "remote"
+            }
+        }
+    ]
+}

+ 53 - 0
App.vue

@@ -0,0 +1,53 @@
+<script>
+	// const uat_domain = 'https://gather.test.sxzxyj.net' // 预发布
+	const uat_domain = 'http://192.168.16.217:8305' // 本地
+	const pro_domain = 'https://gather.zyucgj.cn' // 生产
+	const uat_URL = uat_domain+'/gc-gather/' // 预发布
+	const pro_URL = pro_domain+'/gc-gather/'  // 生产
+	const buildType = 1 // 打包环境对应类型,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: {  
+			baseDomain: process.env.NODE_ENV == 'development' ? uat_domain : pro_domain,
+			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;
+	}
+	.content {
+		padding: 20upx;
+	}
+</style>

+ 37 - 0
api/data.js

@@ -0,0 +1,37 @@
+import axios from '@/libs/axios.js';
+// 根据字典code查询选项
+export const getLookUpDatas = (params) => {
+  return axios.request({
+    url: `lookup/findByLookup/${params.type}`,
+    method: 'get'
+  })
+}
+// 开箱操作
+export const openDevice = (params) => {
+  return axios.request({
+    url: `device/ctl/open`,
+    method: 'post',
+	data: params
+  })
+}
+//查询省
+export const  getProvince = () => {
+  return axios.request({
+    url: `area/PROVINCE`,
+    method: 'get'
+  })
+}
+//查询市
+export const  getCityByPro = params => {
+  return axios.request({
+    url: `area/CITY/${params.id}`,
+    method: 'get'
+  })
+}
+//查询区
+export const  getDistrictByCity = params => {
+  return axios.request({
+    url: `area/DISTRICT/${params.id}`,
+    method: 'get'
+  })
+}

+ 35 - 0
api/index.js

@@ -0,0 +1,35 @@
+import axios from '@/libs/axios.js'
+// 新增采集信息
+export const gatherList = params => {
+  return axios.request({
+    url: `gather/query/${params.pageNo}/${params.pageSize}`,
+    method: 'post',
+	data: params
+  })
+}
+
+// 查询采集信息详情
+export const gatherDetail = (params) => {
+  return axios.request({
+    url: `gather/findById/${params.id}`,
+    method: 'get',
+  })
+}
+
+// 采集信息总计
+export const gatherTotal = (params) => {
+  return axios.request({
+    url: `gather/sumByOperator`,
+    method: 'post',
+	data: params
+  })
+}
+
+// 保存采集信息详情
+export const gatherSave = (params) => {
+  return axios.request({
+    url: `gather/save`,
+    method: 'post',
+	data:params
+  })
+}

+ 49 - 0
api/login.js

@@ -0,0 +1,49 @@
+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: `user/userInfo`,
+    method: 'get'
+  })
+};
+export const loginPhone = params => {
+  return axios.request({
+    url: `loginPhone`,
+    method: 'post',
+	data: params
+  },true)
+};

+ 38 - 0
api/user.js

@@ -0,0 +1,38 @@
+import axios from '@/libs/axios.js'
+ 
+// 获取图片验证码
+export const getCaptcha = randomCode => {
+  return axios.request({
+    url: `captcha/create/${randomCode}`,
+    method: 'get',
+    responseType: 'blob',
+  }, true)
+}
+// 发送短信验证码
+export const sendVerifyCode = params => {
+  return axios.request({
+    url: 'sendVerifiCode',
+    method: 'post',
+    data: params
+  }, true)
+}
+
+// 修改密码
+export const changePwd = params => {
+  return axios.request({
+    url: 'user/updatePwd',
+    method: 'post',
+    data: {
+      oldPassword: params.oldPassword,
+      password: params.password
+    }
+  }).then(res => res)
+} 
+// 手机号登录 校验用户账号状态
+export const checkCustomerState = params => { 
+  let url = `checkCustomerState/${params.phone}`
+  return axios.request({
+    url: url,
+    method: 'get'
+  })
+}

+ 443 - 0
components/address.vue

@@ -0,0 +1,443 @@
+<template>
+	<view class="simple-address" v-if="showPopup">
+		<!-- 遮罩层 -->
+		<view
+			class="simple-address-mask"
+			v-if="maskClick"
+			:class="[ani + '-mask', animation ? 'mask-ani' : '']"
+			:style="{
+				'background-color': maskBgColor
+			}"
+			@tap="hideMask(true)"
+		></view>
+		
+		<!-- 省市区 -->
+		<view class="simple-address-content simple-address--fixed" :class="[type, ani + '-content', animation ? 'content-ani' : '']">
+			<view class="simple-address__header">
+				<view class="simple-address__header-btn-box" @click="pickerCancel">
+					<text class="simple-address__header-text" :style="{ color: cancelColor, fontSize: btnFontSize }">取消</text>
+				</view>
+				<view class="simple-address__header-btn-box" @click="pickerConfirm">
+					<text class="simple-address__header-text" :style="{ color: confirmColor, fontSize: btnFontSize }">确定</text>
+				</view>
+			</view>
+			<view class="simple-address__box">
+				<picker-view class="simple-address-view" :value="pickerValue" @change="pickerChange">
+					<picker-view-column>
+						<!-- #ifndef APP-NVUE -->
+						<view class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in provinceDataList" :key="index">{{ item.name }}</view>
+						<!-- #endif -->
+						<!-- #ifdef APP-NVUE -->
+						<text class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in provinceDataList" :key="index">{{ item.name }}</text>
+						<!-- #endif -->
+					</picker-view-column>
+					<picker-view-column>
+						<!-- #ifndef APP-NVUE -->
+						<view class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in cityDataList" :key="index">{{ item.name }}</view>
+						<!-- #endif -->
+						<!-- #ifdef APP-NVUE -->
+						<text class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in cityDataList" :key="index">{{ item.name }}</text>
+						<!-- #endif -->
+					</picker-view-column>
+					<picker-view-column>
+						<!-- #ifndef APP-NVUE -->
+						<view class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in areaDataList" :key="index">{{ item.name }}</view>
+						<!-- #endif -->
+						<!-- #ifdef APP-NVUE -->
+						<text class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in areaDataList" :key="index">{{ item.name }}</text>
+						<!-- #endif -->
+					</picker-view-column>
+				</picker-view>
+			</view>
+		</view>
+		
+	</view>
+</template>
+
+<script>
+	import { getProvince, getCityByPro, getDistrictByCity } from '@/api/data.js'
+	export default{
+		name: 'Address',
+		props: {
+			// 是否允许点击遮罩层关闭
+			maskClick: {
+				type: Boolean,
+				default: true
+			},
+			// 是否开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			//  遮罩层背景颜色
+			maskBgColor: {
+				type: String,
+				default: 'rgba(0, 0, 0, 0.4)' //背景颜色 rgba(0, 0, 0, 0.4) 为空则调用 uni.scss
+			},
+			/* 弹出层类型,可选值;  暂时只支持底部弹出
+					bottom:底部弹出层
+				*/
+			type: {
+				type: String,
+				default: 'bottom'
+			},
+			cancelColor: {
+				type: String,
+				default: '' // 取消按钮颜色
+			},
+			confirmColor: {
+				type: String,
+				default: '' // 确认按钮颜色
+			},
+			fontSize: {
+				type: String,
+				default: '28rpx' // picker-item字体大小
+			},
+			btnFontSize: {
+				type: String,
+				default: '' // 按钮的字体大小
+			},
+		},
+		data(){
+			return{
+				showPopup: false,  //  是否显示省市区选择
+				ani: '',
+				pickerValue: [0, 0, 0],  //  默认值下标
+				pickerId: [],  //  省市区默认值id
+				provinceDataList: [],  //  省数据列表
+				cityDataList: [],  //  市数据列表
+				areaDataList: [],  //  区数据列表
+				isFirst: false,  //  是否首次进入组件
+			}
+		},
+		methods: {
+			//  初始化
+			init(){
+				this.provinceDataList = []
+				this.cityDataList = []
+				this.areaDataList = []
+				this.isFirst = false
+			},
+			//  点击遮罩层关闭选择省市区
+			hideMask() {
+				this._$emit('onCancel')
+				this.close()
+			},
+			//  打开省市区picker
+			open(idArr) {
+				//  初始化数据
+				this.init()
+				this.pickerId = idArr
+				//  idArr 省市区默认id
+				if(this.pickerId && this.pickerId.length > 0){
+					//  获取省下拉
+					this.getProvinceList(idArr)
+				}else{
+					//  获取省下拉
+					this.getProvinceList()
+				}
+				this.showPopup = true
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.ani = 'simple-' + this.type
+					}, 100)
+				});
+			},
+			//  关闭省市区picker
+			close(type) {
+				if (!this.maskClick && type) return
+				this.ani = ''
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.showPopup = false
+					}, 300)
+				})
+			},
+			//  取消按钮
+			pickerCancel() {
+				this._$emit('onCancel')
+				this.close()
+			},
+			//  确定按钮
+			pickerConfirm() {
+				this._$emit('onConfirm')
+				if(!this.isFirst){
+					//  首次
+					this.isFirst = true
+				}
+				this.close()
+			},
+			//  省市区  change
+			pickerChange(e) {
+				const _this = this
+				let changePickerValue = e.detail.value
+				
+				if (this.pickerValue[0] !== changePickerValue[0]) {
+					if(this.pickerId && this.pickerId.length > 0 && this.isFirst){
+						//  获取市下拉
+						this.getCityList(this.pickerId[0], this.pickerId)
+					}else{
+						// 第一级发生滚动,省 change重置市区下拉数据
+						this.getCityList(this.provinceDataList[changePickerValue[0]].id)
+						changePickerValue[1] = 0
+						changePickerValue[2] = 0
+					}
+				} else if (this.pickerValue[1] !== changePickerValue[1]) {
+					if(this.pickerId && this.pickerId.length > 0 && this.isFirst){
+						//  获取市下拉
+						this.getAreaList(this.pickerId[1], this.pickerId)
+					}else{
+						// 第二级滚动,市 change重置区县下拉数据
+						this.getAreaList(this.cityDataList[changePickerValue[1]].id)
+						changePickerValue[2] = 0
+					}
+				}
+				this.pickerValue = changePickerValue
+				//  setTimeout解决由于省市区接口请求数据有延缓导致获值报错
+				setTimeout(()=>{
+					_this._$emit('onChange')
+				},1000)
+			},
+			_$emit(emitName) {
+				let pickObj = {
+					label: this._getLabel(),
+					value: this.pickerValue,
+					cityCode: this._getCityCode(),
+					areaCode: this._getAreaCode(),
+					provinceCode: this._getProvinceCode(),
+					labelArr: this._getLabel().split('-')
+				}
+				this.$emit(emitName, pickObj)
+			},
+			_getLabel() {
+				//  name报错未发现页面影响(由于省市区接口请求数据延缓)
+				let pcikerLabel =
+					this.provinceDataList[this.pickerValue[0]].name + '-' + this.cityDataList[this.pickerValue[1]].name + '-' + this.areaDataList[this.pickerValue[2]].name
+				return pcikerLabel
+			},
+			_getCityCode() {
+				return this.cityDataList[this.pickerValue[1]].id
+			},
+			_getProvinceCode() {
+				return this.provinceDataList[this.pickerValue[0]].id
+			},
+			_getAreaCode() {
+				return this.areaDataList[this.pickerValue[2]].id
+			},
+			//  获取省下拉数据
+			getProvinceList(idArr){
+				const _this = this
+				getProvince().then(res => {
+				  if (res.code == 200 || res.status == 204 || res.status == 200) {
+				    _this.provinceDataList = res.data || []  //  省下拉数据
+					//  判断是否有默认值
+					if(idArr){
+						_this.getCityList(idArr[0], idArr)
+						_this.getAreaIndArr(idArr)
+					}else{
+						//  默认省数据第一条下的市下拉数据
+						_this.getCityList(_this.provinceDataList[0].id)
+					}
+					
+				  } else {
+				    _this.provinceDataList = []
+				  }
+				})
+			},
+			// 获取城市列表
+			getCityList (val, idArr) {
+				const _this = this
+				_this.cityDataList = []
+				_this.areaDataList = []
+				if (val != null && val != '') {
+					getCityByPro({id: val}).then(res => {
+						if (res.code == 200 || res.status == 204 || res.status == 200) {
+							_this.cityDataList = res.data || []  //  市下拉数据
+							//  判断是否有默认值
+							if(idArr){
+								_this.getAreaList(idArr[1], idArr)
+								_this.getAreaIndArr(idArr)
+							}else{
+								//  默认市数据第一条下的区县下拉数据
+								_this.getAreaList(_this.cityDataList[0].id)
+							}
+						} else {
+							_this.cityDataList = []
+						}
+					})
+				}
+			},
+			// 获取区县列表
+			getAreaList (val, idArr) {
+				const _this = this
+				this.areaDataList = []
+				if (val != null && val != '') {
+					getDistrictByCity({id: val}).then(res => {
+						if (res.code == 200 || res.status == 204 || res.status == 200) {
+							_this.areaDataList = res.data || []
+							//  判断是否有默认值
+							if(idArr){ _this.getAreaIndArr(idArr) }
+						} else {
+							_this.areaDataList = []
+						}
+					})
+				}
+			},
+			//  根据id 获取省市区 下标值
+			getAreaIndArr(idArr){
+				const _this = this
+				const provinceInd = _this.provinceDataList.findIndex(res => res['id'] == idArr[0])
+				const cityInd = _this.cityDataList.findIndex(res => res['id'] == idArr[1])
+				const districtInd = _this.areaDataList.findIndex(res => res['id'] == idArr[2])
+				setTimeout(()=>{
+					_this.pickerValue = [provinceInd, cityInd, districtInd]
+				},100)
+				// console.log(_this.pickerValue,'_this.pickerValue')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+.simple-address {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: column;
+}
+
+.simple-address-mask {
+	position: fixed;
+	bottom: 0;
+	top: 0;
+	left: 0;
+	right: 0;
+
+	transition-property: opacity;
+	transition-duration: 0.3s;
+	opacity: 0;
+	/* #ifndef APP-NVUE */
+	z-index: 99999;
+	/* #endif */
+}
+
+.mask-ani {
+	transition-property: opacity;
+	transition-duration: 0.2s;
+}
+
+.simple-bottom-mask {
+	opacity: 1;
+}
+
+.simple-center-mask {
+	opacity: 1;
+}
+
+.simple-address--fixed {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	width: 100%;
+	transition-property: transform;
+	transition-duration: 0.3s;
+	transform: translateY(460rpx);
+	/* #ifndef APP-NVUE */
+	z-index: 999999;
+	/* #endif */
+}
+
+.simple-address-content {
+	background-color: #ffffff;
+}
+
+.simple-content-bottom {
+	bottom: 0;
+	left: 0;
+	right: 0;
+	transform: translateY(500rpx);
+}
+
+.content-ani {
+	transition-property: transform, opacity;
+	transition-duration: 0.2s;
+}
+
+.simple-bottom-content {
+	transform: translateY(0);
+}
+
+.simple-center-content {
+	transform: scale(1);
+	opacity: 1;
+}
+
+.simple-address__header {
+	position: relative;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	flex-wrap: nowrap;
+	justify-content: space-between;
+	border-bottom-color: #f2f2f2;
+	border-bottom-style: solid;
+	border-bottom-width: 1rpx;
+}
+
+.simple-address--fixed-top {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	justify-content: space-between;
+	border-top-color: $uni-border-color;
+	border-top-style: solid;
+	border-top-width: 1rpx;
+}
+
+.simple-address__header-btn-box {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+	height: 70rpx;
+}
+
+.simple-address__header-text {
+	text-align: center;
+	font-size: $uni-font-size-base;
+	color: #1aad19;
+	line-height: 70rpx;
+	padding-left: 40rpx;
+	padding-right: 40rpx;
+}
+
+.simple-address__box {
+	position: relative;
+}
+
+.simple-address-view {
+	position: relative;
+	bottom: 0;
+	left: 0;
+	/* #ifndef APP-NVUE */
+	width: 100%;
+	/* #endif */
+	/* #ifdef APP-NVUE */
+	width: 750rpx;
+	/* #endif */
+	height: 408rpx;
+	background-color: rgba(255, 255, 255, 1);
+}
+
+.picker-item {
+	text-align: center;
+	line-height: 70rpx;
+	text-overflow: ellipsis;
+	font-size: 28rpx;
+}
+</style>

+ 721 - 0
components/almost-lottery/almost-lottery.vue

@@ -0,0 +1,721 @@
+<template>
+  <view class="almost-lottery" :style="{ width: canvasWidth + 40 + 'px', height: canvasHeight + 40 + 'px'}">
+    <view class="almost-lottery__wrap" :style="{width: canvasWidth + canvasMarginTotal + 'px', height: canvasHeight + canvasMarginTotal + 'px'}">
+      <!-- #ifdef MP-ALIPAY -->
+      <canvas :class="className" :id="canvasId" :width="canvasWidth" :height="canvasHeight" :style="{
+         width: canvasWidth + 'px',
+         height: canvasHeight + 'px'
+       }" />
+      <!-- #endif -->
+      <!-- #ifndef MP-ALIPAY -->
+      <canvas :class="className" :canvas-id="canvasId" :width="canvasWidth" :height="canvasHeight" :style="{
+         width: canvasWidth + 'px',
+         height: canvasHeight + 'px'
+       }" />
+      <!-- #endif -->
+      <image class="canvas-img" :src="lotteryImg" :style="{
+         width: canvasWidth + canvasMarginTotal + 'px',
+         height: canvasHeight + canvasMarginTotal + 'px',
+         transform: `rotate(${canvasAngle + targetAngle}deg)`,
+         transitionDuration: `${transitionDuration}s`
+       }"
+        v-if="lotteryImg"></image>
+      <view class="almost-lottery__action" :style="{
+         width: actionSize + 'px',
+         height: actionSize + 'px'
+       }" @click="handleActionStart"></view>
+      <!-- 为了兼容 app 端 ctx.measureText 所需的标签 -->
+      <text class="almost-lottery__measureText">{{ measureText }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+	import { pathToBase64 } from '@/almost-utils/image-tools.js'
+	import { getStore, setStore, clearStore, downloadFile } from '@/almost-utils/almost-utils.js'
+  export default {
+    name: 'AlmostLottery',
+    props: {
+      // canvas 宽度
+      canvasWidth: {
+        type: Number,
+        default: 240
+      },
+      // canvas 高度
+      canvasHeight: {
+        type: Number,
+        default: 240
+      },
+      // 奖品列表
+      prizeList: {
+        type: Array,
+        required: true,
+        validator: (value) => {
+          return value.length > 1
+        }
+      },
+      // 中奖奖品在列表中的下标
+      prizeIndex: {
+        type: Number,
+        required: true
+      },
+      // 奖品区块对应背景颜色
+      colors: {
+        type: Array,
+        default: () => [
+          '#FFFFFF',
+          '#FFE9AA'
+        ]
+      },
+      // 旋转动画时间 单位s
+      duration: {
+        type: Number,
+        default: 8
+      },
+      // 旋转的圈数
+      ringCount: {
+        type: Number,
+        default: 8
+      },
+      // 指针位置
+      pointerPosition: {
+        type: String,
+        default: 'edge',
+        validator: (value) => {
+          return value === 'edge' || value === 'middle'
+        }
+      },
+      // 字体颜色
+      fontColor: {
+        type: String,
+        default: '#C30B29'
+      },
+      // 文字的大小
+      fontSize: {
+        type: Number,
+        default: 12
+      },
+      // 奖品文字多行情况下的行高
+      lineHeight: {
+        type: Number,
+        default: 16
+      },
+      // 奖品名称所对应的 key 值
+      strKey: {
+        type: String,
+        default: 'name'
+      },
+      // 奖品文字总长度限制
+      strMaxLen: {
+        type: Number,
+        default: 12
+      },
+      // 奖品文字多行情况下第一行文字长度
+      strLineLen: {
+        type: Number,
+        default: 6
+      },
+      // 奖品图片的宽
+      imageWidth: {
+        type: Number,
+        default: 30
+      },
+      // 奖品图片的高
+      imageHeight: {
+        type: Number,
+        default: 30
+      },
+			// 转盘绘制成功的提示
+			successMsg: {
+				type: String,
+				default: '奖品准备就绪,快来参与抽奖吧'
+			},
+			// 转盘绘制失败的提示
+			failMsg: {
+				type: String,
+				default: '奖品仍在准备中,请稍后再来...'
+			},
+			// 是否开启画板的缓存
+			canvasCached: {
+				type: Boolean,
+				default: true
+			},
+			// 内圈与外圈的间距
+			canvasMargin: {
+        type: Number,
+        default: 5
+      }
+    },
+    data() {
+      return {
+        // 画板className
+        className: 'almost-lottery__canvas',
+        // 画板标识
+        canvasId: 'almostLotteryCanvas',
+        // 画板导出的图片
+        lotteryImg: '',
+        // 旋转到奖品目标需要的角度
+        targetAngle: 0,
+        // 旋转动画时间 单位 s
+        transitionDuration: 0,
+        // 是否正在旋转
+        isRotate: false,
+        // 当前停留在那个奖品的序号
+        stayIndex: 0,
+        // 当前中奖奖品的序号
+        targetIndex: 0,
+				// 是否存在可用的缓存转盘图
+				isCacheImg: false,
+				oldLotteryImg: '',
+        // 解决 app 不支持 measureText 的问题
+				// app 已在 2.9.3 的版本中提供了对 measureText 的支持,将在后续版本逐渐稳定后移除相关兼容代码
+        measureText: ''
+      }
+    },
+    computed: {
+      // 根据奖品列表计算 canvas 旋转角度
+      canvasAngle() {
+        let prizeCount = this.prizeList.length
+        let prizeClip = 360 / prizeCount
+        let result = 0
+
+        let diffNum = 90 / prizeClip
+        if (this.pointerPosition === 'edge') {
+          result = -(prizeClip * diffNum)
+        } else {
+          result = -(prizeClip * diffNum + prizeClip / 2)
+        }
+        return result
+      },
+      // 外圆的半径
+      outsideRadius() {
+        return this.canvasWidth / 2
+      },
+      // 内圆的半径
+      insideRadius() {
+        return 20
+      },
+      // 字体的半径
+      textRadius() {
+        return this.fontSize / 2
+      },
+      // 根据画板的宽度计算奖品文字与中心点的距离
+      textDistance() {
+        const textZeroY = Math.round(this.outsideRadius - (this.insideRadius / 2))
+        return textZeroY - this.textRadius
+      },
+      // 设备像素密度
+      pixelRatio() {
+        return uni.getSystemInfoSync().pixelRatio
+      },
+			// 内圈与外圈的距离
+			canvasMarginTotal () {
+				let diffNum = 5
+				let margin = this.canvasMargin * 2
+				if (this.canvasWidth > 240) {
+					return -(this.canvasWidth / 240 * 2) - margin
+				} else if (this.canvasWidth < 240) {
+					return diffNum + (this.canvasWidth / 240 * 2) - margin
+				} else {
+					return diffNum - margin
+				}
+			},
+			// 抽奖按钮的宽高
+			actionSize () {
+				return this.canvasWidth / 2.4
+			}
+    },
+    watch: {
+      // 监听获奖序号的变动
+      prizeIndex(newVal, oldVal) {
+        if (newVal > -1) {
+          this.targetIndex = newVal
+          this.onRotateStart()
+        } else {
+          console.info('旋转结束,prizeIndex 已重置')
+        }
+      }
+    },
+    methods: {
+	  resetAngle(){
+		  let prizeCount = this.prizeList.length
+		  let prizeClip = 360 / prizeCount
+		  this.targetAngle = this.targetAngle - prizeClip/2
+	  },
+      // 开始旋转
+      onRotateStart() {
+        if (this.isRotate) return
+        this.isRotate = true
+        // 奖品总数
+        let prizeCount = this.prizeList.length
+        let baseAngle = 360 / prizeCount
+        let angles = 0
+        if (this.targetAngle === 0) {
+          // 第一次旋转
+          // 因为第一个奖品是从0°开始的,即水平向右方向
+          // 第一次旋转角度 = 270度 - (停留的序号-目标序号) * 每个奖品区间角度 - 每个奖品区间角度的一半 - canvas自身旋转的度数
+          angles = (270 - (this.targetIndex - this.stayIndex) * baseAngle - baseAngle / 2) - this.canvasAngle
+        } else {
+          // 后续旋转
+          // 后续继续旋转 就只需要计算停留的位置与目标位置的角度
+          angles = -(this.targetIndex - this.stayIndex) * baseAngle
+        }
+        // 更新目前序号
+        this.stayIndex = this.targetIndex
+        // 转 8 圈,圈数越多,转的越快
+        this.targetAngle += angles + 360 * this.ringCount
+
+        // 计算转盘结束的时间,预加一些延迟确保转盘停止后触发结束事件
+        let endTime = this.transitionDuration * 1000 + 100
+        let endTimer = setTimeout(() => {
+          clearTimeout(endTimer)
+          endTimer = null
+
+          this.isRotate = false
+          this.$emit('draw-end')
+        }, endTime)
+
+        let resetPrizeTimer = setTimeout(() => {
+          clearTimeout(resetPrizeTimer)
+          resetPrizeTimer = null
+
+          // 每次抽奖结束后都要重置父级附件的 prizeIndex
+          this.$emit('reset-index')
+        }, endTime + 50)
+      },
+      // 点击 开始抽奖 按钮
+      handleActionStart() {
+        this.$emit('draw-start')
+      },
+      // 渲染转盘
+      async onCreateCanvas() {
+        // 获取 canvas 画布
+        const canvasId = this.canvasId
+        const ctx = uni.createCanvasContext(canvasId, this)
+
+        // canvas 的宽高
+        let canvasW = this.canvasWidth
+        let canvasH = this.canvasHeight
+
+        // 根据奖品个数计算 角度
+        let prizeCount = this.prizeList.length
+        let baseAngle = Math.PI * 2 / prizeCount
+
+        // 设置描边颜色
+        ctx.setStrokeStyle('#FFBE04')
+
+        // 设置字体和字号
+        // #ifndef MP
+        let fontFamily =
+          '-apple-system, BlinkMacSystemFont, \'PingFang SC\', \'Helvetica Neue\', STHeiti, \'Microsoft Yahei\', Tahoma, Simsun, sans-serif'
+        ctx.font = `${this.fontSize}px ${fontFamily}`
+        // #endif
+        // #ifdef MP
+        ctx.setFontSize(this.fontSize)
+        // #endif
+
+        // 注意,开始画的位置是从0°角的位置开始画的。也就是水平向右的方向。
+        // 画具体内容
+        for (let i = 0; i < prizeCount; i++) {
+					let prizeItem = this.prizeList[i]
+          // 当前角度
+          let angle = i * baseAngle
+
+          // 保存当前画布的状态
+          ctx.save()
+
+          // 开始画内容
+          ctx.beginPath()
+
+          // 开始画圆弧
+          // x => 圆弧对应的圆心横坐标 x
+          // y => 圆弧对应的圆心横坐标 y
+          // radius => 圆弧的半径大小
+          // startAngle => 圆弧开始的角度,单位是弧度
+          // endAngle => 圆弧结束的角度,单位是弧度
+          // anticlockwise(可选) => 绘制方向,true 为逆时针,false 为顺时针
+          ctx.arc(canvasW * 0.5, canvasH * 0.5, this.outsideRadius, angle, angle + baseAngle, false)
+          ctx.arc(canvasW * 0.5, canvasH * 0.5, this.insideRadius, angle + baseAngle, angle, true)
+
+          // 开始链接线条
+          ctx.stroke()
+          // 每个奖品区块背景填充颜色
+          if (this.colors.length === 2) {
+            ctx.setFillStyle(this.colors[i % 2])
+          } else {
+            ctx.setFillStyle(this.colors[i])
+          }
+          // 填充颜色
+          ctx.fill()
+
+          // 开始绘制奖品内容
+          // 重新映射画布上的 (0,0) 位置
+          let translateX = canvasW * 0.5 + Math.cos(angle + baseAngle / 2) * this.textDistance
+          let translateY = canvasH * 0.5 + Math.sin(angle + baseAngle / 2) * this.textDistance
+          ctx.translate(translateX, translateY)
+
+          // 绘制奖品名称
+          ctx.setFillStyle(this.fontColor)
+          let rewardName = this.strLimit(prizeItem[this.strKey])
+
+          // rotate方法旋转当前的绘图,因为文字是和当前扇形中心线垂直的
+          ctx.rotate(angle + (baseAngle / 2) + (Math.PI / 2))
+
+          // 设置文本位置并处理换行
+          // 是否需要换行
+          let isLineBreak = rewardName.length > this.strLineLen
+          let textOffsetX = this.fontSize === 12 ? 0 : this.textRadius
+          if (isLineBreak) {
+            // 获得多行文本数组
+            rewardName = rewardName.substring(0, this.strLineLen) + ',' + rewardName.substring(this.strLineLen)
+            let rewardNames = rewardName.split(',')
+
+            // 循环文本数组,计算每一行的文本宽度
+            for (let j = 0; j < rewardNames.length; j++) {
+              if (ctx.measureText && ctx.measureText(rewardNames[j]).width) {
+                // 文本的宽度信息
+                let tempStrSize = ctx.measureText(rewardNames[j])
+                ctx.fillText(rewardNames[j], -(tempStrSize.width / 2 + textOffsetX), j * this.lineHeight)
+              } else {
+                this.measureText = rewardNames[j]
+
+                // 等待页面重新渲染
+                await this.$nextTick()
+
+                let textWidth = await this.getTextWidth()
+
+                ctx.fillText(rewardNames[j], -(textWidth / 2 + textOffsetX), j * this.lineHeight)
+                // console.log(rewardNames[j], textWidth, i)
+              }
+            }
+          } else {
+            if (ctx.measureText && ctx.measureText(rewardName).width) {
+              // 文本的宽度信息
+              let tempStrSize = ctx.measureText(rewardName)
+              ctx.fillText(rewardName, -(tempStrSize.width / 2 + textOffsetX), 0)
+            } else {
+              this.measureText = rewardName
+
+              // 等待页面重新渲染
+              await this.$nextTick()
+
+              let textWidth = await this.getTextWidth()
+              ctx.fillText(rewardName, -(textWidth / 2 + textOffsetX), 0)
+            }
+          }
+
+          // 绘制奖品图片
+          if (prizeItem.prizeImage) {
+						// App-Android平台 系统 webview 更新到 Chrome84+ 后 canvas 组件绘制本地图像 uni.canvasToTempFilePath 会报错
+						// 统一将图片处理成 base64
+						// https://ask.dcloud.net.cn/question/103303
+						let reg = /^(https|http)/g
+						// 处理远程图片
+						if (reg.test(prizeItem.prizeImage)) {
+							console.warn('###当前数据列表中的奖品图片为网络图片,开始下载图片...###')
+							let res = await downloadFile(prizeItem.prizeImage)
+							console.log('处理远程图片', res)
+							if (res.ok) {
+								let tempFilePath = res.tempFilePath
+								// #ifndef MP
+								prizeItem.prizeImage = await pathToBase64(tempFilePath)
+								// #endif
+								// #ifdef MP
+								prizeItem.prizeImage = tempFilePath
+								// #endif
+							}
+						} else {
+							// #ifndef MP
+							prizeItem.prizeImage = await pathToBase64(prizeItem.prizeImage)
+							// #endif
+						}
+						
+            ctx.drawImage(prizeItem.prizeImage, -(this.imageWidth / 2), canvasW / 10, this.imageWidth, this.imageHeight)
+          }
+
+          ctx.restore()
+        }
+
+        // 保存绘图并导出图片
+        ctx.draw(true, () => {
+          let drawTimer = setTimeout(() => {
+            clearTimeout(drawTimer)
+            drawTimer = null
+
+            // #ifdef MP-ALIPAY
+            ctx.toTempFilePath({
+              destWidth: this.canvasWidth * this.pixelRatio,
+              destHeight: this.canvasHeight * this.pixelRatio,
+              success: (res) => {
+                // console.log(res.apFilePath)
+                this.handlePrizeImg({
+									ok: true,
+									data: res.apFilePath,
+									msg: '画布导出生成图片成功'
+								})
+              },
+							fail: (err) => {
+                this.handlePrizeImg({
+									ok: false,
+									data: err,
+									msg: '画布导出生成图片失败'
+								})
+							}
+            })
+            // #endif
+            // #ifndef MP-ALIPAY
+            uni.canvasToTempFilePath({
+              destWidth: this.canvasWidth * this.pixelRatio,
+              destHeight: this.canvasHeight * this.pixelRatio,
+              canvasId: this.canvasId,
+              success: (res) => {
+                // 在 H5 平台下,tempFilePath 为 base64
+                // console.log(res.tempFilePath)
+                this.handlePrizeImg({
+									ok: true,
+									data: res.tempFilePath,
+									msg: '画布导出生成图片成功'
+								})
+              },
+							fail: (err) => {
+                this.handlePrizeImg({
+									ok: false,
+									data: err,
+									msg: '画布导出生成图片失败'
+								})
+							}
+            }, this)
+            // #endif
+          }, 500)
+        })
+      },
+      // 处理导出的图片
+      handlePrizeImg(res) {
+				if (res.ok) {
+					let data = res.data
+					
+					if (!this.canvasCached) {
+						this.lotteryImg = data
+						this.handlePrizeImgSuc(res)
+						return
+					}
+					
+					// #ifndef H5
+					if (this.isCacheImg) {
+						uni.getSavedFileList({
+							success: (sucRes) => {
+								let fileList = sucRes.fileList
+								// console.log('getSavedFileList Cached', fileList)
+								
+								let cached = false
+								for (let i = 0; i < fileList.length; i++) {
+									let item = fileList[i]
+									if (item.filePath === data) {
+										cached = true
+										this.lotteryImg = data
+										
+										console.info('经查,本地缓存中存在转盘图可用,本次将不再绘制转盘')
+										this.handlePrizeImgSuc(res)
+										break
+									}
+								}
+								
+								if (!cached) {
+									console.info('经查,本地缓存中存在转盘图不可用,需要重新初始化转盘绘制')
+									this.initCanvasDraw()
+								}
+							},
+							fail: (err) => {
+								this.initCanvasDraw()
+							}
+						})
+					} else {
+						uni.saveFile({
+							tempFilePath: data,
+							success: (sucRes) => {
+								let filePath = sucRes.savedFilePath
+								// console.log('saveFile', filePath)
+								setStore('lotteryImg', filePath)
+								this.lotteryImg = filePath
+								this.handlePrizeImgSuc({
+									ok: true,
+									data: filePath,
+									msg: '画布导出生成图片成功'
+								})
+							},
+							fail: (err) => {
+								this.handlePrizeImg({
+									ok: false,
+									data: err,
+									msg: '画布导出生成图片失败'
+								})
+							}
+						})
+					}
+					// #endif
+					// #ifdef H5
+					console.info('当前为 H5 端,直接使用导出的/缓存中的 base64 图')
+					setStore('lotteryImg', data)
+					this.lotteryImg = data
+					this.handlePrizeImgSuc(res)
+					// #endif
+				} else {
+					console.error('处理导出的图片失败', res)
+					uni.hideLoading()
+					// #ifdef H5
+					console.error('###当前为 H5 端,下载网络图片需要后端配置允许跨域###')
+					// #endif
+					// #ifdef MP
+					console.error('###当前为小程序端,下载网络图片需要配置域名白名单###')
+					// #endif
+				}
+      },
+			// 处理图片完成
+			handlePrizeImgSuc (res) {
+				uni.hideLoading()
+				this.$emit('finish', {
+					ok: res.ok,
+					data: res.data,
+					msg: res.ok ? this.successMsg : this.failMsg
+				})
+			},
+      // 兼容 app 端不支持 ctx.measureText
+      // 已知问题:初始绘制时,低端安卓机 平均耗时 2s
+      getTextWidth() {
+        return new Promise((resolve, reject) => {
+          uni.createSelectorQuery().in(this).select('.almost-lottery__measureText').fields({
+            size: true,
+          }, (res) => {
+            resolve(res.width)
+          }).exec()
+        })
+      },
+      // 处理文字溢出
+      strLimit(value) {
+        let maxLength = this.strMaxLen
+        if (!value || !maxLength) return value
+        return value.length > maxLength ? value.slice(0, maxLength - 1) + '...' : value
+      },
+			// 检查本地缓存中是否存在转盘图
+			checkCacheImg () {
+				console.log('检查本地缓存中是否存在转盘图')
+				// 检查是否已有缓存的转盘图
+				// 检查是否与本次奖品数据相同
+				this.oldLotteryImg = getStore('lotteryImg')
+				let oldPrizeList = getStore('prizeList')
+				let newPrizeList = JSON.stringify(this.prizeList)
+				if (this.oldLotteryImg) {
+					if (oldPrizeList === newPrizeList) {
+						console.log(`经查,本地缓存中存在转盘图 => ${this.oldLotteryImg}`)
+						this.isCacheImg = true
+						
+						console.log('需要继续判断这张缓存图是否可用')
+						this.handlePrizeImg({
+							ok: true,
+							data: this.oldLotteryImg,
+							msg: '画布导出生成图片成功'
+						})
+						return
+					}
+				}
+				
+				console.log('经查,本地缓存中不存在转盘图')
+				this.initCanvasDraw()
+			},
+      // 初始化绘制
+      initCanvasDraw () {
+				console.log('开始初始化转盘绘制')
+				this.isCacheImg = false
+				this.lotteryImg = ''
+				clearStore('lotteryImg')
+        setStore('prizeList', this.prizeList)
+        this.onCreateCanvas()
+      }
+    },
+    mounted() {
+      this.$nextTick(() => {
+        let stoTimer = setTimeout(() => {
+          clearTimeout(stoTimer)
+          stoTimer = null
+					
+					if (this.canvasCached) {
+						this.checkCacheImg()
+					} else {
+						this.onCreateCanvas()
+					}
+          this.transitionDuration = this.duration
+        }, 50)
+      })
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  $lotteryBgUrl: '~static/almost-lottery/almost-lottery__bg';
+  $actionBgUrl: '~static/almost-lottery/almost-lottery__action';
+
+  .almost-lottery {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin: 0 auto;
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: contain;
+    background-image: url($lotteryBgUrl + ".png");
+
+    @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
+      background-image: url($lotteryBgUrl + "2x.png");
+    }
+
+    @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
+      background-image: url($lotteryBgUrl + "3x.png");
+    }
+  }
+	
+	.almost-lottery__wrap {
+    position: relative;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+	}
+
+  .almost-lottery__canvas {
+    position: absolute;
+    left: -9999px;
+    opacity: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .almost-lottery__action {
+    position: absolute;
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: contain;
+    background-image: url($actionBgUrl + ".png");
+
+    @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
+      background-image: url($actionBgUrl + "2x.png");
+    }
+
+    @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
+      background-image: url($actionBgUrl + "3x.png");
+    }
+  }
+
+  .almost-lottery__measureText {
+    position: absolute;
+    left: 0;
+    top: 0;
+    white-space: nowrap;
+    font-size: 12px;
+    opacity: 0;
+  }
+
+  .canvas-img {
+		display: block;
+    transition: transform cubic-bezier(.34, .12, .05, .95);
+  }
+</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" @click="display">
+			<u-icon v-show="showPassword" name="eye-fill" size="30"></u-icon>
+			<u-icon v-show="!showPassword" name="eye-off" size="30"></u-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 {
+		color: #666666;
+		padding: 10upx 20upx;
+	}
+</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'
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 10 - 0
components/uni-icons/uni-icons.vue


+ 178 - 0
components/uni-numbers-box/uni-numbers-box.vue

@@ -0,0 +1,178 @@
+<template>
+	<view class="uni-numbox">
+		<view @click="_calcValue('minus')" class="uni-numbox__minus">
+			<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }">-</text>
+		</view>
+		<input :disabled="disabled" @blur="_onBlur" class="uni-numbox__value" type="number" v-model="inputValue" />
+		<view @click="_calcValue('plus')" class="uni-numbox__plus">
+			<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">+</text>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		name: "UniNumbersBox",
+		props: {
+			index:{
+				type: String,
+				default: ''
+			},
+			value: {
+				type: [Number, String],
+				default: 1
+			},
+			min: {
+				type: Number,
+				default: 0
+			},
+			max: {
+				type: Number,
+				default: 100
+			},
+			step: {
+				type: Number,
+				default: 1
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				inputValue: this.value
+			};
+		},
+		watch: {
+			value(newVal,oldVal) {
+				console.log(newVal,oldVal,'000')
+				this.inputValue = +newVal;
+			},
+			inputValue(newVal, oldVal) {
+				if (+newVal !== +oldVal && this.max >= +newVal && this.min <= +newVal) {
+					this.$emit("change", newVal, this.index);
+				}
+			}
+		},
+		created() {
+			this.inputValue = +this.value;
+		},
+		methods: {
+			_calcValue(type) {
+				if (this.disabled) {
+					return;
+				}
+				const scale = this._getDecimalScale();
+				let value = this.inputValue * scale;
+				let step = this.step * scale;
+				if (type === "minus") {
+					value -= step;
+					if (value < this.min) {
+						return;
+					}
+					if(value > this.max){
+						value = this.max
+					}
+				} else if (type === "plus") {
+					value += step;
+					if (value > this.max) {
+						return;
+					}
+					if(value < this.min){
+						value = this.min
+					}
+				}
+
+				this.inputValue = String(value / scale);
+				this.$emit('input',this.inputValue)
+			},
+			_getDecimalScale() {
+				let scale = 1;
+				// 浮点型
+				if (~~this.step !== this.step) {
+					scale = Math.pow(10, (this.step + "").split(".")[1].length);
+				}
+				return scale;
+			},
+			_onBlur(event) {
+				let value = event.detail.value;
+				if (!value) {
+					this.inputValue = this.min;
+				}
+				value = +value;
+				if (value > this.max) {
+					value = this.max;
+				} else if (value < this.min) {
+					value = this.min;
+				}
+				this.inputValue = value;
+				this.$emit('input',this.inputValue)
+			},
+		}
+	};
+</script>
+<style lang="scss" scoped>
+	$box-height: 30px;
+	/* #ifdef APP-NVUE */
+	$box-line-height: 30px;
+	/* #endif */
+	$box-line-height: 26px;
+	$box-width: 30px;
+
+	.uni-numbox {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		width: 120px;
+	}
+
+	.uni-numbox__value {
+		background-color: $uni-bg-color;
+		width: 40px;
+		height: $box-height;
+		line-height: $box-height;
+		text-align: center;
+		font-size: $uni-font-size-lg;
+		margin: 0 10px;
+	}
+
+	.uni-numbox__minus {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		width: $box-width;
+		height: $box-height;
+		line-height: $box-height;
+		font-size: 20px;
+		color: $uni-text-color;
+		background-color: $uni-bg-color-grey;
+		border-radius: $box-width;
+	}
+
+	.uni-numbox__plus {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		width: $box-width;
+		height: $box-height;
+		line-height: $box-height;
+		background-color: $uni-bg-color-grey;
+		border-radius: $box-width;
+	}
+
+	.uni-numbox--text {
+		font-size: 36rpx;
+		color: $uni-text-color;
+	}
+
+	.uni-numbox--disabled {
+		color: $uni-text-color-disable;
+	}
+</style>

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

@@ -0,0 +1,122 @@
+<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 } 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)
+			  getCaptcha(this.nowRandomCode).then(res => {
+				// console.log(res.data)
+			    //  获取图片路径
+			    _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],
+}

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

@@ -0,0 +1,244 @@
+<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: #222;
+	}
+
+	.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;
+		color:#A6A6A6
+	}
+
+	.uni-button-color {
+		color: #0DC55F;
+	}
+
+	.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>

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

@@ -0,0 +1,295 @@
+<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
+				this.$emit("close")
+			}
+		},
+		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>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
components/w-picker/areadata/areadata.js


+ 742 - 0
components/w-picker/date-picker.vue

@@ -0,0 +1,742 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view v-if="fields=='year'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='month'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='day'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='hour'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='minute'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}分</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='second'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}分</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.seconds" :key="index">{{item}}秒</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{
+					years:[],
+					months:[],
+					days:[],
+					hours:[],
+					minutes:[],
+					seconds:[]
+				},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			startYear:{
+				type:[String,Number],
+				default:""
+			},
+			endYear:{
+				type:[String,Number],
+				default:""
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			disabledAfter:{//是否禁用当前之后的日期
+				type:Boolean,
+				default:false
+			},
+			fields:{
+				type:String,
+				default:"day"
+			}
+		},
+		watch:{
+			fields(val){
+				this.initData();
+			},
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg,example
+				switch(this.fields){
+					case "year":
+						strReg=/^\d{4}$/;
+						example="2019";
+						break;
+					case "month":
+						strReg=/^\d{4}-\d{2}$/;
+						example="2019-02";
+						break;
+					case "day":
+						strReg=/^\d{4}-\d{2}-\d{2}$/;
+						example="2019-02-01";
+						break;
+					case "hour":
+						strReg=/^\d{4}-\d{2}-\d{2} \d{2}(:\d{2}){1,2}?$/;
+						example="2019-02-01 18:00:00或2019-02-01 18";
+						break;
+					case "minute":
+						strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2}){0,1}?$/;
+						example="2019-02-01 18:06:00或2019-02-01 18:06";
+						break;
+					case "second":
+						strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
+						example="2019-02-01 18:06:01";
+						break;
+				}
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day,hour,minute){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				let months=[],days=[],hours=[],minutes=[],seconds=[];
+				let disabledAfter=this.disabledAfter;
+				let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
+				let totalDays=new Date(year,month,0).getDate();//计算当月有几天;
+				let daysLen=disabledAfter?((year*1<curYear||month*1<curMonth)?totalDays:curDay):totalDays;
+				let hoursLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)?24:curHour+1):24;
+				let minutesLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay||hour*1<curHour)?60:curMinute+1):60;
+				let secondsLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay||hour*1<curHour||minute*1<curMinute)?60:curSecond+1):60;
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				};
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				for(let hour=0;hour<hoursLen;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<minutesLen;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				for(let second=0;second<secondsLen;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return{
+					months,
+					days,
+					hours,
+					minutes,
+					seconds
+				}
+			},
+			isLeapYear (Year) {
+				if (((Year % 4)==0) && ((Year % 100)!=0) || ((Year % 400)==0)) {
+					return true;
+				} else { 
+					return false; 
+				}
+			},
+			getData(dVal){
+				//用来处理初始化数据
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let fields=this.fields;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonthdays=curDate.curMonthdays;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				let defaultDate=this.getDefaultDate();
+				let startYear=this.getStartDate().getFullYear();
+				let endYear=this.getEndDate().getFullYear();
+				//颗粒度,禁用当前之后日期仅对year,month,day,hour生效;分钟秒禁用没有意义,
+				let years=[],months=[],days=[],hours=[],minutes=[],seconds=[];
+				let year=dVal[0]*1;
+				let month=dVal[1]*1;
+				let day=dVal[2]*1;
+				let hour=dVal[3]*1;
+				let minute=dVal[4]*1;
+				let monthsLen=disabledAfter?(year<curYear?12:curDate.curMonth):12;
+				let daysLen=disabledAfter?((year<curYear||month<curMonth)?defaultDate.defaultDays:curDay):(curFlag?curMonthdays:defaultDate.defaultDays);
+				let hoursLen=disabledAfter?((year<curYear||month<curMonth||day<curDay)?24:curHour+1):24;
+				let minutesLen=disabledAfter?((year<curYear||month<curMonth||day<curDay||hour<curHour)?60:curMinute+1):60;
+				let secondsLen=disabledAfter?((year<curYear||month<curMonth||day<curDay||hour<curHour||minute<curMinute)?60:curSecond+1):60;
+				for(let year=startYear;year<=(disabledAfter?curYear:endYear);year++){
+					years.push(year.toString())
+				}
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				}
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				for(let hour=0;hour<hoursLen;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<minutesLen;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				// for(let second=0;second<(disabledAfter?curDate.curSecond+1:60);second++){
+				// 	seconds.push(this.formatNum(second));
+				// }
+				for(let second=0;second<60;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return {
+					years,
+					months,
+					days,
+					hours,
+					minutes,
+					seconds
+				}
+			},
+			getCurrenDate(){
+				let curDate=new Date();
+				let curYear=curDate.getFullYear();
+				let curMonth=curDate.getMonth()+1;
+				let curMonthdays=new Date(curYear,curMonth,0).getDate();
+				let curDay=curDate.getDate();
+				let curHour=curDate.getHours();
+				let curMinute=curDate.getMinutes();
+				let curSecond=curDate.getSeconds();
+				return{
+					curDate,
+					curYear,
+					curMonth,
+					curMonthdays,
+					curDay,
+					curHour,
+					curMinute,
+					curSecond
+				}
+			},
+			getDefaultDate(){
+				let value=this.value;
+				let reg=/-/g;
+				let defaultDate=value?new Date(value.replace(reg,"/")):new Date();
+				let defaultYear=defaultDate.getFullYear();
+				let defaultMonth=defaultDate.getMonth()+1;
+				let defaultDay=defaultDate.getDate();
+				let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
+				return{
+					defaultDate,
+					defaultYear,
+					defaultMonth,
+					defaultDay,
+					defaultDays
+				}
+			},
+			getStartDate(){
+				let start=this.startYear;
+				let startDate="";
+				let reg=/-/g;
+				if(start){
+					startDate=new Date(start+"/01/01");
+				}else{
+					startDate=new Date("1970/01/01");
+				}
+				return startDate;
+			},
+			getEndDate(){
+				let end=this.endYear;
+				let reg=/-/g;
+				let endDate="";
+				if(end){
+					endDate=new Date(end+"/12/01");
+				}else{
+					endDate=new Date();
+				}
+				return endDate;
+			},
+			getDval(){
+				let value=this.value;
+				let fields=this.fields;
+				let dVal=null;
+				let aDate=new Date();
+				let year=this.formatNum(aDate.getFullYear());
+				let month=this.formatNum(aDate.getMonth()+1);
+				let day=this.formatNum(aDate.getDate());
+				let hour=this.formatNum(aDate.getHours());
+				let minute=this.formatNum(aDate.getMinutes());
+				let second=this.formatNum(aDate.getSeconds());
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[year,month,day,hour,minute,second]
+					}else{
+						switch(this.fields){
+							case "year":
+								dVal=value?[value]:[];	
+								break;
+							case "month":
+								dVal=value?value.split("-"):[];
+								break;
+							case "day":
+								dVal=value?value.split("-"):[];
+								break;
+							case "hour":
+								dVal=[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")];
+								break;
+							case "minute":
+								dVal=value?[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")]:[];
+								break;
+							case "second":
+								dVal=[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")];
+								break;
+						}
+					}
+				}else{
+					dVal=[year,month,day,hour,minute,second]
+				}
+				return dVal;
+			},
+			initData(){
+				let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
+				let years=[],months=[],days=[],hours=[],minutes=[],seconds=[];
+				let dVal=[],pickVal=[];
+				let value=this.value;
+				let reg=/-/g;
+				let range={};
+				let result="",full="",year,month,day,hour,minute,second,obj={};
+				let defaultDate=this.getDefaultDate();
+				let defaultYear=defaultDate.defaultYear;
+				let defaultMonth=defaultDate.defaultMonth;
+				let defaultDay=defaultDate.defaultDay;
+				let defaultDays=defaultDate.defaultDays;
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curMonthdays=curDate.curMonthdays;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				let dateData=[];
+				dVal=this.getDval();
+				
+				startDate=this.getStartDate();
+				endDate=this.getEndDate();
+				startYear=startDate.getFullYear();
+				startMonth=startDate.getMonth();
+				startDay=startDate.getDate();
+				endYear=endDate.getFullYear();
+				endMonth=endDate.getMonth();
+				endDay=endDate.getDate();
+				dateData=this.getData(dVal);
+				years=dateData.years;
+				months=dateData.months;
+				days=dateData.days;
+				hours=dateData.hours;
+				minutes=dateData.minutes;
+				seconds=dateData.seconds;
+				switch(this.fields){
+					case "year":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0
+						]:(curFlag?[
+							years.indexOf(curYear+'')
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0
+						]);
+						range={years};
+						year=dVal[0]?dVal[0]:years[0];
+						result=full=`${year}`;
+						obj={
+							year
+						}
+						break;
+					case "month":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth))
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0
+						]);
+						range={years,months};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						result=full=`${year+'-'+month}`;
+						obj={
+							year,
+							month
+						}
+						break;
+					case "day":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0
+						]);
+						range={years,months,days};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						result=full=`${year+'-'+month+'-'+day}`;
+						obj={
+							year,
+							month,
+							day
+						}
+						break;
+					case "hour":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+							hours.indexOf(this.formatNum(curHour)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0
+						]);
+						range={years,months,days,hours};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						hour=dVal[3]?dVal[3]:hours[0];
+						result=`${year+'-'+month+'-'+day+' '+hour}`;
+						full=`${year+'-'+month+'-'+day+' '+hour+':00:00'}`;
+						obj={
+							year,
+							month,
+							day,
+							hour
+						}
+						break;
+					case "minute":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+							hours.indexOf(this.formatNum(curHour)),
+							minutes.indexOf(this.formatNum(curMinute)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0
+						]);
+						range={years,months,days,hours,minutes};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						hour=dVal[3]?dVal[3]:hours[0];
+						minute=dVal[4]?dVal[4]:minutes[0];
+						full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':00'}`;
+						result=`${year+'-'+month+'-'+day+' '+hour+':'+minute}`;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute
+						}
+						break;
+					case "second":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0,
+							dVal[5]&&seconds.indexOf(dVal[5])!=-1?seconds.indexOf(dVal[5]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+							hours.indexOf(this.formatNum(curHour)),
+							minutes.indexOf(this.formatNum(curMinute)),
+							seconds.indexOf(this.formatNum(curSecond)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0,
+							dVal[5]&&seconds.indexOf(dVal[5])!=-1?seconds.indexOf(dVal[5]):0
+						]);
+						range={years,months,days,hours,minutes,seconds};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						hour=dVal[3]?dVal[3]:hours[0];
+						minute=dVal[4]?dVal[4]:minutes[0];
+						second=dVal[5]?dVal[5]:seconds[0];
+						result=full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':'+second}`;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute,
+							second
+						}
+						break;
+					default:
+						range={years,months,days};
+						break;
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				});
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let year="",month="",day="",hour="",minute="",second="";
+				let result="",full="",obj={};
+				let months=null,days=null,hours=null,minutes=null,seconds=null;
+				let disabledAfter=this.disabledAfter;
+				let leapYear=false,resetData={};
+				year=(arr[0]||arr[0]==0)?data.years[arr[0]]||data.years[data.years.length-1]:"";
+				month=(arr[1]||arr[1]==0)?data.months[arr[1]]||data.months[data.months.length-1]:"";
+				day=(arr[2]||arr[2]==0)?data.days[arr[2]]||data.days[data.days.length-1]:"";
+				hour=(arr[3]||arr[3]==0)?data.hours[arr[3]]||data.hours[data.hours.length-1]:"";
+				minute=(arr[4]||arr[4]==0)?data.minutes[arr[4]]||data.minutes[data.minutes.length-1]:"";
+				second=(arr[5]||arr[5]==0)?data.seconds[arr[5]]||data.seconds[data.seconds.length-1]:"";
+				resetData=this.resetData(year,month,day,hour,minute);//重新拉取当前日期数据;
+				leapYear=this.isLeapYear(year);//判断是否为闰年;
+				switch(this.fields){
+					case "year":
+						result=full=`${year}`;
+						obj={
+							year
+						};
+						break;
+					case "month":
+						result=full=`${year+'-'+month}`;
+						if(this.disabledAfter)months=resetData.months;
+						if(months)this.range.months=months;
+						obj={
+							year,
+							month
+						}
+						break;
+					case "day":
+						result=full=`${year+'-'+month+'-'+day}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						obj={
+							year,
+							month,
+							day
+						}
+						break;
+					case "hour":
+						result=`${year+'-'+month+'-'+day+' '+hour}`;
+						full=`${year+'-'+month+'-'+day+' '+hour+':00:00'}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+							hours=resetData.hours;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						if(hours)this.range.hours=hours;
+						obj={
+							year,
+							month,
+							day,
+							hour
+						}
+						break;
+					case "minute":
+						full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':00'}`;
+						result=`${year+'-'+month+'-'+day+' '+hour+':'+minute}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+							hours=resetData.hours;
+							minutes=resetData.minutes;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						if(hours)this.range.hours=hours;
+						if(minutes)this.range.minutes=minutes;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute
+						};
+						break;
+					case "second":
+						result=full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':'+second}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+							hours=resetData.hours;
+							minutes=resetData.minutes;
+							//seconds=resetData.seconds;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						if(hours)this.range.hours=hours;
+						if(minutes)this.range.minutes=minutes;
+						//if(seconds)this.range.seconds=seconds;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute,
+							second
+						}
+						break;
+				}
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 345 - 0
components/w-picker/half-picker.vue

@@ -0,0 +1,345 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.sections" :key="index">{{item}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			startYear:{
+				type:String,
+				default:""
+			},
+			endYear:{
+				type:String,
+				default:""
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			disabledAfter:{//是否禁用当前之后的日期
+				type:Boolean,
+				default:false
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{4}-\d{2}-\d{2} [\u4e00-\u9fa5]{2}$/,example;
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let months=[],days=[],sections=[];
+				let disabledAfter=this.disabledAfter;
+				let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
+				let totalDays=new Date(year,month,0).getDate();//计算当月有几天;
+				let daysLen=disabledAfter?((year*1<curYear||month*1<curMonth)?totalDays:curDay):totalDays;
+				let sectionFlag=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)==true?false:true):(curHour>12==true?true:false);
+				sections=["上午","下午"];
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				};
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				if(sectionFlag){
+					sections=["上午"];
+				}
+				return{
+					months,
+					days,
+					sections
+				}
+			},
+			getData(dVal){
+				//用来处理初始化数据
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonthdays=curDate.curMonthdays;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let defaultDate=this.getDefaultDate();
+				let startYear=this.getStartDate().getFullYear();
+				let endYear=this.getEndDate().getFullYear();
+				let years=[],months=[],days=[],sections=[];
+				let year=dVal[0]*1;
+				let month=dVal[1]*1;
+				let day=dVal[2]*1;
+				let monthsLen=disabledAfter?(year<curYear?12:curDate.curMonth):12;
+				let daysLen=disabledAfter?((year<curYear||month<curMonth)?defaultDate.defaultDays:curDay):(curFlag?curMonthdays:defaultDate.defaultDays);
+				let sectionFlag=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)==true?false:true):(curHour>12==true?true:false);
+				for(let year=startYear;year<=(disabledAfter?curYear:endYear);year++){
+					years.push(year.toString())
+				}
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				}
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				if(sectionFlag){
+					sections=["下午"];
+				}else{
+					sections=["上午","下午"];
+				}
+				return {
+					years,
+					months,
+					days,
+					sections
+				}
+			},
+			getCurrenDate(){
+				let curDate=new Date();
+				let curYear=curDate.getFullYear();
+				let curMonth=curDate.getMonth()+1;
+				let curMonthdays=new Date(curYear,curMonth,0).getDate();
+				let curDay=curDate.getDate();
+				let curHour=curDate.getHours();
+				let curSection="上午";
+				if(curHour>=12){
+					curSection="下午";
+				}
+				return{
+					curDate,
+					curYear,
+					curMonth,
+					curMonthdays,
+					curDay,
+					curHour,
+					curSection
+				}
+			},
+			getDefaultDate(){
+				let value=this.value;
+				let reg=/-/g;
+				let defaultDate=value?new Date(value.split(" ")[0].replace(reg,"/")):new Date();
+				let defaultYear=defaultDate.getFullYear();
+				let defaultMonth=defaultDate.getMonth()+1;
+				let defaultDay=defaultDate.getDate();
+				let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
+				return{
+					defaultDate,
+					defaultYear,
+					defaultMonth,
+					defaultDay,
+					defaultDays
+				}
+			},
+			getStartDate(){
+				let start=this.startYear;
+				let startDate="";
+				let reg=/-/g;
+				if(start){
+					startDate=new Date(start+"/01/01");
+				}else{
+					startDate=new Date("1970/01/01");
+				}
+				return startDate;
+			},
+			getEndDate(){
+				let end=this.endYear;
+				let reg=/-/g;
+				let endDate="";
+				if(end){
+					endDate=new Date(end+"/12/31");
+				}else{
+					endDate=new Date();
+				}
+				return endDate;
+			},
+			getDval(){
+				let value=this.value;
+				let dVal=null;
+				let aDate=new Date();
+				let year=this.formatNum(aDate.getFullYear());
+				let month=this.formatNum(aDate.getMonth()+1);
+				let day=this.formatNum(aDate.getDate());
+				let hour=aDate.getHours();
+				let section="上午";
+				if(hour>=12)section="下午";
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[year,month,day,section]
+					}else{
+						let v=value.split(" ");
+						dVal=[...v[0].split("-"),v[1]];
+					}
+				}else{
+					dVal=[year,month,day,section]
+				}
+				return dVal;
+			},
+			initData(){
+				let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
+				let years=[],months=[],days=[],sections=[];
+				let dVal=[],pickVal=[];
+				let value=this.value;
+				let reg=/-/g;
+				let range={};
+				let result="",full="",year,month,day,section,obj={};
+				let defaultDate=this.getDefaultDate();
+				let defaultYear=defaultDate.defaultYear;
+				let defaultMonth=defaultDate.defaultMonth;
+				let defaultDay=defaultDate.defaultDay;
+				let defaultDays=defaultDate.defaultDays;
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curMonthdays=curDate.curMonthdays;
+				let curDay=curDate.curDay;
+				let curSection=curDate.curSection;
+				let dateData=[];
+				dVal=this.getDval();
+				startDate=this.getStartDate();
+				endDate=this.getEndDate();
+				startYear=startDate.getFullYear();
+				startMonth=startDate.getMonth();
+				startDay=startDate.getDate();
+				endYear=endDate.getFullYear();
+				endMonth=endDate.getMonth();
+				endDay=endDate.getDate();
+				dateData=this.getData(dVal);
+				years=dateData.years;
+				months=dateData.months;
+				days=dateData.days;
+				sections=dateData.sections;
+				pickVal=disabledAfter?[
+					dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+					dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+					dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+					dVal[3]&&sections.indexOf(dVal[3])!=-1?sections.indexOf(dVal[3]):0
+				]:(curFlag?[
+					years.indexOf(curYear+''),
+					months.indexOf(this.formatNum(curMonth)),
+					days.indexOf(this.formatNum(curDay)),
+					sections.indexOf(curSection),
+				]:[
+					dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+					dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+					dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+					dVal[3]&&sections.indexOf(dVal[3])!=-1?sections.indexOf(dVal[3]):0
+				]);
+				range={years,months,days,sections};
+				year=dVal[0]?dVal[0]:years[0];
+				month=dVal[1]?dVal[1]:months[0];
+				day=dVal[2]?dVal[2]:days[0];
+				section=dVal[3]?dVal[3]:sections[0];
+				result=full=`${year+'-'+month+'-'+day+' '+section}`;
+				obj={
+					year,
+					month,
+					day,
+					section
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let year="",month="",day="",section="";
+				let result="",full="",obj={};
+				let months=null,days=null,sections=null;
+				let disabledAfter=this.disabledAfter;
+				year=(arr[0]||arr[0]==0)?data.years[arr[0]]||data.years[data.years.length-1]:"";
+				month=(arr[1]||arr[1]==0)?data.months[arr[1]]||data.months[data.months.length-1]:"";
+				day=(arr[2]||arr[2]==0)?data.days[arr[2]]||data.days[data.days.length-1]:"";
+				section=(arr[3]||arr[3]==0)?data.sections[arr[3]]||data.sections[data.sections.length-1]:"";
+				result=full=`${year+'-'+month+'-'+day+' '+section}`;
+				let resetData=this.resetData(year,month,day);
+				if(this.disabledAfter){
+					months=resetData.months;
+					days=resetData.days;
+					sections=resetData.sections;
+				}else{
+					if(year%4==0||(month!=this.checkObj.month)){
+						days=resetData.days;
+					}
+				}
+				if(months)this.range.months=months;
+				if(days)this.range.days=days;
+				if(sections)this.range.sections=sections;
+				obj={
+					year,
+					month,
+					day,
+					section
+				}
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 274 - 0
components/w-picker/linkage-picker.vue

@@ -0,0 +1,274 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column v-for="(group,gIndex) in range" :key="gIndex">
+				<view class="w-picker-item" v-for="(item,index) in group" :key="index">{{item[nodeKey]}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:[],
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[Array,String],
+				default:""
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			options:{
+				type:Array,
+				default(){
+					return []
+				}
+			},
+			defaultProps:{
+				type:Object,
+				default(){
+					return{
+						lable:"label",
+						value:"value",
+						children:"children"
+					}
+				}
+			},
+			level:{
+				//多级联动层级,表示几级联动
+				type:[Number,String],
+				default:2
+			}
+		},
+		computed:{
+			nodeKey(){
+				return this.defaultProps.label;
+			},
+			nodeVal(){
+				return this.defaultProps.value;
+			},
+			nodeChild(){
+				return this.defaultProps.children;
+			}
+		},
+		watch:{
+			value(val){
+				if(this.options.length!=0){
+					this.initData();
+				}
+			},
+			options(val){
+				this.initData();
+			}
+		},
+		created() {
+			if(this.options.length!=0){
+				this.initData();
+			}
+		},
+		methods:{
+			getData(){
+				//用来处理初始化数据
+				let options=this.options;
+				let col1={},col2={},col3={},col4={};
+				let arr1=options,arr2=[],arr3=[],arr4=[];
+				let col1Index=0,col2Index=0,col3Index=0,col4Index=0;
+				let a1="",a2="",a3="",a4="";
+				let dVal=[],obj={};
+				let value=this.value;
+				let data=[];
+				a1=value[0];
+				a2=value[1];
+				if(this.level>2){
+					a3=value[2];
+				}
+				if(this.level>3){
+					a4=value[3];
+				};
+				/*第1列*/
+				col1Index=arr1.findIndex((v)=>{
+					return v[this.defaultType]==a1
+				});
+				col1Index=value?(col1Index!=-1?col1Index:0):0;
+				col1=arr1[col1Index];
+				
+				/*第2列*/
+				arr2=arr1[col1Index][this.nodeChild];
+				col2Index=arr2.findIndex((v)=>{
+					return v[this.defaultType]==a2
+				});
+				col2Index=value?(col2Index!=-1?col2Index:0):0;
+				col2=arr2[col2Index];
+				
+				/*第3列*/
+				if(this.level>2){
+					arr3=arr2[col2Index][this.nodeChild];
+					col3Index=arr3.findIndex((v)=>{
+						return v[this.defaultType]==a3;
+					});
+					col3Index=value?(col3Index!=-1?col3Index:0):0;
+					col3=arr3[col3Index];
+				};
+				
+				
+				/*第4列*/
+				if(this.level>3){
+					arr4=arr3[col4Index][this.nodeChild];
+					col4Index=arr4.findIndex((v)=>{
+						return v[this.defaultType]==a4;
+					});
+					col4Index=value?(col4Index!=-1?col4Index:0):0;
+					col4=arr4[col4Index];
+				};
+				switch(this.level*1){
+					case 2:
+						dVal=[col1Index,col2Index];
+						obj={
+							col1,
+							col2
+						}
+						data=[arr1,arr2];
+						break;
+					case 3:
+						dVal=[col1Index,col2Index,col3Index];
+						obj={
+							col1,
+							col2,
+							col3
+						}
+						data=[arr1,arr2,arr3];
+						break;
+					case 4:
+						dVal=[col1Index,col2Index,col3Index,col4Index];
+						obj={
+							col1,
+							col2,
+							col3,
+							col4
+						}
+						data=[arr1,arr2,arr3,arr4];
+						break
+				}
+				return {
+					data,
+					dVal,
+					obj
+				}
+			},
+			initData(){
+				let dataData=this.getData();
+				let data=dataData.data;
+				let arr1=data[0];
+				let arr2=data[1];
+				let arr3=data[2]||[];
+				let arr4=data[3]||[];
+				let obj=dataData.obj;
+				let col1=obj.col1,col2=obj.col2,col3=obj.col3||{},col4=obj.col4||{};
+				let result="",value=[];
+				let range=[];
+				switch(this.level){
+					case 2:
+						value=[col1[this.nodeVal],col2[this.nodeVal]];
+						result=`${col1[this.nodeKey]+col2[this.nodeKey]}`;
+						range=[arr1,arr2];
+						break;
+					case 3:
+						value=[col1[this.nodeVal],col2[this.nodeVal],col3[this.nodeVal]];
+						result=`${col1[this.nodeKey]+col2[this.nodeKey]+col3[this.nodeKey]}`;
+						range=[arr1,arr2,arr3];
+						break;
+					case 4:
+						value=[col1[this.nodeVal],col2[this.nodeVal],col3[this.nodeVal],col4[this.nodeVal]];
+						result=`${col1[this.nodeKey]+col2[this.nodeKey]+col3[this.nodeKey]+col4[this.nodeKey]}`;
+						range=[arr1,arr2,arr3,arr4];
+						break;
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=dataData.dVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let col1Index=arr[0],col2Index=arr[1],col3Index=arr[2]||0,col4Index=arr[3]||0;
+				let arr1=[],arr2=[],arr3=[],arr4=[];
+				let col1,col2,col3,col4,obj={};
+				let result="",value=[];
+				arr1=this.options;
+				arr2=(arr1[col1Index]&&arr1[col1Index][this.nodeChild])||arr1[arr1.length-1][this.nodeChild]||[];
+				col1=arr1[col1Index]||arr1[arr1.length-1]||{};
+				col2=arr2[col2Index]||arr2[arr2.length-1]||{};
+				if(this.level>2){
+					arr3=(arr2[col2Index]&&arr2[col2Index][this.nodeChild])||arr2[arr2.length-1][this.nodeChild];
+					col3=arr3[col3Index]||arr3[arr3.length-1]||{};
+				}
+				if(this.level>3){
+					arr4=(arr3[col3Index]&&arr3[col3Index][this.nodeChild])||arr3[arr3.length-1][this.nodeChild]||[];
+					col4=arr4[col4Index]||arr4[arr4.length-1]||{};
+				}
+				switch(this.level){
+					case 2:
+						obj={
+							col1,
+							col2
+						}
+						this.range=[arr1,arr2];
+						result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')}`;
+						value=[col1[this.nodeVal]||'',col2[this.nodeVal]||''];
+						break;
+					case 3:
+						obj={
+							col1,
+							col2,
+							col3
+						}
+						this.range=[arr1,arr2,arr3];
+						result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')+(col3[this.nodeKey]||'')}`;
+						value=[col1[this.nodeVal]||'',col2[this.nodeVal]||'',col3[this.nodeVal]||''];
+						break;
+					case 4:
+						obj={
+							col1,
+							col2,
+							col3,
+							col4
+						}
+						this.range=[arr1,arr2,arr3,arr4];
+						result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')+(col3[this.nodeKey]||'')+(col4[this.nodeKey]||'')}`;
+						value=[col1[this.nodeVal]||'',col2[this.nodeVal]||'',col3[this.nodeVal]||'',col4[this.nodeVal]||''];
+						break;
+				}
+				this.checkObj=obj;
+				this.pickVal=arr;
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";	
+</style>
+

+ 344 - 0
components/w-picker/range-picker.vue

@@ -0,0 +1,344 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view  class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.fyears" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.fmonths" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.fdays" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex1">
+				<view class="w-picker-item">-</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.tyears" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.tmonths" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.tdays" :key="index">{{item}}日</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[String,Array],
+				default(){
+					return []
+				}
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			startYear:{
+				type:[String,Number],
+				default:1970
+			},
+			endYear:{
+				type:[String,Number],
+				default:new Date().getFullYear()
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{4}-\d{2}-\d{2}$/,example="2020-04-03";
+				if(!strReg.test(value[0])||!strReg.test(value[1])){
+					console.log(new Error("请传入与mode匹配的value值,例["+example+","+example+"]"))
+				}
+				return strReg.test(value[0])&&strReg.test(value[1]);
+			},
+			resetToData(fmonth,fday,tyear,tmonth){
+				let range=this.range;
+				let tmonths=[],tdays=[];
+				let yearFlag=tyear!=range.tyears[0];
+				let monthFlag=tyear!=range.tyears[0]||tmonth!=range.tmonths[0];
+				let ttotal=new Date(tyear,tmonth,0).getDate();
+				for(let i=yearFlag?1:fmonth*1;i<=12;i++){
+					tmonths.push(this.formatNum(i))
+				}
+				for(let i=monthFlag?1:fday*1;i<=ttotal;i++){
+					tdays.push(this.formatNum(i))
+				}
+				return{
+					tmonths,
+					tdays
+				}
+			},
+			resetData(fyear,fmonth,fday,tyear,tmonth){
+				let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[];
+				let startYear=this.startYear;
+				let endYear=this.endYear;
+				let ftotal=new Date(fyear,fmonth,0).getDate();
+				let ttotal=new Date(tyear,tmonth,0).getDate();
+				for(let i=startYear*1;i<=endYear;i++){
+					fyears.push(this.formatNum(i))
+				}
+				for(let i=1;i<=12;i++){
+					fmonths.push(this.formatNum(i))
+				}
+				for(let i=1;i<=ftotal;i++){
+					fdays.push(this.formatNum(i))
+				}
+				for(let i=fyear*1;i<=endYear;i++){
+					tyears.push(this.formatNum(i))
+				}
+				for(let i=fmonth*1;i<=12;i++){
+					tmonths.push(this.formatNum(i))
+				}
+				for(let i=fday*1;i<=ttotal;i++){
+					tdays.push(this.formatNum(i))
+				}
+				return {
+					fyears,
+					fmonths,
+					fdays,
+					tyears,
+					tmonths,
+					tdays
+				}
+			},
+			getData(dVal){
+				let start=this.startYear*1;
+				let end=this.endYear*1;
+				let value=dVal;
+				let flag=this.current;
+				let aToday=new Date();
+				let tYear,tMonth,tDay,tHours,tMinutes,tSeconds,pickVal=[];
+				let initstartDate=new Date(start.toString());
+				let endDate=new Date(end.toString());
+				if(start>end){
+					initstartDate=new Date(end.toString());
+					endDate=new Date(start.toString());
+				};
+				let startYear=initstartDate.getFullYear();
+				let startMonth=initstartDate.getMonth()+1;
+				let endYear=endDate.getFullYear();
+				let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[],returnArr=[],startDVal=[],endDVal=[];
+				let curMonth=flag?value[1]*1:(startDVal[1]*1+1);
+				let curMonth1=flag?value[5][1]*1:(value[5]*1+1);
+				let totalDays=new Date(value[0],value[1],0).getDate();
+				let totalDays1=new Date(value[4],value[5],0).getDate();
+				for(let s=startYear;s<=endYear;s++){
+					fyears.push(this.formatNum(s));
+				};
+				for(let m=1;m<=12;m++){
+					fmonths.push(this.formatNum(m));
+				};
+				for(let d=1;d<=totalDays;d++){
+					fdays.push(this.formatNum(d));
+				};
+				for(let s=value[0]*1;s<=endYear;s++){
+					tyears.push(this.formatNum(s));
+				};
+				
+				if(value[4]*1>value[0]*1){
+					for(let m=1;m<=12;m++){
+						tmonths.push(this.formatNum(m));
+					};
+					for(let d=1;d<=totalDays1;d++){
+						tdays.push(this.formatNum(d));
+					};
+				}else{
+					for(let m=value[1]*1;m<=12;m++){
+						tmonths.push(this.formatNum(m));
+					};
+					for(let d=value[2]*1;d<=totalDays1;d++){
+						tdays.push(this.formatNum(d));
+					};
+				};
+				
+				pickVal=[
+					fyears.indexOf(value[0])==-1?0:fyears.indexOf(value[0]),
+					fmonths.indexOf(value[1])==-1?0:fmonths.indexOf(value[1]),
+					fdays.indexOf(value[2])==-1?0:fdays.indexOf(value[2]),
+					0,
+					tyears.indexOf(value[4])==-1?0:tyears.indexOf(value[4]),
+					tmonths.indexOf(value[5])==-1?0:tmonths.indexOf(value[5]),
+					tdays.indexOf(value[6])==-1?0:tdays.indexOf(value[6])
+				];
+				return {
+					fyears,
+					fmonths,
+					fdays,
+					tyears,
+					tmonths,
+					tdays,
+					pickVal
+				}
+			},
+			getDval(){
+				let value=this.value;
+				let fields=this.fields;
+				let dVal=null;
+				let aDate=new Date();
+				let fyear=this.formatNum(aDate.getFullYear());
+				let fmonth=this.formatNum(aDate.getMonth()+1);
+				let fday=this.formatNum(aDate.getDate());
+				let tyear=this.formatNum(aDate.getFullYear());
+				let tmonth=this.formatNum(aDate.getMonth()+1);
+				let tday=this.formatNum(aDate.getDate());
+				if(value&&value.length>0){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[fyear,fmonth,fday,"-",tyear,tmonth,tday]
+					}else{
+						dVal=[...value[0].split("-"),"-",...value[1].split("-")];
+					}
+				}else{
+					dVal=[fyear,fmonth,fday,"-",tyear,tmonth,tday]
+				}
+				return dVal;
+			},
+			initData(){
+				let range=[],pickVal=[];
+				let result="",full="",obj={};
+				let dVal=this.getDval();
+				let dateData=this.getData(dVal);
+				let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[];
+				let fyear,fmonth,fday,tyear,tmonth,tday;
+				pickVal=dateData.pickVal;
+				fyears=dateData.fyears;
+				fmonths=dateData.fmonths;
+				fdays=dateData.fdays;
+				tyears=dateData.tyears;
+				tmonths=dateData.tmonths;
+				tdays=dateData.tdays;
+				range={
+					fyears,
+					fmonths,
+					fdays,
+					tyears,
+					tmonths,
+					tdays,
+				}
+				fyear=range.fyears[pickVal[0]];
+				fmonth=range.fmonths[pickVal[1]];
+				fday=range.fdays[pickVal[2]];
+				tyear=range.tyears[pickVal[4]];
+				tmonth=range.tmonths[pickVal[5]];
+				tday=range.tdays[pickVal[6]];
+				obj={
+					fyear,
+					fmonth,
+					fday,
+					tyear,
+					tmonth,
+					tday
+				}
+				result=`${fyear+'-'+fmonth+'-'+fday+'至'+tyear+'-'+tmonth+'-'+tday}`;
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:result.split("至"),
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let result="",full="",obj={};
+				let year="",month="",day="",hour="",minute="",second="",note=[],province,city,area;
+				let checkObj=this.checkObj;
+				let days=[],months=[],endYears=[],endMonths=[],endDays=[],startDays=[];
+				let mode=this.mode;
+				let col1,col2,col3,d,a,h,m;
+				let xDate=new Date().getTime();
+				let range=this.range;
+				let fyear=range.fyears[arr[0]]||range.fyears[range.fyears.length-1];
+				let fmonth=range.fmonths[arr[1]]||range.fmonths[range.fmonths.length-1];
+				let fday=range.fdays[arr[2]]||range.fdays[range.fdays.length-1];
+				let tyear=range.tyears[arr[4]]||range.tyears[range.tyears.length-1];
+				let tmonth=range.tmonths[arr[5]]||range.tmonths[range.tmonths.length-1];
+				let tday=range.tdays[arr[6]]||range.tdays[range.tdays.length-1];
+				let resetData=this.resetData(fyear,fmonth,fday,tyear,tmonth);
+				if(fyear!=checkObj.fyear||fmonth!=checkObj.fmonth||fday!=checkObj.fday){
+					arr[4]=0;
+					arr[5]=0;
+					arr[6]=0;
+					range.tyears=resetData.tyears;
+					range.tmonths=resetData.tmonths;
+					range.tdays=resetData.tdays;
+					tyear=range.tyears[0];
+					checkObj.tyears=range.tyears[0];
+					tmonth=range.tmonths[0];
+					checkObj.tmonths=range.tmonths[0];
+					tday=range.tdays[0];
+					checkObj.tdays=range.tdays[0];
+				}
+				if(fyear!=checkObj.fyear||fmonth!=checkObj.fmonth){
+					range.fdays=resetData.fdays;
+				};
+				if(tyear!=checkObj.tyear){
+					arr[5]=0;
+					arr[6]=0;
+					let toData=this.resetToData(fmonth,fday,tyear,tmonth);
+					range.tmonths=toData.tmonths;
+					range.tdays=toData.tdays;
+					tmonth=range.tmonths[0];
+					checkObj.tmonths=range.tmonths[0];
+					tday=range.tdays[0];
+					checkObj.tdays=range.tdays[0];
+				};
+				if(tmonth!=checkObj.tmonth){
+					arr[6]=0;
+					let toData=this.resetToData(fmonth,fday,tyear,tmonth);
+					range.tdays=toData.tdays;
+					tday=range.tdays[0];
+					checkObj.tdays=range.tdays[0];
+				};
+				result=`${fyear+'-'+fmonth+'-'+fday+'至'+tyear+'-'+tmonth+'-'+tday}`;
+				obj={
+					fyear,fmonth,fday,tyear,tmonth,tday
+				}
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=arr;
+				})
+				this.$emit("change",{
+					result:result,
+					value:result.split("至"),
+					obj:obj
+				})
+				
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 183 - 0
components/w-picker/region-picker.vue

@@ -0,0 +1,183 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.provinces" :key="index">{{item.label}}</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.citys" :key="index">{{item.label}}</view>
+			</picker-view-column>
+			<picker-view-column v-if="!hideArea">
+				<view class="w-picker-item" v-for="(item,index) in range.areas" :key="index">{{item.label}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	import areaData from "./areadata/areadata.js"
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{
+					provinces:[],
+					citys:[],
+					areas:[]
+				},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[Array,String],
+				default:""
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			hideArea:{
+				type:Boolean,
+				default:false
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			getData(){
+				//用来处理初始化数据
+				let provinces=areaData;
+				let dVal=[];
+				let value=this.value;
+				let a1=value[0];//默认值省
+				let a2=value[1];//默认值市
+				let a3=value[2];//默认值区、县
+				let province,city,area;
+				let provinceIndex=provinces.findIndex((v)=>{
+					return v[this.defaultType]==a1
+				});
+				provinceIndex=value?(provinceIndex!=-1?provinceIndex:0):0;
+				let citys=provinces[provinceIndex].children;
+				let cityIndex=citys.findIndex((v)=>{
+					return v[this.defaultType]==a2
+				});
+				cityIndex=value?(cityIndex!=-1?cityIndex:0):0;
+				let areas=citys[cityIndex].children;
+				let areaIndex=areas.findIndex((v)=>{
+					return v[this.defaultType]==a3;
+				});
+				areaIndex=value?(areaIndex!=-1?areaIndex:0):0;
+				dVal=this.hideArea?[provinceIndex,cityIndex]:[provinceIndex,cityIndex,areaIndex];
+				province=provinces[provinceIndex];
+				city=citys[cityIndex];
+				area=areas[areaIndex];
+				let obj=this.hideArea?{
+					province,
+					city
+				}:{
+					province,
+					city,
+					area
+				}
+				return this.hideArea?{
+					provinces,
+					citys,
+					dVal,
+					obj
+				}:{
+					provinces,
+					citys,
+					areas,
+					dVal,
+					obj
+				}
+			},
+			initData(){
+				let dataData=this.getData();
+				let provinces=dataData.provinces;
+				let citys=dataData.citys;
+				let areas=this.hideArea?[]:dataData.areas;
+				let obj=dataData.obj;
+				let province=obj.province,city=obj.city,area=this.hideArea?{}:obj.area;
+				let value=this.hideArea?[province.value,city.value]:[province.value,city.value,area.value];
+				let result=this.hideArea?`${province.label+city.label}`:`${province.label+city.label+area.label}`;
+				this.range=this.hideArea?{
+					provinces,
+					citys,
+				}:{
+					provinces,
+					citys,
+					areas
+				};
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=dataData.dVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let provinceIndex=arr[0],cityIndex=arr[1],areaIndex=this.hideArea?0:arr[2];
+				let provinces=areaData;
+				let citys=(provinces[provinceIndex]&&provinces[provinceIndex].children)||provinces[provinces.length-1].children||[];
+				let areas=this.hideArea?[]:((citys[cityIndex]&&citys[cityIndex].children)||citys[citys.length-1].children||[]);
+				let province=provinces[provinceIndex]||provinces[provinces.length-1],
+				city=citys[cityIndex]||[citys.length-1],
+				area=this.hideArea?{}:(areas[areaIndex]||[areas.length-1]);
+				let obj=this.hideArea?{
+					province,
+					city
+				}:{
+					province,
+					city,
+					area
+				}
+				if(this.checkObj.province.label!=province.label){
+					//当省更新的时候需要刷新市、区县的数据;
+					this.range.citys=citys;
+					if(!this.hideArea){
+						this.range.areas=areas;
+					}
+					
+				}
+				if(this.checkObj.city.label!=city.label){
+					//当市更新的时候需要刷新区县的数据;
+					if(!this.hideArea){
+						this.range.areas=areas;
+					}
+				}
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=arr;
+				})
+				let result=this.hideArea?`${province.label+city.label}`:`${province.label+city.label+area.label}`;
+				let value=this.hideArea?[province.value,city.value]:[province.value,city.value,area.value];
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";	
+</style>
+

+ 129 - 0
components/w-picker/selector-picker.vue

@@ -0,0 +1,129 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range" :key="index">{{item[nodeKey]}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			options:{
+				type:[Array,Object],
+				default(){
+					return []
+				}
+			},
+			value:{
+				type:String,
+				default:""
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			defaultProps:{
+				type:Object,
+				default(){
+					return{
+						label:"label",
+						value:"value"
+					}
+				}
+			}
+		},
+		data() {
+			return {
+				pickVal:[]
+			};
+		},
+		computed:{
+			nodeKey(){
+				return this.defaultProps.label;
+			},
+			nodeValue(){
+				return this.defaultProps.value;
+			},
+			range(){
+				return this.options
+			}
+		},
+		watch:{
+			value(val){
+				if(this.options.length!=0){
+					this.initData();
+				}
+			},
+			options(val){
+				this.initData();
+			}
+		},
+		created() {
+			if(this.options.length!=0){
+				this.initData();
+			}
+		},
+		methods:{
+			initData(){
+				let dVal=this.value||"";
+				let data=this.range;
+				let pickVal=[0];
+				let cur=null;
+				let label="";
+				let value,idx;
+				if(this.defaultType==this.nodeValue){
+					value=data.find((v)=>v[this.nodeValue]==dVal);
+					idx=data.findIndex((v)=>v[this.nodeValue]==dVal);
+				}else{
+					value=data.find((v)=>v[this.nodeKey]==dVal);
+					idx=data.findIndex((v)=>v[this.nodeKey]==dVal);
+				}
+				pickVal=[idx!=-1?idx:0];
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				if(this.defaultType==this.nodeValue){
+					this.$emit("change",{
+						result:value?value[this.nodeKey]:data[0][this.nodeKey],
+						value:dVal||data[0][this.nodeKey],
+						obj:value?value:data[0]
+					})
+				}else{
+					this.$emit("change",{
+						result:dVal||data[0][this.nodeKey],
+						value:value?value[this.nodeValue]:data[0][this.nodeValue],
+						obj:value?value:data[0]
+					})
+				}
+				
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let pickVal=[arr[0]||0];
+				let data=this.range;
+				let cur=data[arr[0]];
+				let label="";
+				let value="";
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:cur[this.nodeKey],
+					value:cur[this.nodeValue],
+					obj:cur
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 250 - 0
components/w-picker/shortterm-picker.vue

@@ -0,0 +1,250 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.dates" :key="index">{{item.label}}</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item.label}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item.label}}分</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			expand:{
+				type:[Number,String],
+				default:30
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$/,example="2019-12-12 18:05:00或者2019-12-12 18:05";
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let months=[],days=[],sections=[];
+				let disabledAfter=this.disabledAfter;
+				let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
+				let totalDays=new Date(year,month,0).getDate();//计算当月有几天;
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				};
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				return{
+					months,
+					days,
+					sections
+				}
+			},
+			getData(dVal){
+				//用来处理初始化数据
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let dates=[],hours=[],minutes=[];
+				let curDate=new Date();
+				let curYear=curDate.getFullYear();
+				let curMonth=curDate.getMonth();
+				let curDay=curDate.getDate();
+				let aDate=new Date(curYear,curMonth,curDay);
+				for(let i=0;i<this.expand*1;i++){
+					aDate=new Date(curYear,curMonth,curDay+i);
+					let year=aDate.getFullYear();
+					let month=aDate.getMonth()+1;
+					let day=aDate.getDate();
+					let label=year+"-"+this.formatNum(month)+"-"+this.formatNum(day);
+					switch(i){
+						case 0:
+							label="今天";
+							break;
+						case 1:
+							label="明天";
+							break;
+						case 2:
+							label="后天";
+							break
+					}
+					dates.push({
+						label:label,
+						value:year+"-"+this.formatNum(month)+"-"+this.formatNum(day)
+					})
+				};
+				for(let i=0;i<24;i++){
+					hours.push({
+						label:this.formatNum(i),
+						value:this.formatNum(i)
+					})
+				}
+				for(let i=0;i<60;i++){
+					minutes.push({
+						label:this.formatNum(i),
+						value:this.formatNum(i)
+					})
+				}
+				return {
+					dates,
+					hours,
+					minutes
+				}
+			},
+			getDefaultDate(){
+				let value=this.value;
+				let reg=/-/g;
+				let defaultDate=value?new Date(value.replace(reg,"/")):new Date();
+				let defaultYear=defaultDate.getFullYear();
+				let defaultMonth=defaultDate.getMonth()+1;
+				let defaultDay=defaultDate.getDate();
+				let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
+				return{
+					defaultDate,
+					defaultYear,
+					defaultMonth,
+					defaultDay,
+					defaultDays
+				}
+			},
+			getDval(){
+				let value=this.value;
+				let dVal=null;
+				let aDate=new Date();
+				let year=this.formatNum(aDate.getFullYear());
+				let month=this.formatNum(aDate.getMonth()+1);
+				let day=this.formatNum(aDate.getDate());
+				let date=this.formatNum(year)+"-"+this.formatNum(month)+"-"+this.formatNum(day);
+				let hour=aDate.getHours();
+				let minute=aDate.getMinutes();
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[date,hour,minute]
+					}else{
+						let v=value.split(" ");
+						dVal=[v[0],...v[1].split(":")];
+					}
+				}else{
+					dVal=[date,hour,minute]
+				}
+				return dVal;
+			},
+			initData(){
+				let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
+				let dates=[],hours=[],minutes=[];
+				let dVal=[],pickVal=[];
+				let value=this.value;
+				let reg=/-/g;
+				let range={};
+				let result="",full="",date,hour,minute,obj={};
+				let defaultDate=this.getDefaultDate();
+				let defaultYear=defaultDate.defaultYear;
+				let defaultMonth=defaultDate.defaultMonth;
+				let defaultDay=defaultDate.defaultDay;
+				let defaultDays=defaultDate.defaultDays;
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let dateData=[];
+				dVal=this.getDval();
+				dateData=this.getData(dVal);
+				dates=dateData.dates;
+				hours=dateData.hours;
+				minutes=dateData.minutes;
+				pickVal=[
+					dates.findIndex(n => n.value == dVal[0])!=-1?dates.findIndex(n => n.value == dVal[0]):0,
+					hours.findIndex(n => n.value == dVal[1])!=-1?hours.findIndex(n => n.value == dVal[1]):0,
+					minutes.findIndex(n => n.value == dVal[2])!=-1?minutes.findIndex(n => n.value == dVal[2]):0,
+				];
+				range={dates,hours,minutes};
+				date=dVal[0]?dVal[0]:dates[0].label;
+				hour=dVal[1]?dVal[1]:hours[0].label;
+				minute=dVal[2]?dVal[2]:minutes[0].label;
+				result=full=`${date+' '+hour+':'+minute}`;
+				obj={
+					date,
+					hour,
+					minute
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let date="",hour="",minute="";
+				let result="",full="",obj={};
+				let disabledAfter=this.disabledAfter;
+				date=(arr[0]||arr[0]==0)?data.dates[arr[0]]||data.dates[data.dates.length-1]:"";
+				hour=(arr[1]||arr[1]==0)?data.hours[arr[1]]||data.hours[data.hours.length-1]:"";
+				minute=(arr[2]||arr[2]==0)?data.minutes[arr[2]]||data.minutes[data.minutes.length-1]:"";
+				result=full=`${date.label+' '+hour.label+':'+minute.label+':00'}`;
+				obj={
+					date,
+					hour,
+					minute
+				}
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 218 - 0
components/w-picker/time-picker.vue

@@ -0,0 +1,218 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}分</view>
+			</picker-view-column>
+			<picker-view-column v-if="second">
+				<view class="w-picker-item" v-for="(item,index) in range.seconds" :key="index">{{item}}秒</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			second:{
+				type:Boolean,
+				default:true
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{2}:\d{2}:\d{2}$/,example="18:00:05";
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day,hour,minute){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				for(let hour=0;hour<24;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<60;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				for(let second=0;second<60;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return{
+					hours,
+					minutes,
+					seconds
+				}
+			},
+			getData(curDate){
+				//用来处理初始化数据
+				let hours=[],minutes=[],seconds=[];
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let fields=this.fields;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				for(let hour=0;hour<24;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<60;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				for(let second=0;second<60;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return this.second?{
+					hours,
+					minutes,
+					seconds
+				}:{
+					hours,
+					minutes
+				}
+			},
+			getCurrenDate(){
+				let curDate=new Date();
+				let curHour=curDate.getHours();
+				let curMinute=curDate.getMinutes();
+				let curSecond=curDate.getSeconds();
+				return this.second?{
+					curHour,
+					curMinute,
+					curSecond
+				}:{
+					curHour,
+					curMinute,
+				}
+			},
+			getDval(){
+				let value=this.value;
+				let fields=this.fields;
+				let dVal=null;
+				let aDate=new Date();
+				let hour=this.formatNum(aDate.getHours());
+				let minute=this.formatNum(aDate.getMinutes());
+				let second=this.formatNum(aDate.getSeconds());
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[hour,minute,second]
+					}else{
+						dVal=value?value.split(":"):[];
+					}
+				}else{
+					dVal=this.second?[hour,minute,second]:[hour,minute]
+				}
+				return dVal;
+			},
+			initData(){
+				let curDate=this.getCurrenDate();
+				let dateData=this.getData(curDate);
+				let pickVal=[],obj={},full="",result="",hour="",minute="",second="";
+				let dVal=this.getDval();
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let hours=dateData.hours;
+				let minutes=dateData.minutes;
+				let seconds=dateData.seconds;
+				let defaultArr=this.second?[
+					dVal[0]&&hours.indexOf(dVal[0])!=-1?hours.indexOf(dVal[0]):0,
+					dVal[1]&&minutes.indexOf(dVal[1])!=-1?minutes.indexOf(dVal[1]):0,
+					dVal[2]&&seconds.indexOf(dVal[2])!=-1?seconds.indexOf(dVal[2]):0
+				]:[
+					dVal[0]&&hours.indexOf(dVal[0])!=-1?hours.indexOf(dVal[0]):0,
+					dVal[1]&&minutes.indexOf(dVal[1])!=-1?minutes.indexOf(dVal[1]):0
+				];
+				pickVal=disabledAfter?defaultArr:(curFlag?(this.second?[
+					hours.indexOf(this.formatNum(curDate.curHour)),
+					minutes.indexOf(this.formatNum(curDate.curMinute)),
+					seconds.indexOf(this.formatNum(curDate.curSecond)),
+				]:[
+					hours.indexOf(this.formatNum(curDate.curHour)),
+					minutes.indexOf(this.formatNum(curDate.curMinute))
+				]):defaultArr);
+				this.range=dateData;
+				this.checkObj=obj;
+				hour=dVal[0]?dVal[0]:hours[0];
+				minute=dVal[1]?dVal[1]:minutes[0];
+				if(this.second)second=dVal[2]?dVal[2]:seconds[0];
+				result=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute}`;
+				full=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute+':00'}`;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let hour="",minute="",second="",result="",full="",obj={};
+				hour=(arr[0]||arr[0]==0)?data.hours[arr[0]]||data.hours[data.hours.length-1]:"";
+				minute=(arr[1]||arr[1]==0)?data.minutes[arr[1]]||data.minutes[data.minutes.length-1]:"";
+				if(this.second)second=(arr[2]||arr[2]==0)?data.seconds[arr[2]]||data.seconds[data.seconds.length-1]:"";
+				obj=this.second?{
+					hour,
+					minute,
+					second
+				}:{
+					hour,
+					minute
+				};
+				this.checkObj=obj;
+				result=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute}`;
+				full=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute+':00'}`;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";	
+</style>
+

+ 26 - 0
components/w-picker/w-picker.css

@@ -0,0 +1,26 @@
+.w-picker-flex2{
+	flex:2;
+}
+.w-picker-flex1{
+	flex:1;
+}
+.w-picker-view {
+	width: 100%;
+	height: 476upx;
+	overflow: hidden;
+	background-color: rgba(255, 255, 255, 1);
+	z-index: 666;
+}
+.d-picker-view{
+	height: 100%;
+}
+
+.w-picker-item {
+  text-align: center;
+  width: 100%;
+  height: 88upx;
+  line-height: 88upx;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-size: 30upx;
+}

+ 341 - 0
components/w-picker/w-picker.vue

@@ -0,0 +1,341 @@
+<template name="w-picker">
+	<view class="w-picker" :key="createKey" :data-key="createKey">
+		<view class="mask" :class="{'visible':visible}" @tap="onCancel" @touchmove.stop.prevent catchtouchmove="true"></view>
+		<view class="w-picker-cnt" :class="{'visible':visible}">
+			<view class="w-picker-header"  @touchmove.stop.prevent catchtouchmove="true">
+				<text @tap.stop.prevent="onCancel">取消</text>
+				<slot></slot>
+				<text :style="{'color':themeColor}" @tap.stop.prevent="pickerConfirm">确定</text>
+			</view>
+			<date-picker 
+				v-if="mode=='date'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:fields="fields"
+				:item-height="itemHeight"
+				:current="current"
+				:disabled-after="disabledAfter"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</date-picker>
+			
+			<range-picker
+				v-if="mode=='range'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</range-picker>
+			
+			<half-picker
+				v-if="mode=='half'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				:disabled-after="disabledAfter"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</half-picker>
+			
+			<shortterm-picker
+				v-if="mode=='shortTerm'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				expand="60"
+				:disabled-after="disabledAfter"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</shortterm-picker>
+			
+			<time-picker
+				v-if="mode=='time'"
+				class="w-picker-wrapper"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				:disabled-after="disabledAfter"
+				:second="second"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</time-picker>
+			
+			<selector-picker
+				v-if="mode=='selector'"
+				class="w-picker-wrapper"
+				:value="value"
+				:item-height="itemHeight"
+				:options="options"
+				:default-type="defaultType"
+				:default-props="defaultProps"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</selector-picker>
+			
+			<region-picker
+				v-if="mode=='region'"
+				class="w-picker-wrapper"
+				:value="value"
+				:hide-area="hideArea"
+				:default-type="defaultType"
+				:item-height="itemHeight"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</region-picker>
+			
+			<linkage-picker
+				v-if="mode=='linkage'"
+				class="w-picker-wrapper"
+				:value="value"
+				:options="options"
+				:level="level"
+				:default-type="defaultType"
+				:default-props="defaultProps"
+				:item-height="itemHeight"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</linkage-picker>
+		</view>
+	</view>
+</template>
+
+<script>
+	import datePicker from "./date-picker.vue"
+	import rangePicker from "./range-picker.vue"
+	import halfPicker from "./half-picker.vue"
+	import shorttermPicker from "./shortterm-picker.vue"
+	import timePicker from "./time-picker.vue"
+	import selectorPicker from "./selector-picker.vue"
+	import regionPicker from "./region-picker.vue"
+	import linkagePicker from "./linkage-picker.vue"
+	export default {
+		name:"w-picker",
+		components:{
+			datePicker,
+			rangePicker,
+			halfPicker,
+			timePicker,
+			selectorPicker,
+			shorttermPicker,
+			regionPicker,
+			linkagePicker
+		},
+		props:{
+			mode:{
+				type:String,
+				default:"date"
+			},
+			value:{//默认值
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认显示当前时间,如果是,传的默认值将失效
+				type:Boolean,
+				default:false
+			},
+			themeColor:{//确认按钮主题颜色
+				type:String,
+				default:"#f5a200"
+			},
+			fields:{//日期颗粒度:year、month、day、hour、minute、second
+				type:String,
+				default:"date"
+			},
+			disabledAfter:{//是否禁用当前之后的日期
+				type:Boolean,
+				default:false
+			},
+			second:{//time-picker是否显示秒
+				type:Boolean,
+				default:true
+			},
+			options:{//selector,region数据源
+				type:[Array,Object],
+				default(){
+					return []
+				}
+			},
+			defaultProps:{//selector,linkagle字段转换配置
+				type:Object,
+				default(){
+					return{
+						label:"label",
+						value:"value",
+						children:"children"
+					}
+				}
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			hideArea:{//mode=region时,是否隐藏区县列
+				type:Boolean,
+				default:false
+			},
+			level:{
+				//多级联动层级,表示几级联动,区间2-4;
+				type:[Number,String],
+				default:2
+			},
+			timeout:{//是否开启点击延迟,当快速滚动 还没有滚动完毕点击关闭时得到的值是不准确的
+				type:Boolean,
+				default:false
+			},
+			expand:{//mode=shortterm 默认往后拓展天数
+				type:[Number,String],
+				default:30
+			},
+			startYear:{
+				type:[String,Number],
+				default:1970
+			},
+			endYear:{
+				type:[String,Number],
+				default:new Date().getFullYear()
+			},
+			visible:{
+				type:Boolean,
+				default:false
+			}
+		},
+		created() {
+			this.createKey=Math.random()*1000;
+		},
+		data() {
+			return {
+				itemHeight:`height: ${uni.upx2px(88)}px;`,
+				result:{},
+				confirmFlag:true
+			};
+		},
+		methods:{
+			touchStart(){
+				if(this.timeout){
+					this.confirmFlag=false;
+				}
+			},
+			touchEnd(){
+				if(this.timeout){
+					setTimeout(()=>{
+						this.confirmFlag=true;
+					},500)
+				}
+			},
+			handlerChange(res){
+				let _this=this;
+				this.result={...res};
+			},
+			show(){
+				this.$emit("update:visible",true);
+			},
+			hide(){
+				this.$emit("update:visible",false);
+			},
+			onCancel(res){
+				this.$emit("update:visible",false);
+				this.$emit("cancel");
+			},
+			pickerConfirm(){
+				if(!this.confirmFlag){
+					return;
+				};
+				this.$emit("confirm",this.result);
+				this.$emit("update:visible",false);
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.w-picker-item {
+	  text-align: center;
+	  width: 100%;
+	  height: 88upx;
+	  line-height: 88upx;
+	  text-overflow: ellipsis;
+	  white-space: nowrap;
+	  font-size: 30upx;
+	}
+	.w-picker{
+		z-index: 888;
+		.mask {
+		  position: fixed;
+		  z-index: 1000;
+		  top: 0;
+		  right: 0;
+		  left: 0;
+		  bottom: 0;
+		  background: rgba(0, 0, 0, 0.6);
+		  visibility: hidden;
+		  opacity: 0;
+		  transition: all 0.3s ease;
+		  z-index: 999999;
+		}
+		.mask.visible{
+			visibility: visible;
+			opacity: 1;
+		}
+		.w-picker-cnt {
+		  position: fixed;
+		  bottom: 0;
+		  left: 0;
+		  width: 100%;
+		  transition: all 0.3s ease;
+		  transform: translateY(100%);
+		  z-index: 999999;
+		  background-color: #fff;
+		}
+		.w-picker-cnt.visible {
+		  transform: translateY(0);
+		}
+		.w-picker-header{
+		  display: flex;
+		  align-items: center;
+		  padding: 0 30upx;
+		  height: 88upx;
+		  background-color: #fff;
+		  position: relative;
+		  text-align: center;
+		  font-size: 32upx;
+		  justify-content: space-between;
+		  border-bottom: solid 1px #eee;
+		  .w-picker-btn{
+		  	font-size: 30upx;
+		  }
+		}
+		
+		.w-picker-hd:after {
+		  content: ' ';
+		  position: absolute;
+		  left: 0;
+		  bottom: 0;
+		  right: 0;
+		  height: 1px;
+		  border-bottom: 1px solid #e5e5e5;
+		  color: #e5e5e5;
+		  transform-origin: 0 100%;
+		  transform: scaleY(0.5);
+		}
+	}
+</style>

+ 82 - 0
libs/axios.js

@@ -0,0 +1,82 @@
+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,'pppp')
+				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'){
+						uni.showToast({
+							icon:'none',
+							title: ret.message,
+							mask: true
+						})
+					}
+					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
+}

+ 95 - 0
libs/getDate.js

@@ -0,0 +1,95 @@
+// 引入 moment 时间插件
+import moment from 'moment'
+// 获取今日/昨日/本周/上周/本月/上月 时间
+export default {
+  // 获取今日的开始结束时间
+  getToday () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().startOf('day').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  // 获取昨日的开始结束时间
+  getYesterday () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().add(-1, 'days').startOf('day').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().add(-1, 'days').endOf('day').valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  //  近7天
+  getRecentday () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment().subtract('days', 6).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  // 获取当前周的开始到当天结束时间
+  getCurrWeekDays () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().week(moment().week()).startOf('week').add('days').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  // 获取上一周的开始结束时间
+  getLastWeekDays () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().week(moment().week() - 1).startOf('week').add('days').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().week(moment().week() - 1).endOf('week').add('days').valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  // 获取当前月的开始到当天结束时间
+  getCurrMonthDays () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().month(moment().month()).startOf('month').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  // 获取上一个月的开始结束时间
+  getLastMonthDays () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().month(moment().month() - 1).startOf('month').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().month(moment().month() - 1).endOf('month').valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  // 获取上2个月的开始结束时间
+  getLastTwoMonthDays () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().month(moment().month() - 2).startOf('month').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().month(moment().month() - 2).endOf('month').valueOf()).format('YYYY-MM-DD')
+    return obj
+  },
+  // 获取近3个月到今天的开始结束时间
+  getThreeMonthDays () {
+    const obj = {
+      starttime: '',
+      endtime: ''
+    }
+    obj.starttime = moment(moment().month(moment().month() - 2).startOf('month').valueOf()).format('YYYY-MM-DD')
+    obj.endtime = moment(moment().valueOf()).format('YYYY-MM-DD')
+    return obj
+  }
+}

+ 70 - 0
libs/tools.js

@@ -0,0 +1,70 @@
+export function objToUrl(obj) {
+  let uri = '';
+  let keys = Object.keys(obj);
+  keys.forEach(item => {
+    uri += '&' + item + '=' + obj[item];
+  });
+
+  uri = uri.substr(1);
+
+  return uri;
+}
+
+export function parseQueryString(url) {
+    var obj = {};
+    var keyvalue = [];
+    var key = "",
+        value = "";
+    var paraString = url.substring(url.indexOf("?") + 1, url.length).split("&");
+    for (var i in paraString) {
+        keyvalue = paraString[i].split("=");
+        key = keyvalue[0];
+        value = keyvalue[1];
+        obj[key] = value;
+    }
+    return obj;
+}
+// 保存图片到阿里云
+export const saveImgToAliOss = function(src,callback){
+	console.log(src,getApp().globalData.baseUrl,'getApp().globalData.baseUrl')
+	const authorization = getApp().globalData.token
+	uni.uploadFile({
+		url: getApp().globalData.baseUrl + 'upload/', //自行修改各自的对应的接口 
+		filePath: src,
+		name: 'file',
+		header: {'X-AUTH-TOKEN':authorization},
+		success: (uploadFileRes) => {
+			if (uploadFileRes) {
+				let res = JSON.parse(uploadFileRes.data);
+				callback(res)
+			}
+			uni.showToast({
+				icon: 'none',
+				title: uploadFileRes ? '保存图片成功' : '保存图片失败'
+			})
+		},
+		fail:(error) => {
+			console.log(error)
+		}
+	});
+}
+// 小数点后两位
+export const numberToFixed = function (val, num, max) {
+  let maxNums = max || 100000000
+  let _value = val + ''
+  _value = _value.replace(/[^\d.]/g, '')// 清楚数字和.以外的字数
+  _value = _value.replace(/^\./g, '')
+  _value = _value.replace(/\.{2,}/g, '')// 保留第一个,清楚多余的
+
+  if (num == 1)_value = _value.replace(/^(\-)*(\d+)\.(\d).*$/, '$1$2.$3')
+  else if (num == 3)_value = _value.replace(/^(\-)*(\d+)\.(\d\d\d).*$/, '$1$2.$3')
+  else if (num == 4)_value = _value.replace(/^(\-)*(\d+)\.(\d\d\d\d).*$/, '$1$2.$3')
+  else if (num == 5)_value = _value.replace(/^(\-)*(\d+)\.(\d\d\d\d\d).*$/, '$1$2.$3')
+  else if (num == 0)_value = _value.indexOf('.') >= 0 ? _value.split('.')[0] : _value
+  else _value = _value.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3')
+  // console.log(_value,maxNums,Number(_value)>Number(maxNums),'_value')
+  return Number(_value)>Number(maxNums) ? maxNums : _value
+}
+
+
+  

+ 5 - 0
libs/validate.js

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

+ 17 - 0
main.js

@@ -0,0 +1,17 @@
+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)
+
+const app = new Vue({
+	store,
+    ...App
+})
+app.$mount()

+ 85 - 0
manifest.json

@@ -0,0 +1,85 @@
+{
+    "name" : "分类垃圾录入端",
+    "appid" : "__UNI__778E110",
+    "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" : "wxc0af8df4c4a03f3f",
+        "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
+    }
+}

+ 56 - 0
package-lock.json

@@ -0,0 +1,56 @@
+{
+  "name": "zxyj-rider-mini-html",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "charenc": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+      "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
+    },
+    "crypt": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+      "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+    },
+    "md5": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+      "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+      "requires": {
+        "charenc": "0.0.2",
+        "crypt": "0.0.2",
+        "is-buffer": "~1.1.6"
+      }
+    },
+    "moment": {
+      "version": "2.29.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
+      "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
+    },
+    "uview-ui": {
+      "version": "1.7.9",
+      "resolved": "https://registry.npmjs.org/uview-ui/-/uview-ui-1.7.9.tgz",
+      "integrity": "sha512-YSZvz69Yt/7qoJohc9+tWVQsb4fUiE30OnvTZhUA0H+SkxhQXK6MgNsmL/lZ7RVqFLwawQBzDths2ReluvFOcA=="
+    },
+    "weapp-qrcode": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/weapp-qrcode/-/weapp-qrcode-1.0.0.tgz",
+      "integrity": "sha512-4sa3W0rGDVJ9QaeZpAKlAuUxVyjhDwiUqHyGK/jJMsRMXnhb4yO8qWU/pZruMo+iT5J6CraS67lDMFb1VY+RaA==",
+      "requires": {
+        "extend": "^3.0.2"
+      }
+    }
+  }
+}

+ 23 - 0
package.json

@@ -0,0 +1,23 @@
+{
+  "name": "zxyj-rider-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-rider-mini-html.git"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "md5": "^2.3.0",
+    "moment": "^2.29.1",
+    "uview-ui": "^1.7.9",
+    "weapp-qrcode": "^1.0.0"
+  },
+  "devDependencies": {}
+}

+ 88 - 0
pages.json

@@ -0,0 +1,88 @@
+{
+	"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/index/addData",
+			"style": {
+				"navigationBarTitleText": "垃圾数据录入"
+			}
+		},
+		{
+			"path": "pages/login/login",
+			"style": {
+				"navigationBarTitleText": "授权登录"
+			}
+		},
+		{
+			"path": "pages/login/phoneLogin",
+			"style": {
+				"navigationBarTitleText": "手机号登录"
+			}
+		},
+		{
+			"path": "pages/userCenter/index",
+			"style": {
+				"navigationBarTitleText": "个人中心",
+				"navigationBarBackgroundColor": "#0DC55F",
+				"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": "##0DC55F",
+		"backgroundColor": "#ffffff",
+		"fontSize": "14px",
+		"list": [
+		{
+			"pagePath": "pages/index/index",
+			"iconPath": "static/index/logo.png",
+			"selectedIconPath": "static/index/logo-active.png",
+			"text": "首页"
+		}, 
+		{
+			"pagePath": "pages/userCenter/index",
+			"iconPath": "static/tabbar/user.png",
+			"selectedIconPath": "static/tabbar/user-active.png",
+			"text": "我的"
+		}]
+	}
+}

+ 223 - 0
pages/agreement/agreement.vue

@@ -0,0 +1,223 @@
+<template>
+	<view class="content">
+		<view class="page-content">
+			<view class="page-care agreement">
+				<view class="title">-分类垃圾录入端用户服务协议(声明)-</view>
+				<view class="xy-cons">
+					<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 class="MsoNormal">(二) 第三方网站对本用户小程序的链接</view>
+						<view class="MsoNormal">
+							第三方网站对本用户小程序的链接,并不必然表示本公司对第三方作出了某种认可、授权、赞助,或表明其属于本公司的关联企业、合资企业、合作企业或业务伙伴。多数情况下,本公司并未意识到或授权第三方提供对本用户小程序的链接。
+						</view>
+						
+					</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">本用户小程序将采取合理有效的安全手段保护用户所提供的用户信息,在未经得用户许可前,本公司承诺并保证绝不将用户的任何个人信息和资料提供给任何无关第三方,但有下列情形之一的除外:</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">
+						五、知识产权保护
+					</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>
+					</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">
+						七、服务变更、暂停、中断或终止
+					</view>
+					<view class="MsoNormal">
+						如因系统升级或维护需要而暂停本用户小程序网络服务时,本用户小程序将尽可能事先通告。
+					</view>
+					<view class="MsoNormal">
+						如发生下列情形之一,本用户小程序有权随时暂停、中断或终止向用户提供本用户小程序用户服务而无需通知,并有权拒绝用户于当前、未来某时段或永久使用本用户小程序所提供服务之全部或任何部分:(1)用户提供任何错误、不实、过时、不完整或具有误导性的信息和资料,或本用户小程序有理由或有依据怀疑前述信息和资料为错误、不实、过时、不完整或具误导性的;(2)用户违反本法律声明或相关使用协议中规定的任何使用规则。
+					</view>
+					<view class="MsoNormal">
+						除上述情形外,本用户小程序同时保留在不事先通知用户的情况下随时暂停、中断或终止部分或全部网络服务的权利,对于所有服务的暂停、中断或终止而造成的任何损失,除法律另有规定或双方另有约定外,本公司不对用户或任何第三方承担任何法律责任。
+					</view>
+					<view class="MsoNormal">
+						八、条款修改
+					</view>
+					<view class="MsoNormal">
+						本公司保留在任何时间自行修改、增删本法律声明中的任何内容和条款的权利。您每次登录或使用本用户小程序时,均视为同意接受当时已生效的用户声明条款的制约。因此,建议您每次登录或使用本用户小程序时查看本法律声明是否修订或增删及修订或增删的情况
+					</view>
+					<view class="MsoNormal">
+						九、法律适用与司法管辖
+					</view>
+					<view class="MsoNormal">
+						本法律声明的制定、执行、解释及争议问题的解决和处理均适用中华人民共和国法律、法规。双方因使用本用户小程序所产生的争议,若协商不成,任何一方可向本公司所在地人民法院提起诉讼。
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		components: {
+		},
+		data() {
+			return {
+			}
+		},
+		onLoad(option) {
+			uni.setNavigationBarTitle({
+				title: "服务协议"
+			})
+			// 开启分享
+			uni.showShareMenu({
+				withShareTicket: true,
+				menus: ['shareAppMessage', 'shareTimeline']
+			})
+		},
+		methods: {
+		}
+	}
+</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>

+ 326 - 0
pages/index/addData.vue

@@ -0,0 +1,326 @@
+<template>
+	<view class="content">
+		<view>
+			<view class="form-data">
+				<u-form :model="form" ref="uForm" :label-width="140" :error-type="['toast']">
+						<u-form-item prop="gatherNum_other" label="其他垃圾:" >
+							<u-input :disabled="isView" @input="numberToFixed('gatherNum_other',0,999999)" type="number" placeholder="请输入" v-model="form.gatherNum_other" />
+							<text slot="right" style="color: #000000;">桶(约{{form.gatherNum_other*240}}kg)</text>
+						</u-form-item>
+						<u-form-item prop="gatherNum_chuyu" label="厨余垃圾:" >
+							<u-input :disabled="isView" @input="numberToFixed('gatherNum_chuyu',0,999999)" placeholder="请输入" type="number" v-model="form.gatherNum_chuyu" />
+							<text slot="right" style="color: #000000;">桶(约{{form.gatherNum_chuyu*240}}kg)</text>
+						</u-form-item>
+						<u-form-item prop="gatherNum_jianzhu" label="建筑垃圾:" >
+							<u-input :disabled="isView" @input="numberToFixed('gatherNum_jianzhu',2,999999)" placeholder="请输入" type="digit" v-model="form.gatherNum_jianzhu" />
+							<text slot="right" style="color: #000000;">吨(约{{form.gatherNum_jianzhu*1000}}kg)</text>
+						</u-form-item>
+				</u-form>
+			</view>
+			<view class="form-data remark">
+				<text>信息备注</text>
+				<textarea :disabled="isView" :auto-height="true" :maxlength="30" v-model="form.remarks" class="receiveAddress"/>
+			</view>
+			<view class="form-data qyImg">
+				<text>清运照片</text>
+				<view class="info-main">
+					<view v-if="photograph.length<3 && !isView" class="camera-btn">
+						<view @click="goPhotograph" class="photograph-camera">
+							<u-icon name="camera" color="#bfbfbf" size="60" ></u-icon>
+						</view>
+						<view >
+							(最多3张)
+						</view>
+						<view >
+							(每张最大不超过10M)
+						</view>
+					</view>
+					<view v-show="photograph.length" v-for="(item,index) in photograph" :key="index" class="photograph-con">
+						<u-icon v-show="!isView" name="close-circle-fill" color="#fa3534" size="50" class="close-circle-icon" @click="cancelPhotograph(index)"></u-icon>
+						<u-image width="100%" height="100%" :src="item" @click="previewPictures(item)"></u-image>
+					</view>
+				</view>
+			</view>
+			<u-button v-if="isView" @click="isView = false" class="submitBtn" type="waring" shape="circle">修改</u-button>
+			<u-button v-else @click="submit" :loading="loading" class="submitBtn" type="success" shape="circle">提交</u-button>
+			<uni-popup ref="openModal" type="center">
+				<uni-popup-dialog content="确认提交吗?" @confirm="onOk" :title="title"></uni-popup-dialog>
+			</uni-popup>
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniPopup from '@/components/uni-popup/uni-popup.vue'
+	import uniPopupDialog from '@/components/uni-popup/uni-popup-dialog.vue'
+	import {gatherSave,gatherDetail} from '@/api/index.js'
+	import {numberToFixed,saveImgToAliOss} from '@/libs/tools.js'
+	export default {
+		components: {
+			uniPopup,uniPopupDialog
+		},
+		data() {
+			return {
+				title:'',
+				loading:false, // 提交按钮加载圈
+				photograph: [], //  图片路径
+				form: {
+					gatherNum_other: '', // 其它垃圾
+					gatherNum_chuyu: '', // 厨余垃圾
+					gatherNum_jianzhu: '', // 建筑垃圾
+					remarks: '' // 备注
+				},
+				isView: false ,// 是否是查看详情
+				itemId:''
+			};
+		},
+		onLoad(option) {
+			console.log(option,option.id,'ppppppppp')
+			// 查看
+			if(option && option.id){
+				this.isView = true
+				this.itemId=option.id
+				this.getDetailData()
+			}else {
+				console.log('--------新增')
+				this.photograph = []
+				this.form.remarks = ''
+				this.$refs.uForm.resetFields()
+			}
+		},
+		onUnload() {
+		},
+		methods: {
+			// 保留几位小数
+			numberToFixed: function (key, num, max) {
+			  let val = this.form[key]
+			  let ret = numberToFixed(val, num, max)
+			  this.$nextTick(() => {
+			  	this.form[key] = ret
+			  })
+			},
+			// 获取详情数据
+			getDetailData(){
+				gatherDetail({id:this.itemId}).then(res=>{
+					if(res.status==200){
+						console.log(res)
+						const a = res.data.gatherRubbishList.find(item=>item.rubbishType=='GATHER_BUILDING')
+						const b = res.data.gatherRubbishList.find(item=>item.rubbishType=='GATHER_OTHER')
+						const c = res.data.gatherRubbishList.find(item=>item.rubbishType=='GATHER_KITCHEN')
+						this.form.gatherNum_jianzhu= a ? a.gatherNum.toString() : '0'
+						this.form.gatherNum_chuyu= c ? c.gatherNum.toString() : '0'
+						this.form.gatherNum_other= b ? b.gatherNum.toString() : '0'
+						this.form.remarks=res.data.remarks
+						this.photograph=res.data.imageUrlList || []
+						console.log(this.form,'this.form')
+					}
+				})
+			},
+			// 拍照或从相册选图片
+			goPhotograph() {
+				uni.chooseImage({
+				    count: 1, //默认9
+				    sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
+				    sourceType: ['album','camera'], //从相册选择
+				    success:  (res) =>{
+						console.log(res,'rrrrrrrrrrr')
+				        console.log(JSON.stringify(res.tempFilePaths));
+						console.log(this.photograph.length,'this.photograph')
+						if(res.tempFiles[0].size>10485760){
+							uni.showToast({
+								title: '图片过大无法上传!',
+								icon: 'none'
+							})
+							return
+						}
+						uni.showLoading({
+							title:'正在保存图片...',
+							mask: true
+						})
+						saveImgToAliOss(res.tempFilePaths[0],(ret)=>{
+							// console.log(ret,'----------ret')
+							this.photograph.push(ret.data)
+							// _this.photograph = ret.data
+							uni.hideLoading()
+						})
+				    }
+				});
+			},
+			//  预览图片
+			previewPictures(item) {
+				const photograph = [item];
+				uni.previewImage({
+					urls: photograph,
+				});
+			},
+			//  删除图片
+			cancelPhotograph(index) {
+				setTimeout(() => {
+					this.photograph.splice(index,1)
+				}, 500);
+			},
+			// 提交
+			submit(){
+				if((!this.form.gatherNum_other ||this.form.gatherNum_other==0)  && (!this.form.gatherNum_chuyu || this.form.gatherNum_chuyu==0) && (!this.form.gatherNum_jianzhu || this.form.gatherNum_jianzhu==0)) {
+					uni.showToast({
+						title: '请至少输入一项垃圾分类的数量!',
+						icon: 'none'
+					})
+					return false
+				} else {
+					this.$refs.openModal.open()
+				}
+			},
+			// 确认提交
+			onOk() {
+				this.$refs.openModal.close()
+				let params = {
+					rubbishEntityList: [],
+					imageUrlList: this.photograph,
+					remarks: this.form.remarks
+				}
+				if(this.itemId){
+					params.id=this.itemId
+				}
+				if(this.form.gatherNum_other) {
+					params.rubbishEntityList.push({
+						"rubbishType":"GATHER_OTHER",
+						"rubbishWeight": this.form.gatherNum_other*240,
+						"gatherNum": this.form.gatherNum_other,
+						"unit":"BUCKET"
+					})
+				}
+				if(this.form.gatherNum_chuyu) {
+					params.rubbishEntityList.push({
+						"rubbishType":"GATHER_KITCHEN",
+						"rubbishWeight": this.form.gatherNum_chuyu*240,
+						"gatherNum": this.form.gatherNum_chuyu,
+						"unit":"BUCKET"
+					})
+				}
+				if(this.form.gatherNum_jianzhu) {
+					params.rubbishEntityList.push({
+						"rubbishType":"GATHER_BUILDING",
+						"rubbishWeight": this.form.gatherNum_jianzhu*1000,
+						"gatherNum": this.form.gatherNum_jianzhu,
+						"unit":"TON"
+					})
+				}
+				this.loading=true
+				gatherSave(params).then(res=>{
+					if(res.status == 200) {
+						uni.showToast({
+							title: res.message,
+							icon: 'none'
+						})
+						setTimeout(()=>{
+							uni.navigateBack()
+						},300)
+					} else {
+						uni.showToast({
+							title: res.message,
+							icon: 'none'
+						})
+					}
+					this.loading= false
+				})
+			},
+			
+		},
+		// 必须要在onReady生命周期,因为onLoad生命周期组件可能尚未创建完毕
+		onReady() {
+			this.$refs.uForm.setRules(this.rules);
+		}
+	}
+</script>
+
+<style lang="scss">
+	.content{
+		width: 100%;
+		height: 100vh;
+		padding: 0;
+		background: #F5F5F5;
+		// .buttons{
+		// 	font-size: 32upx;
+		// 	display: block;
+		// 	height: 80upx;
+		// 	line-height: 80upx;
+		// 	margin: 0 auto;
+		// 	background-color: #0DC55F;
+		// 	color: #fff;
+		// 	border-radius: 50upx;
+		// }
+		.receiveAddress{
+			width: 100%;
+			line-height: 1.5em;
+			box-sizing: border-box;
+			font-style: normal;
+		}
+		.form-data{
+			padding: 0 40upx;
+			margin-bottom: 20upx;
+			background-color: #fff;
+		}
+		.remark{
+			padding: 40upx ;
+		}
+		.qyImg{
+			padding: 40upx ;
+		}
+		.info-main {
+			margin-top: 14upx;
+			border-radius: 12upx;
+			background-color: #fff;
+			display: flex;
+			.location-icon {
+				padding-left: 10upx;
+				vertical-align: sub;
+			}
+			.photograph-camera {
+				border: 2upx dashed #bfbfbf;
+				border-radius: 12upx;
+				width: 140upx;
+				height: 140upx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+			}
+			.camera-btn{
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				color: #999999;
+				font-size: 24upx;
+			}
+			.photograph-con {
+				margin: 0 20upx;
+				width: 140upx;
+				height: 140upx;
+				text-align: center;
+				position: relative;
+				.photograph-icon {
+					display: inline-block;
+					padding-top: 48upx;
+				}
+				.close-circle-icon {
+					background-color: #fff;
+					border-radius: 50%;
+					position: absolute;
+					right: -23upx;
+					top: -23upx;
+					z-index: 9;
+				}
+			}
+		}
+		.submitBtn{
+			font-size: 32upx;
+			display: block;
+			width: 670upx;
+			height: 80upx;
+			line-height: 80upx;
+			margin: 0 auto;
+			color: #fff;
+			border-radius: 50upx;
+		}
+	}
+</style>

+ 398 - 0
pages/index/index.vue

@@ -0,0 +1,398 @@
+<template>
+	<view class="content">
+		<view class="header">
+			<view @click="openSearch">
+				<text v-if="searchForm.beginDate">{{searchForm.beginDate}}至{{searchForm.endDate}}</text>
+				<text v-else>全部</text>
+				<!-- <u-icon name="arrow-right" color="#999999" size="28"></u-icon> -->
+				<u-image v-if="hasLogin" @click="openScreen=true" class="filter-img" width="36rpx" height="36rpx" src="/static/filter.png"></u-image>
+			</view>
+			<!-- 总计 -->
+			<view class="header-cont">
+				<view class="cont-item">
+					<text>其它垃圾</text>
+					<text v-if="!titleStatus" class="other">{{otherTotal}}kg</text>
+					<u-loading :show="titleStatus"></u-loading>
+				</view>
+				<view class="cont-item">
+					<text>厨余垃圾</text>
+					<text v-if="!titleStatus" class="chuyu">{{chuyuTotal}}kg</text>
+					<u-loading :show="titleStatus"></u-loading>
+				</view>
+				<view class="cont-item">
+					<text>建筑垃圾</text>
+					<text v-if="!titleStatus" class="jianzhu">{{jianzhuTotal}}kg</text>
+					<u-loading :show="titleStatus"></u-loading>
+				</view>
+			</view>
+		</view>
+		<scroll-view  class="scroll-con" :scroll-top="scrollTop" scroll-y @scrolltolower="onreachBottom">
+			<!-- 列表数据 -->
+			<view class="cell-item" v-for="item in listdata">
+				<view class="cell-item-title" >
+					<view>
+						<text>{{item.createDate}}</text>
+					</view>
+					<view @click="intoPage(item)">
+						<span class="view">查看详情</span>
+						<u-icon name="arrow-right" color="#999999" size="28"></u-icon>
+					</view>
+				</view>
+				<view class="cell-item-cont">
+					<!-- 其他垃圾 -->
+					<view class="cont-item">
+						<u-image src="/static/index/other.png"></u-image>
+						<text>{{item.gatherOther? (item.gatherOther/1000).toFixed(3)/1 :0}}kg</text>
+					</view>
+					<!-- 厨余垃圾 -->
+					<view class="cont-item">
+						<u-image src="/static/index/chuyu.png"></u-image>
+						<text>{{item.gatherKitchen ? (item.gatherKitchen/1000).toFixed(3)/1 :0}}kg</text>
+					</view>
+					<!-- 建筑垃圾 -->
+					<view class="cont-item">
+						<u-image src="/static/index/jianzhu.png"></u-image>
+						<text>{{item.gatherBuilding ? (item.gatherBuilding/1000).toFixed(3)/1 :0}}kg</text>
+					</view>
+				</view>
+			</view>
+			<view class="nodata" v-if="listdata.length==0 && status!='loading'">
+				<u-empty :text="noDataText" mode="list"></u-empty>
+			</view>
+			<view class="loadmore">
+				<u-loadmore v-if="total>pageSize || status=='loading'" :status="status" />
+			</view>
+		</scroll-view>
+		<view @click="intoPage()" class="footer">
+			<!-- <u-icon name="edit-pen" size="30"></u-icon> -->
+			<text>垃圾数据录入</text>
+		</view>
+		<!-- 筛选弹框 -->
+		<search-modal v-if="openScreen" :visible="openScreen" :defaultParams="searchForm" @refresh="refresh" @close="openScreen=false"></search-modal>
+	</view>
+</template>
+
+<script>
+	import searchModal from './searchModal.vue'
+	import {gatherList,gatherTotal} from '@/api/index.js'
+	import { checkLogin } from '@/api/login.js'
+	import moment from 'moment'
+	import getDate from '@/libs/getDate.js'
+	export default {
+		components: { searchModal },
+		data() {
+			return {
+				hasLogin: false, // 是否登录
+				listdata: [], // 列表数据
+				otherTotal: 0, // 其它垃圾总和
+				jianzhuTotal: 0, // 建筑垃圾总和
+				chuyuTotal: 0, // 厨余垃圾总和
+				pageNo: 1,  //  当前页码
+				pageSize: 10,  //  每页条数
+				total: 0,  //  数据总条数
+				noDataText: '暂无数据',  //  列表请求状态提示语
+				status: 'loadmore',  //  加载中状态
+				titleStatus: false,  //  顶部加载中状态
+				openScreen: false,  //  是否打开筛选
+				searchForm: {  //  查询条件
+					beginDate: getDate.getRecentday().starttime,  //  创建时间默认筛选近7天
+					endDate: getDate.getRecentday().endtime,  //  创建时间默认筛选近7天
+				},
+				scrollTop: 0,  //  滚动条位置
+			}
+		},
+		onLoad() {
+			this.checkUpdate()
+			// 开启分享
+			uni.showShareMenu({
+				withShareTicket: true,
+				menus: ['shareAppMessage', 'shareTimeline']
+			})
+		},
+		onUnload() {
+			
+		},
+		onShareAppMessage(res) {
+		},
+		onShareTimeline(res) {
+		},
+		onShow() {
+			checkLogin().then(res => {
+				this.hasLogin = res.status == 200
+				if(this.hasLogin){
+					this.pageInit()
+				} else {
+					this.noDataText = '您尚未登录或登录已过期,完成登录后可查看录入信息!'
+				}
+			})
+		},
+		methods: {
+			pageInit() {
+				this.pageNo = 1
+				this.pageSize = 10
+				this.total = 0
+				this.scrollTop = 0
+				this.otherTotal= 0 // 其它垃圾总和
+				this.jianzhuTotal= 0 // 建筑垃圾总和
+				this.chuyuTotal= 0 // 厨余垃圾总和
+				this.searchForm.beginDate = getDate.getRecentday().starttime
+				this.searchForm.endDate = getDate.getRecentday().endtime
+				console.log(this.searchForm,'this.searchForm')
+				this.searchHandle()
+				this.getTotal()
+			},
+			// 检查更新
+			checkUpdate(){
+				if (uni.canIUse('getUpdateManager')) {
+				      const updateManager = uni.getUpdateManager()
+				      updateManager.onCheckForUpdate(function (res) {
+				        console.log('onCheckForUpdate====', res)
+				        // 请求完新版本信息的回调
+				        if (res.hasUpdate) {
+				          console.log('res.hasUpdate====')
+				          updateManager.onUpdateReady(function () {
+				            uni.showModal({
+				              title: '更新提示',
+				              content: '新版本已经准备好,是否重启应用?',
+				              success: function (res) {
+				                console.log('success====', res)
+				                // res: {errMsg: "showModal: ok", cancel: false, confirm: true}
+				                if (res.confirm) {
+				                  // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
+				                  updateManager.applyUpdate()
+				                }
+				              }
+				            })
+				          })
+				          updateManager.onUpdateFailed(function () {
+				            // 新的版本下载失败
+				            uni.showModal({
+				              title: '已经有新版本了哟~',
+				              content: '新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~'
+				            })
+				          })
+				        }
+				      })
+				    }
+			},
+			// 打开查询弹窗
+			openSearch(){
+				if(this.hasLogin){
+					this.openScreen = true
+				}
+			},
+			// 获取查询参数 刷新列表
+			refresh(params){
+				this.searchForm = params
+				this.listdata = []
+				this.total = 0
+				this.searchHandle(1)
+				this.getTotal()
+			},
+			// 搜索查询
+			searchHandle (pageNo) {
+				this.status = "loading"
+				pageNo ? this.pageNo = pageNo : null
+				gatherList({
+					pageNo: this.pageNo,
+					pageSize: this.pageSize,
+					beginDate: this.searchForm.beginDate?moment(this.searchForm.beginDate).format('YYYY-MM-DD 00:00:00'):'',
+					endDate: this.searchForm.endDate ? moment(this.searchForm.endDate).format('YYYY-MM-DD 23:59:59'):'',
+				}).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.scrollTop = 0
+						}
+						this.total = res.data.count || 0
+						this.noDataText = '暂无数据'
+					} else {
+						this.noDataText = res.message
+						this.listdata = []
+						this.total = 0
+					}
+					this.status = "loadmore"
+				})
+			},
+			// scroll-view到底部加载更多
+			onreachBottom() {
+				console.log(111111)
+				if(this.listdata.length < this.total){
+					this.pageNo += 1
+					this.searchHandle()
+				}else{
+					uni.showToast({ title: '已经到底了', icon: 'none' })
+					this.status = "nomore"
+				}
+			},
+			// 总计
+			getTotal(){
+				this.titleStatus = true
+				gatherTotal({
+					beginDate: this.searchForm.beginDate?moment(this.searchForm.beginDate).format('YYYY-MM-DD 00:00:00'):'',
+					endDate: this.searchForm.endDate ? moment(this.searchForm.endDate).format('YYYY-MM-DD 23:59:59'):''}).then(res=>{
+						if(res.status==200){
+							const a = res.data.find(item=>item.rubbishType=='GATHER_BUILDING')
+							const b = res.data.find(item=>item.rubbishType=='GATHER_OTHER')
+							const c = res.data.find(item=>item.rubbishType=='GATHER_KITCHEN')
+							this.jianzhuTotal= a ? (a.rubbishWeight/1000).toFixed(3)/1 : '0'
+							this.chuyuTotal= c ? (c.rubbishWeight/1000).toFixed(3)/1 : '0'
+							this.otherTotal= b ? (b.rubbishWeight/1000).toFixed(3)/1 : '0'
+						}
+					console.log(res)
+					this.titleStatus = false
+				})
+			},
+			// 打开垃圾数据录入页面
+			intoPage(item) {
+				const flag=item ? item.gatherId : ''
+				console.log(flag,'---------hangid,;;;;')
+				let url = flag ? `/pages/index/addData?id=${flag}` : `/pages/index/addData`
+				// uni.navigateTo({
+				//     url: url
+				// })
+				if(!this.hasLogin){
+					uni.showModal({
+					  title: '提示',
+					  content: '您尚未登录或登录已过期,请登录后使用',
+					  success: (res)=> {
+					    if (res.confirm) {
+					     uni.navigateTo({
+					         url: '/pages/login/login'
+					     })
+					    } else if (res.cancel) {
+					      console.log('用户点击取消');
+					    }
+					  }
+					});
+				} else {
+					uni.navigateTo({
+					    url: url
+					})
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.content {
+		width: 100%;
+		height: 100vh;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		.header{
+			width: 100%;
+			background-color: #FFFFFF;
+			display: flex;
+			flex-direction: column;
+			border-radius: 15upx;
+			padding: 30upx;
+			box-shadow: 0upx 3upx 6upx #eee;
+			margin-bottom: 20upx;
+			:first-child {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+			}
+			.header-cont{
+				margin-top: 20upx;
+				display: flex;
+				align-items: center;
+				justify-content: space-around;
+				.cont-item{
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+					text{
+						line-height: 50upx;
+					}
+					text:last-child{
+						font-weight: 600;
+						font-size: 28upx;
+					}
+				}
+				.other{
+					color: #FB1E1E;
+				}
+				.chuyu{
+					color: #FCAE53;
+				}
+				.jianzhu{
+					color: #4EACFA;
+				}
+			}
+			.filter-img{
+				padding: 10upx;
+			}
+		}
+		.scroll-con{
+			flex: 1;
+			width: 100%;
+			-webkit-box-flex:1;
+			-webkit-flex:1;
+			-ms-flex:1;
+			overflow: auto;
+			.cell-item{
+				width: 100%;
+				background-color: #fff;
+				border-radius: 15upx;
+				// padding: 20upx;
+				box-shadow: 0upx 3upx 6upx #eee;
+				.cell-item-title{
+					padding: 20upx 20upx 10upx;
+					width: 100%;
+					display: flex;
+					justify-content: space-between;
+					// border-bottom: 1px solid #e6e6e6;
+					.view{
+						color: #999999;
+						margin-right: 12upx;
+					}
+				}
+				.cell-item-cont{
+					width: 100%;
+					display: flex;
+					justify-content: space-between;
+					padding: 20upx;
+					margin-bottom: 20upx;
+					.cont-item{
+						display: flex;
+						flex-direction: column;
+						align-items: center;
+						width: 33%;
+						// border-right: 1px solid #e6e6e6;
+						:first-child {
+							width: 120upx;
+							height: 120upx;
+							margin-bottom: 20upx;
+						}
+					}
+					:last-child {
+						border: none;
+					}
+				}
+			}
+		}
+		.footer{
+			z-index: 9999;
+			// position: absolute;
+			// bottom: 30upx;
+			// margin: 20upx;
+			width: 95%;
+			height: 80upx;
+			background-color: #0DC55F;
+			opacity: 1;
+			border-radius: 50upx;
+			color: #fff;
+			font-size: 30rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+	}
+</style>

+ 196 - 0
pages/index/searchModal.vue

@@ -0,0 +1,196 @@
+<template>
+	<!-- 待办单查询 -->
+	<div class="modal-content">
+		<u-popup v-model="isShow" class="search-popup" mode="right" @close="handleClose" length="85%">
+			<view class="search-title">筛选</view>
+			<u-form class="form-content" :model="form" ref="uForm" label-width="150">
+				<u-form-item label="录入时间" prop="time" class="form-item">
+					<u-input clearable v-model="form.time" disabled placeholder="请选择投递时间区间" class="form-item-inp" @click="openPicker" />
+					<u-icon v-show="form.time.length != 0" name="close-circle-fill" color="#c8c0cc" size="32" class="close-circle" @click="clearTime"></u-icon>
+				</u-form-item>
+			</u-form>
+			<view class="uni-list-btns">
+				<u-button class="handle-btn search-btn" size="medium" hover-class="none" :custom-style="customBtnStyle" @click="handleSearch">查询</u-button>
+				<u-button class="handle-btn" size="medium" hover-class="none" @click="resetForm">重置</u-button>
+			</view>
+		</u-popup>
+		<!-- 时间选择器 -->
+		<w-picker
+			class="date-picker"
+			:visible.sync="showPicker"
+			mode="range"
+			:current="true"
+			fields="day"
+			@confirm="onSelected($event, 'range')"
+			@cancel="showPicker = false"
+			ref="date"
+		></w-picker>
+	</div>
+</template> 
+
+<script>
+import wPicker from '@/components/w-picker/w-picker.vue'
+export default {
+	components: { wPicker },
+	props: {
+		visible: {
+			type: Boolean,
+			default: false
+		},
+		defaultParams: {  //  默认筛选条件
+			type: Object,
+			default: () => {
+				return {};
+			}
+		}
+	},
+	watch: {
+		visible(newValue, oldValue) {
+			if (newValue) {
+				this.isShow = newValue
+			}
+		},
+		isShow(newValue, oldValue) {
+			if (newValue) {
+			} else {
+				this.init()
+				this.$emit('close')
+			}
+		}
+	},
+	data() {
+		return {
+			customBtnStyle: {  //  自定义按钮样式
+				borderColor: '#2ab4e5',
+				backgroundColor: '#2ab4e5',
+				color: '#fff'
+			},
+			isShow: this.visible, // 显示隐藏
+			showPicker: false, // 是否显示时间弹窗
+			form: {
+				time: '', // 发起时间区间
+				customerMobile: '', // 门店名称
+			},
+			beginDate: null, // 开始时间
+			endDate: null // 结束时间
+		};
+	},
+	mounted() {
+		this.init()
+	},
+	methods: {
+		//  初始化数据
+		init(){
+			if(this.defaultParams.beginDate && this.defaultParams.endDate){
+				this.form.time = this.defaultParams.beginDate + ' ~ ' + this.defaultParams.endDate
+			}
+			this.beginDate = this.defaultParams.beginDate ? this.defaultParams.beginDate : ''
+			this.endDate = this.defaultParams.endDate ? this.defaultParams.endDate : ''
+			this.form.customerMobile = this.defaultParams.customerMobile ? this.defaultParams.customerMobile : ''
+		},
+		//  清空创建时间
+		clearTime(){
+			this.form.time = ''
+			this.beginDate = ''
+			this.endDate = ''
+		},
+		// 关闭弹窗
+		handleClose() {
+			this.isShow = false
+		},
+		// 打开时间选择弹框
+		openPicker() {
+			this.showPicker = true
+		},
+		// 确认日期选择
+		onSelected(v) {
+			this.form.time = v.value[0] + ' ~ ' + v.value[1]
+			this.beginDate = v.value[0]
+			this.endDate = v.value[1]
+			this.showPicker = false
+		},
+		// 查询
+		handleSearch() {
+			let params = {
+				beginDate: this.beginDate,
+				endDate: this.endDate,
+				customerMobile: this.form.customerMobile,
+			};
+			this.$emit('refresh', params)
+			this.isShow = false
+		},
+		// 重置
+		resetForm() {
+			this.$refs.uForm.resetFields()
+			this.$emit('refresh', {})
+			this.isShow = false
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.modal-content {
+	position: relative;
+}
+.search-popup {
+	.search-title {
+		text-align: center;
+		padding: 18upx 22upx;
+		color: #333;
+		border-bottom: 1px solid #eee;
+	}
+	.form-content {
+		display: block;
+		padding: 0 20upx;
+		box-sizing: content-box;
+		.status-item {
+			width: 30%;
+			text-align: center;
+			line-height: 60upx;
+			background-color: #F8F8F8;
+			color: #333;
+			border-radius: 6upx;
+			font-size: 24upx;
+			margin: 0 10upx;
+		}
+		.active {
+			background-color: #ffaa00;
+			color: #fff;
+		}
+		.form-item-inp{
+			display: inline-block;
+			width: 88%;
+			vertical-align: top;
+		}
+	}
+	.uni-list {
+		margin: 20upx;
+		.uni-list-cell {
+			display: flex;
+			align-items: center;
+			border-bottom: 1px dashed #eeeeee;
+			.uni-list-cell-db {
+				flex: 1;
+			}
+			.uni-input {
+				height: 2.5em;
+				line-height: 2.5em;
+			}
+		}
+	}
+	.uni-list-btns {
+		display: flex;
+		padding: 20upx;
+		margin-top: 200rpx;
+		.handle-btn {
+			font-size: 28upx;
+			margin: 0 30upx;
+			width: 100%;
+			flex: 1;
+		}
+	}
+}
+.date-picker {
+}
+</style>

+ 85 - 0
pages/login/login.scss

@@ -0,0 +1,85 @@
+.login-wrap, .forget-wrap, .apply-wrap{
+	background-color: #FFFFFF;
+	width: 100%;
+	height: 100vh;
+	box-sizing: border-box;
+	.logo{
+		display: flex;
+		justify-content: center;
+		padding: 100upx 0;
+	}
+	.login-form{
+		display: block;
+		width: 80%;
+		margin: 0 auto;
+		.form-item{
+			margin-bottom: 20upx;
+			position: relative;
+			display: flex;
+			align-items: center;
+			border: 1upx solid #dcdee2;
+			border-radius: 15upx;
+			.require-tip{
+				position: absolute;
+				left: 0;
+				top: 26upx;
+				color: red;
+			}
+			.item-inp{
+				flex-grow: 1;
+			}
+			.item-inp, .item-picker{
+				font-size: 30upx;
+				padding: 14upx 20upx;
+			}
+			.getcode-btn{
+				position: absolute;
+				right: 0upx;
+				top: 0;
+				font-size: 24upx;
+				background: #999;
+				color: #FFFFFF;
+				border: 0;
+				border-top-left-radius: 0;
+				border-bottom-left-radius: 0;
+				line-height: 3;
+				z-index: 10;
+			}
+		}
+		.flex-box{
+			display: flex;
+			justify-content: space-between;
+			font-size: 26upx;
+		}
+		.form-btn-con{
+			margin-top: 60upx;
+			.form-btn{
+				font-size: 32upx;
+				display: block;
+				line-height: 75upx;
+				margin: 0 auto;
+				background-color: #07c160;
+				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;
+		}
+	}
+}

+ 235 - 0
pages/login/login.vue

@@ -0,0 +1,235 @@
+<template>
+	<view class="content">
+		<!-- <view> -->
+			<view>
+				<view class="logo-cont">
+					<u-image src="/static/login_icon.png" width="160" height="160" class="logo"></u-image>
+					<u-image src="/static/login_slogan.png" mode="aspectFit" width="286" height="36" class="logo_text"></u-image>
+				</view>
+				<view class="login-btns">
+					<u-button shape="circle" :custom-style="wxButton" class="login-btn" type="success" :hair-line="false" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
+						微信授权登录
+					</u-button>
+					<u-gap height="30"></u-gap>
+					<u-button shape="circle" :custom-style="phoneButton" class="login-btn phoneBtn" @click="phoneLogin" :hoverClass='none'>
+						账号密码登录
+					</u-button>
+				</view>
+			</view>
+			<view class="nologin">
+				<u-button shape="circle" size="mini" plain lass="login-btn" @click="cansel" type="info">
+					暂不登录
+				</u-button>
+			</view>
+		<!-- </view> -->
+		<view @click="toYs" class="yszc">登录即代表同意<text class="yszc-text">服务协议及隐私保护政策</text></view>
+	</view>
+</template>
+
+<script>
+	import {
+		login
+	} from "@/api/login"
+	export default {
+		components: {},
+		data() {
+			return {
+				path: '/pages/index/index',
+				lanuch: false,
+				code: '',
+				wxButton: {
+					background: '#0DC55F'
+				},
+				phoneButton: {
+					background: '#fff',
+					color:'#000',
+				}
+			}
+		},
+		onLoad(option) {
+			// 开启分享
+			uni.showShareMenu({
+				withShareTicket: true,
+				menus: ['shareAppMessage', 'shareTimeline']
+			})
+			// 为解决首次登录异常问题 进入页面先调微信登录获取code
+			this.wxLogin()
+			const {
+				path,
+				lanuch
+			} = option;
+			this.lanuch = (lanuch + '') === 'true';
+			if (path) {
+				this.path = decodeURIComponent(path);
+			}
+		},
+		methods: {
+			cansel(){
+				this.$store.state.vuex_noLogin = true
+				uni.reLaunch({
+					url: '/pages/index/index'
+				})
+			},
+			toYs(){
+				uni.navigateTo({
+					url: '/pages/agreement/agreement'
+				})
+			},
+			getPhoneNumber(e) {
+				let _this = this
+				uni.showLoading({
+					mask: true,
+					title: '加载中'
+				});
+				if (!this.code) {
+					this.wxLogin()
+				} 
+				console.log(this.code, e)
+				if (e.target.errMsg === 'getPhoneNumber:ok') {
+					setTimeout(()=>{
+						const params={
+							code: _this.code,
+							encryptedData: e.target.encryptedData,
+							iv: e.target.iv,
+						}
+						login(params).then(res => {
+							uni.hideLoading();
+							_this.code = ''
+							console.log(res,_this.path, 'login data')
+							if (res.status == '200') {
+								getApp().globalData.token = res.data
+								_this.$u.vuex('vuex_token',res.data)
+								uni.$emit("getUserInfo")
+								if (_this.path === '/pages/index/index' || _this.lanuch) {
+									uni.reLaunch({
+										url: _this.path
+									});
+								} else {
+									uni.redirectTo({
+										url: _this.path
+									});
+								}
+							} else {
+								uni.showToast({
+									title: res.message,
+									icon: 'none',
+									duration: 2500
+								});
+							}
+						});
+					},300)
+				} else {
+					uni.showToast({
+						title: '授权失败',
+						icon: 'none',
+						duration: 2500
+					});
+				}
+			},
+			// 调微信登录 获取code
+			wxLogin () {
+				let _this = this
+				uni.login({
+					provider: 'weixin',
+					success(res) {
+						console.log(res.code)
+						_this.code = res.code
+					}
+				})
+			},
+			// 手机号登录
+			phoneLogin () {
+				if(this.url){
+					wx.navigateTo({
+						url: '/pages/login/phoneLogin?path='+this.path+'&lanuch='+this.lanuch+'&encode='+this.url 
+					})
+				}else{
+					wx.navigateTo({
+						url: '/pages/login/phoneLogin?path='+this.path+'&lanuch='+this.lanuch
+					})
+				}
+			  
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.content{
+		width: 100%;
+		height: 100vh;
+		padding:0 40upx;
+		margin: 0 auto;
+		.logo-cont{
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+		}
+		.logo{
+			display: inline-block;
+			margin:250upx 256upx 24upx;
+		}
+		.logo_text{
+			display: inline-block;
+			margin-bottom: 200upx;
+		}
+		.login-btn{
+			display: block;
+			width: 670upx;
+			height: 80upx;
+		}
+		.phoneBtn:active{
+			background: #fff ;
+			outline: none;
+		}
+		.nologin{
+			text-align: center;
+			margin: 100upx 0 50upx 0;
+		}
+		.yszc{
+			font-size: 24upx;
+			text-align: center;
+			.yszc-text{
+				color:#07c160;
+			}
+			
+		}
+	}
+	// .content {
+	// 	width: 100%;
+	// 	height: 100%;
+	// 	padding: 40upx;
+	// 	display: flex;
+	// 	flex-direction: column;
+	// 	> view{
+	// 		text-align: center;
+	// 		&:first-child{
+	// 			flex-grow: 1;
+	// 			height: 80vh;
+	// 			padding: 20% 10%;
+	// 			display: flex;
+	// 			flex-direction: column;
+	// 			justify-content: space-between;
+	// 		}
+	// 		&:last-child{
+	// 			font-size: 24upx;
+	// 			text{
+	// 				color: #07c160;
+	// 			}
+	// 		}
+	// 		.logo{
+	// 			text-align: center;
+	// 			display: flex;
+	// 			justify-content: center;
+	// 		}
+	// 		.login-btns{
+	// 			// padding: 60upx 0;
+	// 			.login-btn{
+	// 				width: 670upx;
+	// 				height: 50upx;
+	// 				border-radius: 25upx;
+	// 			}
+	// 		}
+	// 	}
+	// }
+</style>

+ 79 - 0
pages/login/loginPw.scss

@@ -0,0 +1,79 @@
+.login-wrap, .forget-wrap, .apply-wrap{
+	background-color: #FFFFFF;
+	width: 100%;
+	height: 100vh;
+	box-sizing: border-box;
+	padding: 0 40upx;
+	.logo{
+		display: block;
+		margin: 0 auto;
+		padding: 200upx 256upx 60upx;
+	}
+	.login-form{
+		display: block;
+		.form-item{
+			margin-bottom: 20upx;
+			position: relative;
+			display: flex;
+			align-items: center;
+			border-bottom: 1upx solid #e5e5e5;
+			.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;
+				height: 80upx;
+				line-height: 80upx;
+				margin: 0 auto;
+				background-color: #0DC55F;
+				color: #fff;
+				border-radius: 50upx;
+			}
+			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;
+		}
+	}
+}

+ 166 - 0
pages/login/phoneLogin.vue

@@ -0,0 +1,166 @@
+<template>
+	<view class="login-wrap">
+		<!-- <view v-if="!buildType" style="text-align: center;color: #999999;padding-top: 40px;">{{ envTips }}</view> -->
+		<u-image src="/static/login_icon.png" width="160" height="160" class="logo"></u-image>
+		<form class="login-form" @submit="formSubmit">
+			<view class="form-item">
+				<u-image src="/static/user_icon_number.png" width="44" height="44" class="logo_user_icon"></u-image>
+				<input v-model="form.loginName" class="item-inp" name="loginName" type="text" maxlength="30" placeholder="请输入用户名" />
+			</view>
+			<view class="form-item">
+				<u-image src="/static/user_icon_password.png" width="44" height="44" class="logo_pwd_icon"></u-image>
+				<input v-model="form.password" class="item-inp" name="password" password type="text" maxlength="12" placeholder="请输入密码" />
+			</view>
+			<view class="flex-box" style="margin-top: 40upx;">
+				<view class="remember-pass">
+					<u-checkbox shape="circle" v-model="isRemember" value="true" :size="30" :label-size="26" active-color="#0DC55F" class="login-form-checkbox" @change="rememberChange">记住密码</u-checkbox>
+				</view>
+			</view>
+			<view class="form-btn-con">
+				<button class="form-btn" form-type="submit">登录</button>
+			</view>
+		</form>
+		<!-- <view class="authUserYs">
+			<u-checkbox v-model="isAuthuserYs" value="true" :size="30" :label-size="26" active-color="#2ab4e5" class="login-form-checkbox" @change="authuserChange">登录代表同意</u-checkbox>
+			<text @click="toYsPage()" style="vertical-align: text-top;font-size: 26rpx;text-decoration: underline;">乐色助手用户协议及隐私保护政策</text>
+		</view> -->
+	</view>
+</template>
+
+<script>
+import { checkVersionToLoadUpdate, setStorageForAppVersion,clzConfirm } from '@/libs/tools'
+import { isvalidPhone } from '@/libs/validate.js';
+import { loginPhone } from '@/api/login.js';
+import { getSysVersion } from '@/api/data.js'
+export default {
+	data() {
+		return {
+			form: {
+				loginName: this.$store.state.vuex_user.account,
+				password: this.$store.state.vuex_user.password
+			},
+			isRemember: true, //  是否记住密码
+			isAuthuserYs: true, // 是否同意隐私政策
+			envTips: '',
+			buildType: '',
+		};
+	},
+	mounted() {
+		this.isRemember = this.$store.state.vuex_isRemember || true;
+		this.isAuthuserYs = this.$store.state.vuex_isUsrAuthYs || true;
+		this.envTips = getApp().globalData.envTips;
+		this.buildType = getApp().globalData.buildType;
+	},
+	onShow() {
+	},
+	methods: {
+		//  获取微信login  code
+		getWechatCode(){
+			const _this = this
+			wx.login({
+			  success (res) {
+			    if (res.code) {
+					//发起网络请求
+					let formData = JSON.parse(JSON.stringify(_this.form))
+					formData.code = res.code
+					_this.submitForm(formData)
+			    } else {
+					console.log('登录失败,点击“登录”重试!' + res.errMsg)
+			    }
+			  }
+			})
+		},
+		//  表单提交
+		formSubmit() {
+			const _this = this;
+			//  表单校验
+			if (_this.form.loginName == '') {
+				uni.showToast({ icon: 'none', title: '请输入用户名' });
+				return;
+			}
+			if (!isvalidPhone(_this.form.loginName)) {
+				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: '正在登录...'
+			});
+			// #ifdef MP-WEIXIN
+				//  获取微信login   code
+				this.getWechatCode()
+			// #endif
+			// #ifndef MP-WEIXIN
+				this.submitForm(_this.form)
+			// #endif
+		},
+		submitForm(formData){
+			const _this = this;
+			//  登录
+			loginPhone(formData).then(res => {
+				uni.hideLoading();
+				if (res.status == 200) {
+					_this.$u.vuex('vuex_token', res.data);
+					getApp().globalData.token = res.data;
+					//登录成功将用户名密码存储到用户本地
+					if (_this.isRemember) {
+						//用户勾选“记住密码”
+						_this.$u.vuex('vuex_user.account', _this.form.loginName);
+						_this.$u.vuex('vuex_user.password', _this.form.password);
+					} else {
+						//用户没有勾选“记住密码”
+						_this.$u.vuex('vuex_user.account', '');
+						_this.$u.vuex('vuex_user.password', '');
+						_this.form.loginName = '';
+						_this.form.password = '';
+					}
+					uni.setStorageSync('loginTimeOut', 'NO');
+					_this.toMain();
+				} else {
+					uni.showToast({ icon: 'none', title: res.message });
+				}
+			});
+		},
+		toMain() {
+			// 跳转到首页
+			uni.switchTab({
+				url: '/pages/index/index'
+			});
+		},
+		//  忘记密码
+		forgetPass() {
+			uni.navigateTo({
+				url: '/pages/login/forget-pass?username=' + this.form.loginName
+			});
+		},
+		//  判断是否记住密码
+		rememberChange(e) {
+			this.isRemember = e.value;
+		},
+		// 隐私
+		authuserChange(e) {
+			this.isAuthuserYs = e.value;
+			this.$u.vuex('vuex_isUsrAuthYs', this.isAuthuserYs);
+		},
+		toYsPage() {
+			uni.navigateTo({
+				url: '/pages/agreement/agreement'
+			});
+		}
+	}
+};
+</script>
+<style scoped lang="scss">
+@import './loginPw.scss';
+</style>

+ 62 - 0
pages/userCenter/aboutUs.vue

@@ -0,0 +1,62 @@
+<template>
+	<view class="container">
+		<view class="logoContent">
+			<view class="logoBorder">
+				<image class="logoImg" src="/static/logo.jpg"></image>
+			</view>
+		</view>
+		<view class="textContent">
+			<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:180rpx;
+					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>

+ 214 - 0
pages/userCenter/changePwd.vue

@@ -0,0 +1,214 @@
+<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" :maxlength="12" @input="getOldPwd" 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" @input="getPasswd" 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" @input="getPasswdCheck" type="password" displayable v-model="formCustom.passwdCheck" placeholder="请输入6-12位新密码"></m-input>
+				</view>
+			</view>
+		</view>
+		<!-- 按钮 -->
+		<view class="form-footer-btn">
+			<u-button size="medium" hover-class="none" shape="circle" @click="handleReset">重置</u-button>
+			<u-button size="medium" hover-class="none" shape="circle" :loading="loading" :custom-style="customBtnStyle" @click="handleSubmit">保存</u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import mInput from '../../components/m-input.vue'
+	import { changePwd } from '@/api/user.js'
+	import { logout } from '@/api/login.js'
+	export default{
+		name:'changePwd',
+		components: {
+			mInput
+		},
+		data(){
+			return{
+				formCustom: {
+				  passwd: '',
+				  passwdCheck: '',
+				  oldPwd: ''
+				},
+				loading: false,
+				customBtnStyle: {  //  自定义按钮样式
+					borderColor: '#0DC55F',
+					backgroundColor: '#0DC55F',
+					color: '#fff'
+				},
+			}
+		},
+		methods:{
+			messageInfo(info){
+				uni.showToast({
+					icon:'none',
+					title: info
+				})
+			},
+			// 不能输入汉字
+			getOldPwd(val) {
+				console.log(val,'vvvvvvv')
+				 if (/[\u4E00-\u9FA5]/g.test(val)) {
+					this.$nextTick(() => {
+						this.formCustom.oldPwd = ''
+					})
+				}
+			},
+			getPasswd(val) {
+				console.log(val,'vvvvvvv')
+				 if (/[\u4E00-\u9FA5]/g.test(val)) {
+					this.$nextTick(() => {
+						this.formCustom.passwd = ''
+					})
+				}
+			},
+			getPasswdCheck(val) {
+				console.log(val,'vvvvvvv')
+				 if (/[\u4E00-\u9FA5]/g.test(val)) {
+					this.$nextTick(() => {
+						this.formCustom.passwdCheck = ''
+					})
+				}
+			},
+			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;
+				align-items: center;
+				justify-content: space-between;
+				.title{
+					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: 140upx 50upx 0;
+			display: flex;
+			justify-content: space-between;
+		}
+		.form-footer-btn uni-button{
+			width: 50%;
+			margin: 0 50upx;
+			font-size: 30upx;
+		}
+	}
+</style>

+ 231 - 0
pages/userCenter/index.vue

@@ -0,0 +1,231 @@
+<template>
+	<view class="user-content">
+		<!-- 头部 -->
+		<view class="user-head" >
+			<view>
+				<open-data type="userAvatarUrl" class="user-photo"></open-data>
+				<!-- <u-image v-show="!hasLogin" src="/static/logo.png" width="100" height="100"></u-image> -->
+			</view>
+			<view class="user-info">
+				<view v-if="hasLogin" class="user-info-item">{{userData.stationName || ''}}</view>
+				<view v-if="hasLogin" class="user-info-item">{{userData.gatherName||''}} {{userData.gatherPhone || ''}}</view>
+				<u-button v-else="!hasLogin" plain size="mini" shape="circle" @click="toLoginPage">立即登录</u-button>
+			</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  v-if="hasLogin" icon="lock" icon-size="38" :icon-style="{color:'#00aaff'}" index="1" @click="toPage" title="修改密码"></u-cell-item>
+				<u-cell-item  icon="error-circle" icon-size="38" :icon-style="{color:'#aaaaff'}" :value="version" @click="checkUpdate" title="版本检查"></u-cell-item>
+			</u-cell-group>
+		</view>
+		<view class="user-list quit">
+			<u-cell-group>
+				<u-cell-item  v-if="hasLogin" icon="minus-circle" icon-size="40" index="3" @click="quitSys" title="退出登录"></u-cell-item>
+			</u-cell-group>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { checkLogin, getUserInfo, logout } from '@/api/login.js'
+	export default {
+		data() {
+			return {
+				hasLogin: false,
+				userData: {},
+				version: '1.0.0' // 小程序版本
+			};
+		},
+		onShow() {
+			checkLogin().then(res => {
+				this.hasLogin = res.status == 200
+				console.log(this.hasLogin,res,'this.hasLogin')
+				if(this.hasLogin){
+					this.getUserInfo()
+				}else{
+					// uni.navigateTo({
+					// 	url:"/pages/login/login"
+					// })
+				}
+			})
+		},
+		computed: {
+			user() {
+				return this.userData 
+			}
+		},
+		onLoad() {
+			// this.getVersion()
+			// 开启分享
+			uni.showShareMenu({
+				withShareTicket: true,
+				menus: ['shareAppMessage', 'shareTimeline']
+			})
+		},
+		methods:{
+			// 获取当前小程序版本
+			getVersion(){
+				const accountInfo = uni.getAccountInfoSync();
+				this.version = accountInfo.miniProgram.version
+				console.log(accountInfo,accountInfo.miniProgram.appId,'ppppppppp'); // 小程序 appId
+			},
+			// 检查更新
+			checkUpdate(){
+				if (uni.canIUse('getUpdateManager')) {
+				      const updateManager = uni.getUpdateManager()
+				      updateManager.onCheckForUpdate(function (res) {
+				        console.log('onCheckForUpdate====', res)
+				        // 请求完新版本信息的回调
+				        if (res.hasUpdate) {
+				          console.log('res.hasUpdate====')
+				          updateManager.onUpdateReady(function () {
+				            uni.showModal({
+				              title: '更新提示',
+				              content: '新版本已经准备好,是否重启应用?',
+				              success: function (res) {
+				                console.log('success====', res)
+				                // res: {errMsg: "showModal: ok", cancel: false, confirm: true}
+				                if (res.confirm) {
+				                  // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
+				                  updateManager.applyUpdate()
+				                }
+				              }
+				            })
+				          })
+				          updateManager.onUpdateFailed(function () {
+				            // 新的版本下载失败
+				            uni.showModal({
+				              title: '已经有新版本了哟~',
+				              content: '新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~'
+				            })
+				          })
+				        } else {
+							uni.showToast({
+								title: '已是最新版本',
+								icon: 'none'
+							})
+						}
+				      })
+				    }
+			},
+			// 获取用户信息
+			getUserInfo(){
+				getUserInfo().then(res => {
+					console.log(res,'getUserInfo')
+					if(res.status == 200){
+						this.$u.vuex("vuex_userData",res.data)
+						this.userData = res.data
+						console.log(this.userData,'-----用户信息')
+					}
+				})
+			},
+			// 打开对应的页面
+			toPage(index){
+				console.log(index)
+				let pageUrl=[
+					'/pages/agreement/agreement?fromPage=usercenter',
+					'/pages/userCenter/changePwd',
+					]
+				uni.navigateTo({
+					url: pageUrl[index]
+				})
+			},
+			// 退出登录
+			quitSys(){
+				let _this = this
+				uni.showModal({
+				  title: '提示',
+				  content: '您确定要退出登录吗?',
+				  success(res) {
+				    if (res.confirm) {
+				      logout().then(res => {
+					   console.log(res,'logout')
+				       getApp().globalData.token = ''
+				       _this.$u.vuex('vuex_token','')
+					   // 关闭socket
+				        uni.reLaunch({
+				        	url: '/pages/login/login'
+				        })
+				      })
+				    }
+				  }
+				})
+			},
+			
+			toLoginPage(){
+				uni.navigateTo({
+					url: '/pages/login/login'
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.user-content{
+		width: 100%;
+		.user-head{
+			background: #0DC55F;
+			display: flex;
+			align-items: center;
+			padding: 35rpx 55rpx;
+			.user-info{
+				flex-grow: 1;
+				padding: 0 20rpx;
+				color: #FFFFFF;
+				.user-info-item{
+					margin: 10rpx 0;
+					:first-child{
+						font-size: 34upx;
+					}
+				}
+			}
+			.user-photo{
+				display: block;
+				width: 100rpx;
+				height: 100rpx;
+				border-radius: 50%;
+				overflow: hidden;
+			}
+		}
+		.my-ledou{
+			background: #FFFFFF;
+			border-radius: 15upx;
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 20upx;
+			margin: 20upx;
+			box-shadow: 0upx 3upx 6upx #eee;
+			.user-jifen{
+				display: flex;
+				align-items: center;
+				text{
+					font-size: 40upx;
+					font-weight: bold;
+					color: red;
+				}
+				>view{
+					&:first-child{
+						display: flex;
+						align-items: center;
+					}
+				}
+			}
+		}
+		.user-list{
+			border-radius: 15upx;
+			margin: 20upx;
+			overflow: hidden;
+			box-shadow: 0upx 3upx 6upx #eee;
+			.quit{
+				margin-top: 20upx;
+			}
+		}
+		.user-btn{
+			padding: 85rpx 200rpx;
+		}
+	}
+</style>

BIN
static/CLOTHES.png


BIN
static/camera.png


BIN
static/edit.png


BIN
static/filter.png


BIN
static/imgError.png


BIN
static/index/chuyu.png


BIN
static/index/jianzhu.png


BIN
static/index/logo-active.png


BIN
static/index/logo.png


BIN
static/index/other.png


BIN
static/loading.gif


BIN
static/login_icon.png


BIN
static/login_slogan.png


BIN
static/logo.jpg


BIN
static/logo.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


BIN
static/user_icon_number.png


BIN
static/user_icon_password.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)
+	}
+}

+ 178 - 0
store/index.js

@@ -0,0 +1,178 @@
+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_storeId',
+	'vuex_bizId',
+	'vuex_orderNo',
+	'vuex_orderStatus'
+];
+
+// 保存变量到本地存储中
+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_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_cartNums: 0,
+		vuex_OrderAddress: {}, // 地址
+		vuex_toOrderList: [],
+		// 如果无需保存到本地永久存储,无需lifeData.xx方式
+		vuex_socket: null,
+		vuex_wsMessageData: '',
+		vuex_heartInterId: null,
+		// 当前网络连接状态
+		vuex_netStatus: '',
+		// 客服电话
+		vuex_kfMobile: '4008826012',
+		vuex_noLogin: true,
+		vuex_isClick: false,
+		// 大转盘
+		vuex_LuckDraw: null,
+		vuex_LuckDrawTimes: 0
+	},
+	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

+ 108 - 0
uni.scss

@@ -0,0 +1,108 @@
+@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;
+}
+// 必填*
+.u-required{
+	position: relative;
+}
+.u-required:before{
+	content: '*';
+	position: absolute;
+	left: -8px;
+	top: 10upx;
+	font-size: 14px;
+	color: #fa3534;
+	height: 9px;
+	line-height: 1;
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است