zhaogongxue 1 year ago
parent
commit
0149e0072e
100 changed files with 29409 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 16 0
      .hbuilderx/launch.json
  3. 51 0
      App.vue
  4. 0 0
      common/Easemob-chat-4.0.7.js
  5. 31 0
      common/amap-wx.js
  6. 97 0
      common/echartDate.js
  7. 76 0
      common/http.interceptor.js
  8. 2 0
      common/url.js
  9. 348 0
      common/utils.js
  10. 23 0
      common/wss.js
  11. 516 0
      components/biaofun-datetime-picker/biaofun-datetime-picker.vue
  12. 30 0
      components/biaofun-datetime-picker/使用说明.md
  13. 211 0
      components/gf-goods/gf-goods.vue
  14. 144 0
      components/gf-scroll/gf-scroll.vue
  15. 350 0
      components/gf-search/gf-search.vue
  16. 31 0
      index.html
  17. 63 0
      main.js
  18. 84 0
      manifest.json
  19. 16 0
      package.json
  20. 323 0
      pages.json
  21. 435 0
      pages/baobei/baobei.vue
  22. 875 0
      pages/h5/rizhao.vue
  23. 811 0
      pages/h5/yifang.vue
  24. 196 0
      pages/index/broker.vue
  25. 251 0
      pages/index/chat.vue
  26. 36 0
      pages/index/fuli-info.vue
  27. 78 0
      pages/index/fuli.vue
  28. 602 0
      pages/index/houses-info.vue
  29. 454 0
      pages/index/index.vue
  30. 338 0
      pages/index/info.vue
  31. 75 0
      pages/index/news-info.vue
  32. 166 0
      pages/index/news.vue
  33. 392 0
      pages/index/pk-info.vue
  34. 282 0
      pages/index/pk-list.vue
  35. 183 0
      pages/index/pk-search.vue
  36. 214 0
      pages/index/search.vue
  37. 113 0
      pages/index/video.vue
  38. 298 0
      pages/mine/baobei-info.vue
  39. 85 0
      pages/mine/baobei-list.vue
  40. 323 0
      pages/mine/mine.vue
  41. 124 0
      pages/mine/setting.vue
  42. 141 0
      pages/mine/userinfo.vue
  43. 63 0
      pages/mine/xieyi.vue
  44. 109 0
      pages/mine/yongjin.vue
  45. 25 0
      pages/video/info.vue
  46. 140 0
      pages/video/video.vue
  47. 242 0
      pagesA/components/echarts-uniapp/echarts-uniapp.vue
  48. 0 0
      pagesA/components/echarts-uniapp/echarts.min.js
  49. 105 0
      pagesA/components/echarts-uniapp/wx-canvas.js
  50. 167 0
      pagesA/index/allHouses.vue
  51. 221 0
      pagesA/index/counter-info.vue
  52. 695 0
      pagesA/index/counter.vue
  53. 167 0
      pagesA/index/dataCenter.vue
  54. 167 0
      pagesA/index/newHouses.vue
  55. 1295 0
      platforms/h5/common/OrbitControls.js
  56. 568 0
      platforms/h5/common/obj/MTLLoader.js
  57. 905 0
      platforms/h5/common/obj/OBJLoader.js
  58. 13013 0
      platforms/h5/common/three.js
  59. BIN
      static/images/baobei-img.png
  60. BIN
      static/images/baobei.png
  61. BIN
      static/images/baobei1.png
  62. BIN
      static/images/code.png
  63. BIN
      static/images/dataCenter1-1.png
  64. BIN
      static/images/dijia-bg.png
  65. BIN
      static/images/head.png
  66. BIN
      static/images/hot.png
  67. BIN
      static/images/index.png
  68. BIN
      static/images/index1.png
  69. BIN
      static/images/jindu.png
  70. BIN
      static/images/jindu1.png
  71. BIN
      static/images/mine-tabs1.png
  72. BIN
      static/images/mine-tabs2.png
  73. BIN
      static/images/mine-tabs3.png
  74. BIN
      static/images/mine.png
  75. BIN
      static/images/mine1.png
  76. BIN
      static/images/pk.png
  77. BIN
      static/images/popup1-1.png
  78. BIN
      static/images/qianbao.png
  79. BIN
      static/images/shaixuan.png
  80. BIN
      static/images/tabs1.png
  81. BIN
      static/images/tabs2.png
  82. BIN
      static/images/tabs3.png
  83. BIN
      static/images/tabs4.png
  84. BIN
      static/images/video-play.png
  85. BIN
      static/images/video.png
  86. BIN
      static/images/video1.png
  87. BIN
      static/images/xiangji.png
  88. BIN
      static/images/xiegang.png
  89. 34 0
      store/index.js
  90. 10 0
      uni.promisify.adaptor.js
  91. 76 0
      uni.scss
  92. 21 0
      uview-ui/LICENSE
  93. 106 0
      uview-ui/README.md
  94. 190 0
      uview-ui/components/u-action-sheet/u-action-sheet.vue
  95. 256 0
      uview-ui/components/u-alert-tips/u-alert-tips.vue
  96. 290 0
      uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
  97. 1265 0
      uview-ui/components/u-avatar-cropper/weCropper.js
  98. 24 0
      uview-ui/components/u-avatar/u-avatar.vue
  99. 153 0
      uview-ui/components/u-back-top/u-back-top.vue
  100. 216 0
      uview-ui/components/u-badge/u-badge.vue

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+/unpackage/
+.DS_Store

+ 16 - 0
.hbuilderx/launch.json

@@ -0,0 +1,16 @@
+{ // 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": [{
+     	"default" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"mp-weixin" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"type" : "uniCloud"
+     }
+    ]
+}

+ 51 - 0
App.vue

@@ -0,0 +1,51 @@
+<script>
+	export default {
+		onLaunch: function() {
+			console.log('App Launch')
+			// #ifdef MP-WEIXIN
+			if (uni.getStorageSync("token") && uni.getStorageSync("hx_username")) {
+				this.$WebIM.conn.open({
+					user: uni.getStorageSync("hx_username"),
+					pwd: "999999",
+				}).then(() => {
+					console.log("login success");
+				}).catch((reason) => {
+					console.log("login fail", reason);
+				});
+			}
+			this.$u.post('/api/Index/platform_config').then(res => {
+				if (res.code == 1) {
+					this.$store.commit('setconfig', res.data)
+				}
+			})
+			this.$u.post('/api/Index/popularity').then(res => {
+				this.$store.commit('setpopularity', res.data)
+			})
+			this.$amapPlugin.getRegeo({
+				success: (res) => {
+					//成功回调
+					// console.log(res[0].regeocodeData.addressComponent);
+					this.$store.commit('setcity', res[0].regeocodeData.addressComponent)
+				},
+				fail: function(info) {
+					//失败回调
+					// console.log(info)
+					this.$store.commit('setcity', '定位失败')
+				}
+			})
+			// #endif
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
+	@import "uview-ui/index.scss";
+</style>

File diff suppressed because it is too large
+ 0 - 0
common/Easemob-chat-4.0.7.js


+ 31 - 0
common/amap-wx.js

@@ -0,0 +1,31 @@
+function AMapWX(a){this.key=a.key;this.requestConfig={key:a.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};this.MeRequestConfig={key:a.key,serviceName:"https://restapi.amap.com/rest/me"}}
+AMapWX.prototype.getWxLocation=function(a,b){wx.getLocation({type:"gcj02",success:function(c){c=c.longitude+","+c.latitude;wx.setStorage({key:"userLocation",data:c});b(c)},fail:function(c){wx.getStorage({key:"userLocation",success:function(d){d.data&&b(d.data)}});a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getMEKeywordsSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.city&&(d.city=b.city);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);b.sig&&(d.sig=
+b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/local",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEIdSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.id&&(d.id=b.id);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/id",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&
+0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEPolygonSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.polygon&&(d.polygon=b.polygon);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);
+b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/polygon",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEaroundSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.center&&(d.center=b.center);b.radius&&(d.radius=b.radius);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&
+(d.pageSize=b.pageSize);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/around",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getGeo=function(a){var b=this.requestConfig,c=a.options;b={key:this.key,extensions:"all",s:b.s,platform:b.platform,appname:this.key,sdkversion:b.sdkversion,logversion:b.logversion};c.address&&(b.address=c.address);c.city&&(b.city=c.city);c.batch&&(b.batch=c.batch);c.sig&&(b.sig=c.sig);wx.request({url:"https://restapi.amap.com/v3/geocode/geo",data:b,method:"GET",header:{"content-type":"application/json"},success:function(d){(d=d.data)&&d.status&&"1"===d.status?a.success(d):a.fail({errCode:"0",
+errMsg:d})},fail:function(d){a.fail({errCode:"0",errMsg:d.errMsg||""})}})};
+AMapWX.prototype.getRegeo=function(a){function b(d){var e=c.requestConfig;wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:c.key,location:d,extensions:"all",s:e.s,platform:e.platform,appname:c.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){g=g.data.regeocode;var h=g.addressComponent,f=[],k=g.roads[0].name+"\u9644\u8fd1",m=d.split(",")[0],n=d.split(",")[1];if(g.pois&&
+g.pois[0]){k=g.pois[0].name+"\u9644\u8fd1";var l=g.pois[0].location;l&&(m=parseFloat(l.split(",")[0]),n=parseFloat(l.split(",")[1]))}h.provice&&f.push(h.provice);h.city&&f.push(h.city);h.district&&f.push(h.district);h.streetNumber&&h.streetNumber.street&&h.streetNumber.number?(f.push(h.streetNumber.street),f.push(h.streetNumber.number)):f.push(g.roads[0].name);f=f.join("");a.success([{iconPath:a.iconPath,width:a.iconWidth,height:a.iconHeight,name:f,desc:k,longitude:m,latitude:n,id:0,regeocodeData:g}])}else a.fail({errCode:g.data.infocode,
+errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this;a.location?b(a.location):c.getWxLocation(a,function(d){b(d)})};
+AMapWX.prototype.getWeather=function(a){function b(g){var h="base";a.type&&"forecast"==a.type&&(h="all");wx.request({url:"https://restapi.amap.com/v3/weather/weatherInfo",data:{key:d.key,city:g,extensions:h,s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(f){if(f.data.status&&"1"==f.data.status)if(f.data.lives){if((f=f.data.lives)&&0<f.length){f=f[0];var k={city:{text:"\u57ce\u5e02",data:f.city},
+weather:{text:"\u5929\u6c14",data:f.weather},temperature:{text:"\u6e29\u5ea6",data:f.temperature},winddirection:{text:"\u98ce\u5411",data:f.winddirection+"\u98ce"},windpower:{text:"\u98ce\u529b",data:f.windpower+"\u7ea7"},humidity:{text:"\u6e7f\u5ea6",data:f.humidity+"%"}};k.liveData=f;a.success(k)}}else f.data.forecasts&&f.data.forecasts[0]&&a.success({forecast:f.data.forecasts[0]});else a.fail({errCode:f.data.infocode,errMsg:f.data.info})},fail:function(f){a.fail({errCode:"0",errMsg:f.errMsg||""})}})}
+function c(g){wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:d.key,location:g,extensions:"all",s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(h){if(h.data.status&&"1"==h.data.status){h=h.data.regeocode;if(h.addressComponent)var f=h.addressComponent.adcode;else h.aois&&0<h.aois.length&&(f=h.aois[0].adcode);b(f)}else a.fail({errCode:h.data.infocode,errMsg:h.data.info})},
+fail:function(h){a.fail({errCode:"0",errMsg:h.errMsg||""})}})}var d=this,e=d.requestConfig;a.city?b(a.city):d.getWxLocation(a,function(g){c(g)})};
+AMapWX.prototype.getPoiAround=function(a){function b(e){e={key:c.key,location:e,s:d.s,platform:d.platform,appname:c.key,sdkversion:d.sdkversion,logversion:d.logversion};a.querytypes&&(e.types=a.querytypes);a.querykeywords&&(e.keywords=a.querykeywords);wx.request({url:"https://restapi.amap.com/v3/place/around",data:e,method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){if((g=g.data)&&g.pois){for(var h=[],f=0;f<g.pois.length;f++){var k=0==
+f?a.iconPathSelected:a.iconPath;h.push({latitude:parseFloat(g.pois[f].location.split(",")[1]),longitude:parseFloat(g.pois[f].location.split(",")[0]),iconPath:k,width:22,height:32,id:f,name:g.pois[f].name,address:g.pois[f].address})}a.success({markers:h,poisData:g.pois})}}else a.fail({errCode:g.data.infocode,errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this,d=c.requestConfig;a.location?b(a.location):c.getWxLocation(a,function(e){b(e)})};
+AMapWX.prototype.getStaticmap=function(a){function b(e){c.push("location="+e);a.zoom&&c.push("zoom="+a.zoom);a.size&&c.push("size="+a.size);a.scale&&c.push("scale="+a.scale);a.markers&&c.push("markers="+a.markers);a.labels&&c.push("labels="+a.labels);a.paths&&c.push("paths="+a.paths);a.traffic&&c.push("traffic="+a.traffic);e="https://restapi.amap.com/v3/staticmap?"+c.join("&");a.success({url:e})}var c=[];c.push("key="+this.key);var d=this.requestConfig;c.push("s="+d.s);c.push("platform="+d.platform);
+c.push("appname="+d.appname);c.push("sdkversion="+d.sdkversion);c.push("logversion="+d.logversion);a.location?b(a.location):this.getWxLocation(a,function(e){b(e)})};
+AMapWX.prototype.getInputtips=function(a){var b=Object.assign({},this.requestConfig);a.location&&(b.location=a.location);a.keywords&&(b.keywords=a.keywords);a.type&&(b.type=a.type);a.city&&(b.city=a.city);a.citylimit&&(b.citylimit=a.citylimit);wx.request({url:"https://restapi.amap.com/v3/assistant/inputtips",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.tips&&a.success({tips:c.data.tips})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||
+""})}})};
+AMapWX.prototype.getDrivingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.waypoints&&(b.waypoints=a.waypoints);a.avoidpolygons&&(b.avoidpolygons=a.avoidpolygons);a.avoidroad&&(b.avoidroad=a.avoidroad);wx.request({url:"https://restapi.amap.com/v3/direction/driving",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths,
+taxi_cost:c.data.route.taxi_cost||""})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getWalkingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/walking",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getTransitRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.city&&(b.city=a.city);a.cityd&&(b.cityd=a.cityd);wx.request({url:"https://restapi.amap.com/v3/direction/transit/integrated",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&(c=c.data.route,a.success({distance:c.distance||"",taxi_cost:c.taxi_cost||
+"",transits:c.transits}))},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getRidingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/riding",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};module.exports.AMapWX=AMapWX;

+ 97 - 0
common/echartDate.js

@@ -0,0 +1,97 @@
+var getDataCenter = function(xAxis = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], yAxis = [820, 1132, 901, 1134, 890, 1330, 920, 1000, 800, 900, 800, 1000],name) {
+	// console.log((uni.getSystemInfoSync().screenWidth - 50) / yAxis.length);
+	return {
+		grid: {
+			top: '30px',
+			bottom: '30px',
+			left: '24px',
+			right: '24px'
+		},
+		tooltip: {
+			trigger: 'axis',
+			axisPointer: {
+				type: 'shadow'
+			}
+		},
+		xAxis: {
+			type: 'category',
+			boundaryGap: false,
+			data: xAxis, //['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+			show: true,
+			axisLine: {
+				show: false,
+			},
+			axisLabel: {
+				fontSize: 9
+			},
+			splitLine: {
+				//x网格样式
+				show: true,
+				lineStyle: {
+					color: "rgba(31,126,255,0.4)",
+					type: "dashed",
+					width: "1",
+				},
+			},
+			axisTick: {
+				show: false
+			}
+		},
+		yAxis: {
+			type: 'value',
+			show: false
+		},
+		axisLine: {
+			//是否显示以及样式设置
+			show: true,
+			onZero: true, //表示 X 轴或者 Y 轴的轴线是否在另一个轴的 0 刻度上,只有在另一个轴为数值轴且包含 0 刻度时有效。
+			lineStyle: {
+				color: '#7E85AB',
+				type: 'dashed',
+			}
+		},
+		series: [{
+			name:name,
+			data: yAxis, //[820, 1132, 901, 1134, 890, 1330, 920, 1000, 800, 900, 800, 1000],
+			type: 'line',
+			areaStyle: {
+				color: {
+					type: 'linear',
+					x: 0,
+					y: 0,
+					x2: 0,
+					y2: 1,
+					colorStops: [{
+						offset: 0,
+						color: '#1F7EFF' // 0% 处的颜色
+					}, {
+						offset: 1,
+						color: '#F8F8F8' // 100% 处的颜色
+					}],
+					global: false // 缺省为 false
+				}
+			},
+			itemStyle: {
+				normal: {
+					label: {
+						show: true,
+						color: '#1F7EFF',
+						offset: [0, -2],
+						fontSize: 12,
+						width: (uni.getSystemInfoSync().screenWidth - 50) / yAxis.length,
+						overflow: 'truncate'
+					}
+				}
+			},
+			lineStyle: {
+				color: '#1F7EFF'
+			},
+			symbol: 'circle',
+			symbolSize: 6
+		}]
+	}
+}
+
+export default {
+	getDataCenter
+}

+ 76 - 0
common/http.interceptor.js

@@ -0,0 +1,76 @@
+import url from "./url.js"
+const install = (Vue, vm) => {
+	Vue.prototype.$u.http.setConfig({
+		baseUrl: url, // 请求的本域名
+		loadingText: '加载中...',
+		loadingTime: 800,
+		showLoading: true, // 是否显示请求中的loading
+		loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
+		originalData: true, // 是否在拦截器中返回服务端的原始数据
+		// 配置请求头信息
+		header: {
+			'content-type': 'application/json;charset=UTF-8',
+			'version': 'v1'
+		},
+	});
+	// 请求拦截部分,如配置,每次请求前都会执行
+	Vue.prototype.$u.http.interceptor.request = (config) => {
+		// 引用token
+		// 方式一,存放在vuex的token,假设使用了uView封装的vuex方式
+		// 见:https://uviewui.com/components/globalVariable.html
+		// config.header.token = vm.token;
+
+		// 方式二,如果没有使用uView封装的vuex方法,那么需要使用$store.state获取
+		// config.header.token = vm.$store.state.token;
+
+		// 方式三,如果token放在了globalData,通过getApp().globalData获取
+		// config.header.token = getApp().globalData.username;
+
+		// 方式四,如果token放在了Storage本地存储中,拦截是每次请求都执行的
+		// 所以哪怕您重新登录修改了Storage,下一次的请求将会是最新值
+		const token = uni.getStorageSync('token');
+		config.header.Authorization = token;
+
+		// 可以对某个url进行特别处理,此url参数为this.$u.get(url)中的url值
+		// if(config.url == '/user/login') config.header.noToken = true;
+		// 最后需要将config进行return
+		return config;
+		// 如果return一个false值,则会取消本次请求
+		// if(config.url == '/user/rest') return false; // 取消某次请求
+	}
+	// 响应拦截,如配置,每次请求结束都会执行本方法
+	Vue.prototype.$u.http.interceptor.response = (res) => {
+		if (res.statusCode == 200) {
+			// res为服务端返回值,可能有code,result等字段
+			// 这里对res.result进行返回,将会在this.$u.post(url).then(res => {})的then回调中的res的到
+			// 如果配置了originalData为true,请留意这里的返回值
+			return res.data;
+		} else {
+			// 假设201为token失效,这里跳转登录
+			if (res.statusCode == 500) {
+				var html = res.data
+				var start = html.indexOf('<h1>')
+				var end = html.indexOf('</h1>')
+				var title = html.substring(start + 4, end)
+
+				var start1 = html.indexOf('</abbr> in <a class="toggle" title=')
+				var end1 = html.indexOf('</a></h2>')
+				var title1 = html.substring(start1 + 35, end1)
+
+				uni.showModal({
+					title: `网络错误:${res.statusCode}`,
+					content: `位置:${title1}\r\n 标题:${title}`
+				})
+			} else {
+				uni.showModal({
+					content: `未知错误:${res.statusCode}`
+				})
+			}
+			return false;
+		}
+	}
+}
+
+export default {
+	install
+}

+ 2 - 0
common/url.js

@@ -0,0 +1,2 @@
+var url = 'https://fangtanhua.hdlkeji.com'
+export default url

+ 348 - 0
common/utils.js

@@ -0,0 +1,348 @@
+/**
+ * @说明:工具集
+ * @作者:陈万照
+ * @公司:山东标梵互动技术有限公司
+ * @官网:http://biaofun.com/
+ * @版本:v1.0.0
+ * @时间:2020年4月28日11:28:13
+ */
+export default {
+	/**
+	 * 同步 try catch 的进一步封装处理
+	 * 使用方法:
+	 * let [err, res] = await this.$utils.asyncTasks(Promise函数);
+	 * if(res) 成功
+	 * if(err) 失败
+	 */
+	asyncTasks(promise) {
+		return promise.then(data => {
+			return [null, data];
+		}).catch(err => [err]);
+	},
+
+	/**
+	 * 精确判断数据是否是 Object 类型
+	 * @param {Any} val 要判断的数据
+	 * @returns {Boolean} true:是;false:不是;
+	 */
+	isObject(val) {
+		return Object.prototype.toString.call(val) === '[object Object]' && val !== null && val !== undefined;
+	},
+
+	/**
+	 * 判断数据是否是 Array 类型
+	 * @param {Any} val 要判断的数据
+	 * @returns {Boolean} true:是;false:不是;
+	 */
+	isArray(val) {
+		return Object.prototype.toString.call(val) === '[object Array]';
+	},
+
+	/**
+	 * 判断数据是否是 String 类型
+	 * @param {Any} val 要判断的数据
+	 * @returns {Boolean} true:是;false:不是;
+	 */
+	isString(val) {
+		return Object.prototype.toString.call(val) === '[object String]';
+	},
+
+	/**
+	 * 精确判断数据是否是 Date 类型
+	 * @param {Any} val 要判断的数据
+	 * @returns {Boolean} true:是;false:不是;
+	 */
+	isDate(val) {
+		return Object.prototype.toString.call(val) === '[object Date]';
+	},
+
+	/**
+	 * 精确判断数据是否是 Function 类型
+	 * @param {Any} val 要判断的数据
+	 * @returns {Boolean} true:是;false:不是;
+	 */
+	isFunction(val) {
+		return Object.prototype.toString.call(val) === '[object Function]';
+	},
+
+	/**
+	 * 精确判断数据是否是 Number 类型
+	 * @param {Any} val 要判断的数据
+	 * @returns {Boolean} true:是;false:不是;
+	 */
+	isNumber(val) {
+		return Object.prototype.toString.call(val) === '[object Number]';
+	},
+
+	/**
+	 * 精确判断数据是否是 Boolean 类型
+	 * @param {Any} val 要判断的数据
+	 * @returns {Boolean} true:是;false:不是;
+	 */
+	isBoolean(val) {
+		return Object.prototype.toString.call(val) === '[object Boolean]';
+	},
+
+	/**
+	 * 判断 URL 是否是绝对 URL。
+	 * @param {String} url 要判断的 URL
+	 * @return {Boolean} true:是绝对URL;false:不是绝对URL;
+	 */
+	isAbsoluteURL(url) {
+		// 如果 URL 以 “<scheme>://” 或 “//”(协议相对URL)开头,则认为它是绝对的
+		// RFC 3986 将方案名称定义为以字母开头的字符序列,然后是字母,数字,加号,句点或连字符的任意组合
+		return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
+	},
+
+	/**
+	 * 合并 baseURL 和相对 URL 成一个完整的 URL
+	 * @param {String} baseURL baseURL
+	 * @param {String} relativeURL 相对 URL
+	 * @returns {String} 返回组合后的完整 URL
+	 */
+	combineURLs(baseURL, relativeURL) {
+		return relativeURL && this.isString(relativeURL) && this.isString(baseURL) ? baseURL.replace(/\/+$/, '') + '/' +
+			relativeURL.replace(/^\/+/, '') : baseURL;
+	},
+
+	/**
+	 * 深度合并对象,只支持合并两个对象,该方法不会改变原有的对象
+	 * @param {Object} FirstOBJ 第一个对象
+	 * @param {Object} SecondOBJ 第二个对象
+	 * @return {Object} 返回深度合并后的对象
+	 */
+	deepMargeObject(FirstOBJ, SecondOBJ) {
+		let ResultOBJ = {};
+		for (let key in FirstOBJ) {
+			ResultOBJ[key] = ResultOBJ[key] && ResultOBJ[key].toString() === "[object Object]" ? this.deepMargeObject(ResultOBJ[
+				key], FirstOBJ[key]) : ResultOBJ[key] = FirstOBJ[key];
+		}
+		for (let key in SecondOBJ) {
+			ResultOBJ[key] = ResultOBJ[key] && ResultOBJ[key].toString() === "[object Object]" ? this.deepMargeObject(ResultOBJ[
+				key], SecondOBJ[key]) : ResultOBJ[key] = SecondOBJ[key];
+		}
+		return ResultOBJ;
+	},
+	
+	/**
+	 * 生成指定长度的随机字符串
+	 * @param {Number} min 最小程度
+	 * @param {Number} max 最大长度 
+	 * @return {String} 返回生成的字符串
+	 */
+	randomString(min, max) {
+		let returnStr = "",
+			range = (max ? Math.round(Math.random() * (max - min)) + min : min),
+			arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+				'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+				'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
+			];
+		for (let i = 0; i < range; i++) {
+			let index = Math.round(Math.random() * (arr.length - 1));
+			returnStr += arr[index];
+		}
+		return returnStr;
+	},
+
+	/**
+	 * 格式化日期
+	 * @param {Date|String} date 日期或日期字符串
+	 */
+	formatDate(date) {
+		let YYYY = null;
+		let M = null;
+		let MM = null;
+		let D = null;
+		let DD = null;
+		let h = null;
+		let hh = null;
+		let m = null;
+		let mm = null;
+		let s = null;
+		let ss = null;
+		let ms = null;
+		let ms2 = null;
+		let ms3 = null;
+		let ms4 = null;
+		let dt = null;
+
+		// 如果 date 是 String 类型
+		if (date && this.isString(date)) {
+			// 真机运行时,如果直接用 new Date('YYYY-MM-DD hh:mm:ss') 会报 Invalid Date 错误,所以采用下面的方式创建日期
+			let dtArr = date.replace(/\//g, '.').replace(/-/g, '.').replace(/:/g, '.').replace(/T/g, ' ').replace(' ', '.').replace(
+				'Z', '').split('.');
+
+			let year = 2020;
+			let month = 12;
+			let day = 18;
+			let hour = 0;
+			let minute = 0;
+			let second = 0;
+			let millisecond = 0;
+			
+			// 年
+			if (dtArr.length > 0 && !isNaN(dtArr[0])) {
+				year = parseInt(dtArr[0]);
+			}
+			// 月
+			if (dtArr.length > 1 && !isNaN(dtArr[1])) {
+				month = parseInt(dtArr[1]);
+			}
+			// 日
+			if (dtArr.length > 2 && !isNaN(dtArr[2])) {
+				day = parseInt(dtArr[2]);
+			}
+			// 时
+			if (dtArr.length > 3 && !isNaN(dtArr[3])) {
+				hour = parseInt(dtArr[3]);
+			}
+			// 分
+			if (dtArr.length > 4 && !isNaN(dtArr[4])) {
+				minute = parseInt(dtArr[4]);
+			}
+			// 秒
+			if (dtArr.length > 5 && !isNaN(dtArr[5])) {
+				second = parseInt(dtArr[5]);
+			}
+			// 毫秒
+			if (dtArr.length > 6 && !isNaN(dtArr[6])) {
+				millisecond = parseInt(dtArr[6]);
+			}
+
+			date = new Date(year, month - 1, day, hour, minute, second, millisecond);
+		}
+
+		// 如果 date 是 Date 类型
+		if (date && this.isDate(date)) {
+			YYYY = date.getFullYear();
+			M = date.getMonth() + 1;
+			MM = M >= 10 ? M : '0' + M;
+			D = date.getDate();
+			DD = D >= 10 ? D : '0' + D;
+			h = date.getHours();
+			hh = h >= 10 ? h : '0' + h;
+			m = date.getMinutes();
+			mm = m >= 10 ? m : '0' + m;
+			s = date.getSeconds();
+			ss = s >= 10 ? s : '0' + s;
+			ms = date.getMilliseconds();
+			ms2 = ms;
+			ms3 = ms;
+			ms4 = ms;
+			if (ms < 10) {
+				ms2 = '0' + ms;
+				ms3 = '00' + ms;
+				ms4 = '000' + ms;
+			} else if (ms < 100) {
+				ms3 = '0' + ms;
+				ms4 = '00' + ms;
+			} else {
+				ms4 = '0' + ms;
+			}
+		}
+
+		// 返回的数据对象
+		let result = {
+			YYYY: YYYY,
+			MM: MM,
+			M: M,
+			DD: DD,
+			D: D,
+			hh: hh,
+			h: h,
+			mm: mm,
+			m: m,
+			ss: ss,
+			s: s,
+			ms: ms,
+			ms2: ms2,
+			ms3: ms3,
+			ms4: ms4,
+			dt: date,
+			f1: `${YYYY}-${MM}-${DD}`,
+			f2: `${YYYY}年${M}月${D}日`,
+			f3: `${YYYY}-${M}-${D} ${hh}:${mm}`,
+			f4: `${h}:${m}:${s}`,
+			f5: `${MM}-${DD}`,
+			f6: `${YYYY}-${MM}`,
+			f7: `${YYYY}年${M}月`,
+			f8: `${h}:${m}`,
+			f9: `${M}月${D}日`,
+			notes: 'YYYY(年),MM(月,补0),M(月,不补0),DD(日,补0),D(日,不补0),hh(时,补0),h(时,不补0),mm(分,补0),m(分,不补0),ss(秒,补0),s(秒,不补0),ms(毫秒,不补0),ms2(毫秒,补0到2位),ms3(毫秒,补0到3位),ms4(毫秒,补0到4位),其余的f1,f2,... 看格式就知道了!'
+		};
+		return result;
+	},
+
+	/**
+	 * 数字转中文
+	 * @param {Number} num 数字
+	 */
+	numberToChinese(num) {
+		if (!/^\d*(\.\d*)?$/.test(num)) return "Number is wrong!";
+		let AA = new Array("零", "一", "二", "三", "四", "五", "六", "七", "八", "九");
+		let BB = new Array("", "十", "百", "千", "万", "亿", "点", "");
+		let a = ("" + num).replace(/(^0*)/g, "").split("."),
+			k = 0,
+			re = "";
+		for (let i = a[0].length - 1; i >= 0; i--) {
+			switch (k) {
+				case 0:
+					re = BB[7] + re;
+					break;
+				case 4:
+					if (!new RegExp("0{4}\\d{" + (a[0].length - i - 1) + "}$").test(a[0]))
+						re = BB[4] + re;
+					break;
+				case 8:
+					re = BB[5] + re;
+					BB[7] = BB[5];
+					k = 0;
+					break;
+			}
+			if (k % 4 == 2 && a[0].charAt(i + 2) != 0 && a[0].charAt(i + 1) == 0) re = AA[0] + re;
+			if (a[0].charAt(i) != 0) re = AA[a[0].charAt(i)] + BB[k % 4] + re;
+			k++;
+		}
+		if (a.length > 1) //加上小数部分(如果有小数部分) 
+		{
+			re += BB[6];
+			for (let i = 0; i < a[1].length; i++) re += AA[a[1].charAt(i)];
+		}
+		return re;
+	},
+	
+	/**
+	 * 计算两个经纬度点之间的距离
+	 * @param {Number} lng1 第一个点的经度
+	 * @param {Number} lat1 第一个点的纬度
+	 * @param {Number} lng2 第二个点的经度
+	 * @param {Number} lat2 第二个点的纬度
+	 */
+	calcDistance(lng1, lat1, lng2, lat2) {
+		var radLat1 = lat1 * Math.PI / 180.0;
+		var radLat2 = lat2 * Math.PI / 180.0;
+		var a = radLat1 - radLat2;
+		var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
+		var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
+			Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
+		s = s * 6378.137; // EARTH_RADIUS;
+		s = Math.round(s * 10000) / 10000;
+		return s;
+	},
+	
+	/**
+	 * 获取数组最小值的下标
+	 * @param {Array} arr 数组
+	 */
+	getArrayMixValueIndex(arrar) {
+		let min = arrar[0];
+		let index = 0;
+		for (let i = 0; i < arrar.length; i++) {
+			if (min > arrar[i]) {
+				min = arrar[i];
+				index = i;
+			}
+		}
+		return index;
+	}
+}

+ 23 - 0
common/wss.js

@@ -0,0 +1,23 @@
+import SDK from "./Easemob-chat-4.0.7.js"; // 3.0sdk
+
+const install = (Vue, vm) => {
+	//实例化SDK对象
+	const WebIM = SDK;
+
+	WebIM.conn = new WebIM.connection({
+		appKey: "1132230508163855#demo",
+		https: true, //是否使用HTTPS
+		url: "wss://im-api-wechat.easecdn.com/websocket", // socket server (3.0 SDK)
+		apiUrl: "https://a1.easecdn.com", // rest server
+		heartBeatWait: 30000, //心跳间隔
+		autoReconnectNumMax: 5, //自动重连次数
+		useOwnUploadFun: false, // 是否使用自己的上传方式(如将图片文件等上传到自己的服务器,构建消息时只传url)
+		delivery:true
+	});
+	
+	Vue.prototype.$WebIM = WebIM
+}
+
+export default {
+	install
+}

+ 516 - 0
components/biaofun-datetime-picker/biaofun-datetime-picker.vue

@@ -0,0 +1,516 @@
+<!-- 
+ * @插件:日期时间选择器
+ * @作者:陈万照
+ * @公司:山东标梵互动信息技术有限公司
+ * @官网:http://biaofun.com/
+ * @微信:C207668802
+ * @QQ:207668802
+ * @邮箱:cwz@biaofun.com || 207668802@qq.com
+ * @版本:v1.0.8
+ -->
+<template>
+	<view class="datatime">
+		<picker mode="multiSelector" :range="range" range-key="text" @change="change" @columnchange="columnchange" :value="value" :disabled="disabled">
+			<view class="content" :class="{ placeholder: !dateStr }">
+				<text>{{ dateStr ? dateStr : placeholder }}</text>
+			</view>
+		</picker>
+	</view>
+</template>
+
+<script>
+import utils from '@/common/utils.js'; // 封装的工具集
+export default {
+	/**
+	 * 数据
+	 */
+	props: {
+		// 是否禁用
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+
+		// 占位符
+		placeholder: {
+			type: String,
+			default: '请选择日期时间'
+		},
+
+		// 表示有效日期时间范围的开始,
+		// 字符串格式为 "YYYY-MM-DD hh:mm"
+		start: {
+			type: String,
+			default: '1970-1-1 00:00'
+		},
+
+		// 表示有效日期时间范围的结束
+		// 字符串格式为 "YYYY-MM-DD hh:mm"
+		end: {
+			type: String,
+			default: '2300-1-1 00:00'
+		},
+
+		// 表示选择器的粒度,有效值:year | month | day | hour | minute
+		fields: {
+			type: String,
+			default: 'minute'
+		},
+
+		// 默认值
+		// 字符串格式为 "YYYY-MM-DD hh:mm"
+		defaultValue: {
+			type: String,
+			default: ''
+		}
+	},
+
+	/**
+	 * 数据
+	 */
+	data() {
+		return {
+			range: [],
+			value: [],
+			dateStr: '', // 最终显示的字符串
+			dtStart: null, // 有效范围开始
+			dtEnd: null, // 有效范围结束
+		};
+	},
+	
+	/**
+	 * 监听数据
+	 */ 
+	watch: {
+		// 默认值
+		defaultValue() {
+			// 设置默认值
+			this.setDefaultValue();
+		}
+	},
+
+	/**
+	 * 组件初次加载完成
+	 */
+	mounted() {
+		// 有效日期开始和结束
+		let start = this.start;
+		let end = this.end;
+		
+		// 验证是否是有效的开始和结束日期
+		if(!utils.isString(this.start)) {
+			console.log('开始日期需为String类型,格式为 "YYYY-MM-DD hh:mm"');
+			start = '1970-1-1 00:00';
+		}
+		if(!utils.isString(this.start)) {
+			console.log('结束日期需为String类型,格式为 "YYYY-MM-DD hh:mm"');
+			start = '2300-1-1 00:00';
+		}
+		
+		// 将开始日期和结束日期转为 Date 
+		let dtStart = utils.formatDate(start).dt;
+		let dtEnd = utils.formatDate(end).dt;
+		
+		// 判断有效日期结束是否大于有效日期开始,如果不是,则将有效日期结束修改为有效日期开始往后300年
+		if (dtEnd <= dtStart) {
+			dtEnd = utils.formatDate(start).dt;
+			dtEnd.setFullYear(dtStart.getFullYear() + 300);
+			dtEnd.setDate(dtEnd.getDate() - 1);
+		}
+		
+		// 更新开始日期和结束日期
+		this.dtStart = dtStart;
+		this.dtEnd = dtEnd;
+
+		// 设置默认值
+		this.setDefaultValue();
+	},
+
+	/**
+	 * 方法
+	 */
+	methods: {
+		/**
+		 * 清空
+		 */
+		clearTime(){
+			this.dateStr = ''
+		},
+		/**
+		 * 确认选择
+		 */
+		change(event) {
+			let year, month, day, hour, minute;
+			if(this.fields == 'year') {
+				year = this.range[0][this.value[0]].number; // 年
+				let dtStr = `${year}`;
+				this.setDateStr(dtStr);
+				this.$emit('change', utils.formatDate(dtStr));
+				return;
+			}
+			else if(this.fields == 'month') {
+				year = this.range[0][this.value[0]].number; // 年
+				month = this.range[1][this.value[1]].number; // 月
+				let dtStr = `${year}-${month}`;
+				this.setDateStr(dtStr);
+				this.$emit('change', utils.formatDate(dtStr));
+				return;
+			}
+			else if(this.fields == 'day') {
+				year = this.range[0][this.value[0]].number; // 年
+				month = this.range[1][this.value[1]].number; // 月
+				day = this.range[2][this.value[2]].number; // 日
+				let dtStr = `${year}-${month}-${day}`;
+				this.setDateStr(dtStr);
+				this.$emit('change', utils.formatDate(dtStr));
+				return;
+			}
+			else if(this.fields == 'hour') {
+				year = this.range[0][this.value[0]].number; // 年
+				month = this.range[1][this.value[1]].number; // 月
+				day = this.range[2][this.value[2]].number; // 日
+				hour = this.range[3][this.value[3]].number; // 时
+				day = this.range[2][this.value[2]].number; // 日
+				let dtStr = `${year}-${month}-${day} ${hour}`;
+				this.setDateStr(dtStr);
+				this.$emit('change', utils.formatDate(dtStr));
+				return;
+			}
+			else if(this.fields == 'minute') {
+				year = this.range[0][this.value[0]].number; // 年
+				month = this.range[1][this.value[1]].number; // 月
+				day = this.range[2][this.value[2]].number; // 日
+				hour = this.range[3][this.value[3]].number; // 时
+				minute = this.range[4][this.value[4]].number; // 分
+				let dtStr = `${year}-${month}-${day} ${hour}:${minute}`;
+				this.setDateStr(dtStr);
+				this.$emit('change', utils.formatDate(dtStr));
+				return;
+			}
+		},
+
+		/**
+		 * 设置显示的值
+		 * @param {Date|String} date 日期字符串或日期对象
+		 */
+		setDateStr(date) {
+			let dt = utils.formatDate(date);
+			if(this.fields == 'year') {
+				this.dateStr = `${dt.YYYY}年`;
+				return;
+			}
+			if(this.fields == 'month') {
+				this.dateStr = `${dt.YYYY}年${dt.M}月`;
+				return;
+			}
+			if(this.fields == 'day') {
+				this.dateStr = `${dt.YYYY}年${dt.M}月${dt.D}日`;
+				return;
+			}
+			if(this.fields == 'hour') {
+				this.dateStr = `${dt.YYYY}年${dt.M}月${dt.D}日 ${dt.h}时`;
+				return;
+			}
+			this.dateStr = `${dt.YYYY}年${dt.M}月${dt.D}日 ${dt.h}时${dt.m}分`;
+		},
+		
+		/**
+		 * 设置年数据
+		 */ 
+		setYearData() {
+			// 有效日期
+			let yearStart = this.dtStart.getFullYear();
+			let yearEnd = this.dtEnd.getFullYear();
+			// 年
+			let years = [];
+			for (let year = yearStart; year <= yearEnd; year++) {
+				let item = {
+					number: year,
+					text: `${year}年`,
+				};
+				years.push(item);
+			}
+			this.range.splice(0, 1, years);
+		},
+		
+		/**
+		 * 设置月数据
+		 * @param {Number} year 年 
+		 */ 
+		setMonthData(year) {
+			// 有效日期
+			let yearStart = this.dtStart.getFullYear();
+			let monthStart = this.dtStart.getMonth() + 1;
+			let yearEnd = this.dtEnd.getFullYear();
+			let monthEnd = this.dtEnd.getMonth() + 1;
+			
+			// 月
+			let months = [];
+			let monthStartIndex = year == yearStart ? monthStart : 1;
+			let monthEndIndex = year == yearEnd ? monthEnd : 12;
+			for (let month = monthStartIndex; month <= monthEndIndex; month++) {
+				let item = {
+					number: month,
+					text: `${month}月`,
+				};
+				months.push(item);
+			}
+			this.range.splice(1, 1, months);
+		},
+		
+		/**
+		 * 设置日数据
+		 * @param {Number} year 年 
+		 * @param {Number} month 月 
+		 */ 
+		setDayData(year, month) {
+			// 有效日期
+			let yearStart = this.dtStart.getFullYear();
+			let monthStart = this.dtStart.getMonth() + 1;
+			let dayStart = this.dtStart.getDate();
+			let yearEnd = this.dtEnd.getFullYear();
+			let monthEnd = this.dtEnd.getMonth() + 1;
+			let dayEnd = this.dtEnd.getDate();
+			
+			// 日
+			let days = [];
+			let dayStartIndex = year == yearStart && month == monthStart ? dayStart : 1;
+			let dayEndIndex; 
+			if(year == yearEnd && month == monthEnd) {
+				dayEndIndex = dayEnd;
+			} else {
+				dayEndIndex = (new Date(year, month, 0)).getDate();
+			}
+			for (let day = dayStartIndex; day <= dayEndIndex; day++) {
+				let item = {
+					number: day,
+					text: `${day}日`,
+				};
+				days.push(item);
+			}
+			this.range.splice(2, 1, days);
+		},
+		
+		/**
+		 * 设置时数据
+		 * @param {Number} year 年 
+		 * @param {Number} month 月 
+		 * @param {Number} day 日 
+		 */ 
+		setHourData(year, month, day) {
+			// 有效日期
+			let yearStart = this.dtStart.getFullYear();
+			let monthStart = this.dtStart.getMonth() + 1;
+			let dayStart = this.dtStart.getDate();
+			let hourStart = this.dtStart.getHours();
+			let yearEnd = this.dtEnd.getFullYear();
+			let monthEnd = this.dtEnd.getMonth() + 1;
+			let dayEnd = this.dtEnd.getDate();
+			let hourEnd = this.dtEnd.getHours();
+			
+			// 时
+			let hours = [];
+			let hourStartIndex = year == yearStart && month == monthStart && day == dayStart ? hourStart : 0;
+			let hourEndIndex = year == yearEnd && month == monthEnd && day == dayEnd ? hourEnd : 23;
+			for (let hour = hourStartIndex; hour <= hourEndIndex; hour++) {
+				let item = {
+					number: hour,
+					text: `${hour}时`,
+				};
+				hours.push(item);
+			}
+			this.range.splice(3, 1, hours);
+		},
+		
+		/**
+		 * 设置分数据
+		 * @param {Number} year 年 
+		 * @param {Number} month 月 
+		 * @param {Number} day 日
+		 * @param {Number} hour 时
+		 */ 
+		setMinuteData(year, month, day, hour) {
+			// 有效日期
+			let yearStart = this.dtStart.getFullYear();
+			let monthStart = this.dtStart.getMonth() + 1;
+			let dayStart = this.dtStart.getDate();
+			let hourStart = this.dtStart.getHours();
+			let minuteStart = this.dtStart.getMinutes();
+			let yearEnd = this.dtEnd.getFullYear();
+			let monthEnd = this.dtEnd.getMonth() + 1;
+			let dayEnd = this.dtEnd.getDate();
+			let hourEnd = this.dtEnd.getHours();
+			let minuteEnd = this.dtEnd.getMinutes();
+			
+			// 分
+			let minutes = [];
+			let minuteStartIndex = year == yearStart && month == monthStart && day == dayStart && hour == hourStart ? minuteStart : 0;
+			let minuteEndIndex = year == yearEnd && month == monthEnd && day == dayEnd && hour == hourEnd ? minuteEnd : 59;
+			for(let minute = minuteStartIndex; minute <= minuteEndIndex; minute++) {
+				let item = {
+					number: minute,
+					text: `${minute}分`,
+				}
+				minutes.push(item);
+			}
+			this.range.splice(4, 1, minutes);
+		},
+		
+		/**
+		 * 设置默认值
+		 */
+		setDefaultValue() {
+			// 默认日期
+			let dtDefault;
+			
+			// 开始日期和结束日期
+			let dtStart = this.dtStart;
+			let dtEnd = this.dtEnd;
+			
+			// 判断是否传了默认日期
+			// 传了默认日期,格式化默认日期为日期对象
+			if(this.defaultValue) {
+				dtDefault = utils.formatDate(this.defaultValue).dt;
+			} 
+			// 如果没有传默认日期,将默认日期设置为当前日期
+			else {
+				dtDefault = new Date();
+			}
+			// 如果默认日期不在有效日期范围内,设置默认日期为有效日期开始值
+			if (dtDefault < dtStart || dtDefault > dtEnd) {
+				dtDefault = dtStart;
+			}
+			
+			// 更新 dateStr
+			if(this.defaultValue) this.setDateStr(dtDefault);
+			
+			// 默认值相关数据
+			let dfYear = dtDefault.getFullYear();
+			let dfMonth = dtDefault.getMonth() + 1;
+			let dfDay = dtDefault.getDate();
+			let dfHour = dtDefault.getHours();
+			let dfMinute = dtDefault.getMinutes();
+			
+			// 设置年数据
+			this.setYearData();
+			// 设置 Year 这一列的 value 值
+			let yearIndex = this.range[0].findIndex(year => {
+				return dfYear == year.number;
+			});
+			this.value.splice(0, 1, yearIndex >= 0 ? yearIndex : 0);
+			
+			// 设置月数据
+			if(this.fields == 'year') return;
+			this.setMonthData(dfYear);
+			// 设置 Month 这一列的 value 值
+			let monthIndex = this.range[1].findIndex(month => {
+				return dfMonth == month.number;
+			});
+			this.value.splice(1, 1, monthIndex >=0 ? monthIndex : 0);
+			
+			// 设置日数据
+			if(this.fields == 'month') return;
+			this.setDayData(dfYear, dfMonth);
+			// 设置 Day 这一列的 value 值
+			let dayIndex = this.range[2].findIndex(day => {
+				return dfDay == day.number;
+			});
+			this.value.splice(2, 1, dayIndex >=0 ? dayIndex : 0);
+			
+			// 设置时数据
+			if(this.fields == 'day') return;
+			this.setHourData(dfYear, dfMonth, dfDay);
+			// 设置 Hour 这一列的 value 值
+			let hourIndex = this.range[3].findIndex(hour => {
+				return dfHour == hour.number;
+			});
+			this.value.splice(3, 1, hourIndex >=0 ? hourIndex : 0);
+			
+			// 设置分数据
+			if(this.fields == 'hour') return;
+			this.setMinuteData(dfYear, dfMonth, dfDay, dfHour);
+			// 设置 Minute 这一列的 value 值
+			let minuteIndex = this.range[4].findIndex(minute => {
+				return dfMinute == minute.number;
+			});
+			this.value.splice(4, 1, minuteIndex >=0 ? minuteIndex : 0);
+		},
+
+		/**
+		 * 某一列的值改变时触发
+		 * @param {Number} event.detail.column 表示改变了第几列(下标从0开始)
+		 * @param {Number} event.detail.value 表示变更值的下标
+		 */
+		columnchange(event) {
+			let columnIndex = event.detail.column; // 改变的列的下标
+			let valueIndex = event.detail.value; // 变更值的下标
+			
+			// 更新改变列的 value
+			this.value.splice(columnIndex, 1, valueIndex);
+			
+			// 改变年要更新月数据
+			if(this.fields == 'year') return;
+			if (columnIndex == 0) {
+				// 当前选择的月
+				let monthBeforeUpdate = this.range[1][this.value[1]];
+				// 更新月数据
+				this.setMonthData(this.range[0][this.value[0]].number);
+				// 更新 Month Value
+				let monthIndex = this.range[1].findIndex(month => {
+					return month.number == monthBeforeUpdate.number;
+				});
+				this.value.splice(1, 1, monthIndex >= 0 ? monthIndex : 0);
+			}
+			
+			// 改变年、月都要更新日数据
+			if(this.fields == 'month') return;
+			if (columnIndex == 0 || columnIndex == 1) {
+				// 当前选择的日
+				let dayBeforeUpdate = this.range[2][this.value[2]];
+				// 更新日数据
+				this.setDayData(this.range[0][this.value[0]].number, this.range[1][this.value[1]].number);
+				// 更新 Day Value
+				let dayIndex = this.range[2].findIndex(day => {
+					return day.number == dayBeforeUpdate.number;
+				});
+				this.value.splice(2, 1, dayIndex >= 0 ? dayIndex : 0);
+			}
+			
+			// 改变年、月、日都要更新时数据
+			if(this.fields == 'day') return;
+			if (columnIndex == 0 || columnIndex == 1 || columnIndex == 2) {
+				// 当前选择的时
+				let hourBeforeUpdate = this.range[3][this.value[3]];
+				// 更新时数据
+				this.setHourData(this.range[0][this.value[0]].number, this.range[1][this.value[1]].number, this.range[2][this.value[2]].number);
+				// 更新 Hour Value
+				let hourIndex = this.range[3].findIndex(hour => {
+					return hour.number == hourBeforeUpdate.number;
+				});
+				this.value.splice(3, 1, hourIndex >= 0 ? hourIndex : 0);
+			}
+			
+			// 当前选择的分
+			if(this.fields == 'hour') return;
+			let minuteBeforeUpdate = this.range[4][this.value[4]];
+			// 更新分数据
+			this.setMinuteData(this.range[0][this.value[0]].number, this.range[1][this.value[1]].number, this.range[2][this.value[2]].number, this.range[3][this.value[3]].number);
+			// 更新 Minute Value
+			let minuteIndex = this.range[4].findIndex(minute => {
+				return minute.number == minuteBeforeUpdate.number;
+			});
+			this.value.splice(4, 1, minuteIndex >= 0 ? minuteIndex : 0);
+		},
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.content {
+	// text-align: right;
+}
+
+.placeholder {
+	color: #949596;
+}
+</style>

+ 30 - 0
components/biaofun-datetime-picker/使用说明.md

@@ -0,0 +1,30 @@
+### 组件说明
+* 日期时间选择器
+* 组件的默认日期有效期范围为:"1970-01-01 00:00" - "2300-01-01 00:00"。
+* 注意:如果您传递的日期有效范围的结束日期小于开始日期,则日期有效范围的结束日期会自动修正为开始日期+300年,比如,您传递的日期有效范围的开始日期为 "2020-11-11 18:30",
+* 结束日期为 "2018-11-11 18:30",则此时将会自动修正结束日期为 "2320-11-11 18:30"。
+* 注意:如果您传递的默认值不在日期有效范围内,则会自动修正默认值为当前日期时间,如果当前日期时间也不在日期有效范围内,则会再次修正为日期有效范围的开始日期。
+* 注意:该组件用到了我自己封装的 utils.js,位置在 '@/common/js/utils.js'。
+
+
+### 插件 props 属性
+* disabled: 是否禁用该组件?Boolean类型;
+* placeholder: 组件没有选中值时显示的内容,String类型;
+* start: 表示有效日期时间范围的开始,String类型,格式为 "YYYY-MM-DD hh:mm";
+* end: 表示有效日期时间范围的结束,String类型,格式必为 "YYYY-MM-DD hh:mm";
+* fields: 选择器的粒度,String类型,有效值为 year、month、day、hour、minute;
+* defaultValue: 默认值,String类型,格式为 "YYYY-MM-DD hh:mm";
+
+### 插件事件
+- change(date):选择日期时间后的回调事件。
+ * date.YYYY: 年;
+ * date.M: 月;
+ * date.MM: 月(补0);
+ * date.D: 日;
+ * date.DD: 日(补0);
+ * date.h: 时;
+ * date.hh: 时(补0);
+ * date.m: 分;
+ * date.mm: 分(补0);
+ * date.dt: Date对象;
+ * ... 还有一些其他的字段,具体看返回值吧!

+ 211 - 0
components/gf-goods/gf-goods.vue

@@ -0,0 +1,211 @@
+<template>
+	<view class="gf-goods">
+		<view :class="type == 1 ? 'row-item u-flex' : 'col-item'" @click="toinfo">
+			<image :src="data.logo" class="item-img" mode="aspectFill"></image>
+			<view class="item-box">
+				<view class="item-name u-line-1">
+					{{data.name}}
+				</view>
+				<view class="item-tips u-flex">
+					<text class="u-line-1" style="white-space: nowrap;" v-if="data.house_type">{{data.house_type.join('·')}}·</text>
+					<text style="white-space: nowrap;">{{data.house_min_area || 0}}-{{data.house_max_area || 0}}㎡</text>
+				</view>
+				<view class="item-tips">
+					{{data.area}}
+				</view>
+				<view class="price-box u-flex">
+					<text>参考均价</text>
+					<text v-if="Number(data.avg_price)">{{data.avg_price}}/㎡</text>
+					<text v-else>{{data.avg_price}}</text>
+				</view>
+				<view class="u-flex label-box u-flex-wrap">
+					<text v-for="(item,index) in data.trait" :key="index" v-if="index < 2">{{item}}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+
+</template>
+
+<script>
+	export default {
+		props: {
+			// b布局1独占一行2一行两个
+			type: {
+				type: [Number, String],
+				default: 2
+			},
+			data: {
+				type: Object,
+				default () {
+					return {
+						area: "鹿城区",
+						avg_price: "27412",
+						house_max_area: 212.06,
+						house_min_area: 116.8,
+						house_type: ["三房两厅两卫", "四房两厅三卫", "四房两厅两卫", "四房两厅四卫"],
+						id: 1,
+						logo: "https://shujuhuifu.oss-cn-beijing.aliyuncs.com/af20f05b337db210/1c354234c4e49d6c.jpg",
+						name: "国鸿中心",
+						trait: ["超级综合体", "一线江景", "约350米摩天超高层", "内外双公园"]
+					}
+				}
+			}
+		},
+		data() {
+			return {
+
+			}
+		},
+		onLoad() {
+
+		},
+		methods: {
+			toinfo(){
+				uni.navigateTo({
+					url:"/pages/index/houses-info?id=" + this.data.id
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	.gf-goods {
+		.row-item {
+			margin-bottom: 32rpx;
+
+			.item-img {
+				width: 280rpx;
+				height: 240rpx;
+				margin-right: 20rpx;
+				border-radius: 20rpx;
+			}
+
+			.item-box {
+				flex: 1;
+				min-width: 1rpx;
+				.label-box {
+					text {
+						margin-right: 10rpx;
+						padding: 0 12rpx;
+						line-height: 40rpx;
+						height: 40rpx;
+						background: #F5F5F5;
+						border-radius: 8rpx;
+						font-size: 18rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+						white-space: nowrap;
+					}
+				}
+
+				.price-box {
+					margin-bottom: 8rpx;
+
+					text:first-child {
+						font-size: 20rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+						margin-right: 8rpx;
+					}
+
+					text:last-child {
+						font-size: 32rpx;
+						font-family: DINAlternate-Bold, DINAlternate;
+						font-weight: bold;
+						color: #FF3B30;
+					}
+				}
+
+				.item-name {
+					font-size: 28rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #131415;
+					margin: 10rpx 0;
+				}
+
+				.item-tips {
+					font-size: 18rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+					margin-bottom: 12rpx;
+				}
+			}
+		}
+
+		.col-item {
+			width: 320rpx;
+			background: #F8FBFF;
+			border-radius: 20rpx;
+			margin-bottom: 20rpx;
+			padding-bottom: 24rpx;
+
+			.item-box {
+				padding: 0 20rpx;
+
+				.label-box {
+					text {
+						margin-right: 10rpx;
+						padding: 0 12rpx;
+						line-height: 40rpx;
+						height: 40rpx;
+						background: #F5F5F5;
+						border-radius: 8rpx;
+						font-size: 18rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+					}
+				}
+
+				.price-box {
+					margin-bottom: 8rpx;
+
+					text:first-child {
+						font-size: 20rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+						margin-right: 8rpx;
+					}
+
+					text:last-child {
+						font-size: 32rpx;
+						font-family: DINAlternate-Bold, DINAlternate;
+						font-weight: bold;
+						color: #FF3B30;
+					}
+				}
+
+				.item-name {
+					font-size: 28rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #131415;
+					margin: 10rpx 0;
+				}
+
+				.item-tips {
+					font-size: 18rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+					margin-bottom: 12rpx;
+				}
+			}
+
+			.item-img {
+				width: 320rpx;
+				height: 240rpx;
+				border-radius: 20rpx;
+			}
+		}
+	}
+
+</style>

+ 144 - 0
components/gf-scroll/gf-scroll.vue

@@ -0,0 +1,144 @@
+<template>
+	<view class="scroll1-box">
+		<scroll-view scroll-x="true" @scroll="scroll" class="scroll-view" :bounces="false" enhanced="true" scroll-anchoring="true">
+			<view class="tabs-scroll-box u-flex u-flex-wrap" :style="{width:scrollWidth * 25 + '%'}">
+				<view class="tabs-item u-flex-col u-col-center" :style="{width:100 / scrollWidth + '%'}" v-for="(item,index) in list" :key="index" @click="tourl(item)">
+					<image :src="item.logo" mode=""></image>
+					<text>{{item.title}}</text>
+				</view>
+			</view>
+		</scroll-view>
+		<view class="hidescroll"></view>
+		<view class="scroll-box">
+			<view class="scroll" :style="{transform: `translateX(${sX})`,width:s_b_W}"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "rt-scroll",
+		props: {
+			list: {
+				type: Array,
+				default: () => {
+					return []
+				}
+			},
+		},
+		data() {
+			return {
+				sX: '0%', //等比偏移量
+				c_W: 0, //最外围容器宽度
+				c_b_W: 0, //最外围容器内容宽度
+				s_b_W: '0px', //自定义滚动条移动盒子宽度
+				tablist: [],
+				scrollWidth: 0
+			};
+		},
+		watch: {
+			list: {
+				handler() {
+					this.getwidth()
+				},
+				deep: true
+			}
+		},
+		created() {
+			this.getwidth()
+		},
+		methods: {
+			getwidth() {
+				this.scrollWidth = Math.ceil(this.list.length / 2)
+				this.$nextTick(() => {
+					this.getdocument()
+				})
+				// console.log(this.scrollWidth);
+			},
+			tourl(item) {
+				this.$emit('click', item)
+			},
+			scroll(e) {
+				if ((this.c_b_W - this.c_W) < 0) {
+					return
+				}
+				let nX = e.detail.scrollLeft // 得到滑动的偏移量
+				var zong = this.c_b_W - this.c_W //总的偏移量
+				this.sX = nX / zong * 100 + '%'
+			},
+			getdocument(){
+				const query = uni.createSelectorQuery().in(this);
+				query.selectAll('.scroll-view,.tabs-scroll-box').boundingClientRect((data) => {
+					// dome元素从外到内依次获取数据,所以写法按顺序来才不会混
+					this.c_W = data[0].width
+					this.c_b_W = data[1].width
+					this.s_b_W = (this.c_b_W - this.c_W) <= 0 ? "100%" : "15px"
+					// console.log(this.c_W, this.c_b_W);
+				}).exec();
+			}
+		},
+	}
+
+</script>
+
+<style lang="scss">
+	.scroll1-box {
+		padding-top: 2rpx;
+		width: 702rpx;
+		// height: 404rpx;
+		background: #FFFFFF;
+		border-radius: 20rpx;
+		padding-bottom: 28rpx;
+		position: relative;
+		.hidescroll{
+			position: absolute;
+			width: 702rpx;
+			height: 20rpx;
+			background-color: #fff;
+			left: 0;
+			bottom: 38rpx;
+			z-index: 10;
+		}
+		.scroll-view {
+			padding-top: 12rpx;
+			
+			
+			.tabs-scroll-box {
+				padding: 0 4rpx;
+
+				.tabs-item {
+					width: 25%;
+					margin: 20rpx 0 12rpx 0;
+					image {
+						width: 84rpx;
+						height: 84rpx;
+						margin-bottom: 20rpx;
+						border-radius: 20rpx;
+					}
+
+					text {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #131415;
+					}
+				}
+			}
+		}
+
+		.scroll-box {
+			width: 30px;
+			height: 10rpx;
+			background: rgba(30, 125, 255, 0.2);
+			border-radius: 5rpx;
+			margin: 10rpx auto 0 auto;
+
+			.scroll {
+				height: 100%;
+				background: #1E7DFF;
+				border-radius: 5rpx;
+			}
+		}
+	}
+
+</style>

+ 350 - 0
components/gf-search/gf-search.vue

@@ -0,0 +1,350 @@
+<template>
+	<view class="gf-search">
+		<u-popup v-model="show" mode="right">
+			<view class="search-popup">
+				<view class="search-item">
+					<view class="search-title">
+						区域筛选
+					</view>
+					<view class="u-flex">
+						<view class="select-box0 u-flex u-row-between" @click="showsheng = true">
+							<text class="text u-line-1">{{shengname || '选择省'}}</text>
+							<u-icon name="arrow-down" color="#D8D8D8" size="26"></u-icon>
+						</view>
+						<view class="select-box0 u-flex u-row-between" @click="showshi = true">
+							<text class="text u-line-1">{{shiname || '选择市'}}</text>
+							<u-icon name="arrow-down" color="#D8D8D8" size="26"></u-icon>
+						</view>
+						<view class="select-box0 u-flex u-row-between" @click="showqu = true">
+							<text class="text u-line-1">{{quname || '选择区'}}</text>
+							<u-icon name="arrow-down" color="#D8D8D8" size="26"></u-icon>
+						</view>
+					</view>
+
+				</view>
+				<view class="search-item">
+					<view class="search-title">
+						价格筛选
+					</view>
+					<view class="select-box1 u-flex">
+						<text :class="price == 1 ? 'text1 text1_active' : 'text1'" @click="price = 1">从低到高</text>
+						<text :class="price == 2 ? 'text1 text1_active' : 'text1'" @click="price = 2">从高到低</text>
+					</view>
+				</view>
+				<view class="search-item">
+					<view class="search-title">
+						面积筛选
+					</view>
+					<view class="select-box2 u-flex">
+						<view class="text1 u-flex">
+							<input type="number" placeholder="请输入" v-model="mianji1">
+							<text>㎡</text>
+						</view>
+						<text class="xian"></text>
+						<view class="text1 u-flex">
+							<input type="number" placeholder="请输入" v-model="mianji2">
+							<text>㎡</text>
+						</view>
+					</view>
+				</view>
+				<view class="search-item">
+					<view class="search-title">
+						户型筛选
+					</view>
+					<view class="select-box u-flex u-row-between" @click="showhuxing = true">
+						<text class="text">{{huxingname || '请选择'}}</text>
+						<u-icon name="arrow-down" color="#D8D8D8" size="26"></u-icon>
+					</view>
+				</view>
+				<view class="search-item">
+					<view class="search-title">
+						类别筛选
+					</view>
+					<view class="select-box u-flex u-row-between" @click="showleibie = true">
+						<text class="text">{{leibiename || '请选择'}}</text>
+						<u-icon name="arrow-down" color="#D8D8D8" size="26"></u-icon>
+					</view>
+				</view>
+				<view class="u-flex search-btn u-row-between">
+					<text @click="chongzhi">重置</text>
+					<text @click="shaixuan">确定</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-select v-model="showsheng" :list="shenglist" label-name="name" value-name="id" @confirm="changesheng"></u-select>
+		<u-select v-model="showshi" :list="shilist" label-name="name" value-name="id" @confirm="changeshi"></u-select>
+		<u-select v-model="showqu" :list="qulist" label-name="name" value-name="id" @confirm="changequ"></u-select>
+		<u-select v-model="showhuxing" :list="huxinglist" label-name="title" value-name="id" @confirm="changehuxing"></u-select>
+		<u-select v-model="showleibie" :list="leibielist" label-name="title" @confirm="changeleibie"></u-select>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				show: false,
+
+				showsheng: false,
+				shenglist: [],
+				shengname: '',
+				shengid: '',
+
+				showshi: false,
+				shilist: [],
+				shiname: '',
+				shiid: '',
+
+				showqu: false,
+				qulist: [],
+				quname: '',
+				quid: '',
+
+				price: '',
+				mianji1: '',
+				mianji2: '',
+				showhuxing: false,
+				huxinglist: [],
+				huxingname: '',
+				huxingid: '',
+				showleibie: false,
+				leibielist: [],
+				leibiename: '',
+			}
+		},
+		created() {
+			this.getconfig()
+		},
+		methods: {
+			shaixuan() {
+				if(this.shengid && !this.quid){
+					this.$u.toast("请选择完整区域")
+					return
+				}
+				if ((this.mianji1 && this.mianji2) || (!this.mianji1 && !this.mianji2)) {
+					this.$emit('shaixuan', {
+						area_id: this.quid,
+						price_sort: this.price,
+						min_area: this.mianji1,
+						max_area: this.mianji2,
+						house_type_id: this.huxingid,
+						property_class: this.leibiename
+					})
+					this.show = false
+				} else {
+					this.$u.toast("面积有误")
+				}
+
+			},
+			chongzhi() {
+				this.shengid = ''
+				this.shengname = ''
+				this.shiid = ''
+				this.shiname = ''
+				this.quid = ''
+				this.quname = ''
+				this.price = ''
+				this.mianji1 = ''
+				this.mianji2 = ''
+				this.huxingid = ''
+				this.huxingname = ''
+				this.leibiename = ''
+				this.show = false
+			},
+			changeleibie(e) {
+				this.leibiename = e[0].label
+			},
+			changehuxing(e) {
+				this.huxingname = e[0].label
+				this.huxingid = e[0].value
+			},
+			changesheng(e) {
+				this.shengname = e[0].label
+				this.shengid = e[0].value
+				this.shilist = []
+				this.shiname = ''
+				this.shiid = ''
+				this.qulist = []
+				this.quname = ''
+				this.quid = ''
+				this.$u.post('/api/Data/area_list', {
+					level: 2,
+					province_id: this.shengid
+				}).then(res => {
+					this.shilist = res.data
+				})
+			},
+			changeshi(e) {
+				this.shiname = e[0].label
+				this.shiid = e[0].value
+				this.qulist = []
+				this.quname = ''
+				this.quid = ''
+				this.$u.post('/api/Data/area_list', {
+					level: 3,
+					city_id: this.shiid
+				}).then(res => {
+					this.qulist = res.data
+				})
+			},
+			changequ(e) {
+				this.quname = e[0].label
+				this.quid = e[0].value
+			},
+			getconfig() {
+				this.$u.post('/api/Data/area_list', {
+					level: 1
+				}).then(res => {
+					this.shenglist = res.data
+				})
+				this.$u.post('/api/Index/house_type_list').then(res => {
+					this.huxinglist = res.data
+				})
+				this.$u.post('/api/Index/property_class_list').then(res => {
+					this.leibielist = res.data
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.gf-search {
+		.search-popup {
+			width: 448rpx;
+			background: #FFFFFF;
+			padding: 0 24rpx;
+
+			.search-btn {
+				margin-top: 40rpx;
+
+				text:first-child {
+					width: 188rpx;
+					line-height: 76rpx;
+					background: #FFA120;
+					border-radius: 8rpx;
+					text-align: center;
+					font-size: 22rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #FFFFFF;
+				}
+
+				text:last-child {
+					width: 188rpx;
+					line-height: 76rpx;
+					background: #1F7EFF;
+					border-radius: 8rpx;
+					text-align: center;
+					font-size: 22rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #FFFFFF;
+				}
+			}
+
+			.search-item {
+				padding: 40rpx 0 24rpx 0;
+				border-bottom: 1rpx solid #F5F5F5;
+
+				.select-box2 {
+					.text1 {
+						width: 128rpx;
+						height: 52rpx;
+						background: #FFFFFF;
+						border-radius: 8rpx;
+						border: 2rpx solid #CCCCCC;
+						padding: 0 10rpx;
+
+						input {
+							flex: 1;
+							text-align: center;
+							font-size: 24rpx;
+						}
+
+						text {
+							font-size: 22rpx;
+							font-family: PingFangSC-Regular, PingFang SC;
+							font-weight: 400;
+							color: #CCCCCC;
+						}
+					}
+
+					.xian {
+						width: 32rpx;
+						height: 2rpx;
+						background: #CCCCCC;
+						border-radius: 8rpx;
+						margin: 0 10rpx;
+					}
+				}
+
+				.select-box1 {
+					.text1 {
+						width: 128rpx;
+						line-height: 52rpx;
+						background: #FFFFFF;
+						border-radius: 8rpx;
+						border: 2rpx solid #CCCCCC;
+						text-align: center;
+						font-size: 22rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #CCCCCC;
+						margin-right: 20rpx;
+					}
+
+					.text1_active {
+						border: 2rpx solid #1F7EFF;
+						color: #1F7EFF;
+					}
+				}
+
+				.search-title {
+					margin-bottom: 20rpx;
+					font-size: 28rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+				}
+
+				.select-box0 {
+					flex: 1;
+					height: 52rpx;
+					background: #FFFFFF;
+					border-radius: 8rpx;
+					border: 2rpx solid #CCCCCC;
+					padding: 0 5rpx;
+					margin-right: 10rpx;
+
+					.text {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #333333;
+						flex: 1;
+					}
+				}
+
+				.select-box {
+					width: 312rpx;
+					height: 52rpx;
+					background: #FFFFFF;
+					border-radius: 8rpx;
+					border: 2rpx solid #CCCCCC;
+					padding: 0 14rpx;
+
+					.text {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #333333;
+						flex: 1;
+					}
+				}
+			}
+
+
+		}
+	}
+</style>

+ 31 - 0
index.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+	<head>
+		<meta charset="utf-8">
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
+		<title>
+			<%= htmlWebpackPlugin.options.title %>
+		</title>
+		<!-- Open Graph data -->
+		<!-- <meta property="og:title" content="Title Here" /> -->
+		<!-- <meta property="og:url" content="http://www.example.com/" /> -->
+		<!-- <meta property="og:image" content="http://example.com/image.jpg" /> -->
+		<!-- <meta property="og:description" content="Description Here" /> -->
+		<script>
+			var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
+			document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+		</script>
+		<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
+		<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
+		<script>
+			var wx1 = wx
+		</script>
+	</head>
+	<body>
+		<noscript>
+			<strong>Please enable JavaScript to continue.</strong>
+		</noscript>
+		<div id="app"></div>
+		<!-- built files will be auto injected -->
+	</body>
+</html>

+ 63 - 0
main.js

@@ -0,0 +1,63 @@
+import App from './App'
+
+// #ifndef VUE3
+import Vue from 'vue'
+import './uni.promisify.adaptor'
+import store from './store'
+import uView from "uview-ui";
+import amap from './common/amap-wx.js';
+import url from "./common/url.js"
+Vue.use(uView);
+
+Vue.prototype.$store = store
+Vue.prototype.$islogin = function() {
+	if (!uni.getStorageSync("token")) {
+		uni.showToast({
+			icon: "none",
+			title: "请登录"
+		})
+		return true
+	} else {
+		return false
+	}
+}
+Vue.prototype.$amapPlugin = new amap.AMapWX({
+	key: '537ed0efe961d9a3b90d9a2d2f775e1f'
+});
+Vue.prototype.$url = url
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+	store,
+	...App
+})
+// #ifdef MP-WEIXIN
+import WebIM from "./common/wss.js"
+Vue.use(WebIM, app)
+// #endif
+
+// http拦截器,此为需要加入的内容,如果不是写在common目录,请自行修改引入路径
+import httpInterceptor from '@/common/http.interceptor.js'
+// 这里需要写在最后,是为了等Vue创建对象完成,引入"app"对象(也即页面的"this"实例)
+Vue.use(httpInterceptor, app)
+
+app.$mount()
+// #endif
+
+
+
+
+
+
+
+
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+	const app = createSSRApp(App)
+	return {
+		app
+	}
+}
+// #endif

+ 84 - 0
manifest.json

@@ -0,0 +1,84 @@
+{
+    "name" : "房探花用户端",
+    "appid" : "__UNI__7F76806",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wxbe9a8d39fe7effdf",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true,
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "获取周边福利"
+            }
+        },
+        "requiredPrivateInfos" : [ "getLocation" ]
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2",
+    "h5" : {
+        "router" : {
+            "base" : "./"
+        },
+        "template" : "index.html"
+    }
+}

+ 16 - 0
package.json

@@ -0,0 +1,16 @@
+{
+    "id": "echarts-uniapp",
+    "name": "echarts for uniapp",
+    "version": "0.0.1",
+    "description": "echarts适配uni-app",
+    "keywords": [
+        "echarts",
+        "图表"
+    ],
+    "dcloudext": {
+        "category": [
+            "前端组件",
+            "通用组件"
+        ]
+    }
+}

+ 323 - 0
pages.json

@@ -0,0 +1,323 @@
+{
+
+	"easycom": {
+		"autoscan": true,
+		"custom": {
+			"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue",
+			"^gf-(.*)": "@/components/gf-$1/gf-$1.vue"
+		}
+	},
+
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		// #ifdef H5
+		{
+			"path": "pages/h5/rizhao",
+			"style": {
+				"navigationBarTitleText": "日照测评",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/h5/yifang",
+			"style": {
+				"navigationBarTitleText": "一房一价",
+				"navigationStyle": "custom"
+			}
+		},
+		// #endif
+		// #ifdef MP-WEIXIN
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "首页",
+				"navigationStyle": "custom",
+				"navigationBarTextStyle": "white"
+			}
+		},
+		{
+			"path": "pages/index/search",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/video",
+			"style": {
+				"navigationBarTitleText": "视频",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/info",
+			"style": {
+				"navigationBarTitleText": "详情",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/chat",
+			"style": {
+				"navigationBarTitleText": "在线咨询",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/houses-info",
+			"style": {
+				"navigationBarTitleText": "详情",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/pk-info",
+			"style": {
+				"navigationBarTitleText": "对比详情",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/pk-search",
+			"style": {
+				"navigationBarTitleText": "楼盘对比",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/pk-list",
+			"style": {
+				"navigationBarTitleText": "楼盘对比",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/broker",
+			"style": {
+				"navigationBarTitleText": "经纪人",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1F7EFF"
+			}
+		},
+		{
+			"path": "pages/index/news-info",
+			"style": {
+				"navigationBarTitleText": "头条详情",
+				"navigationBarTextStyle": "black",
+				"navigationBarBackgroundColor": "#fff"
+			}
+		},
+		{
+			"path": "pages/index/news",
+			"style": {
+				"navigationBarTitleText": "楼市头条",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/index/fuli-info",
+			"style": {
+				"navigationBarTitleText": "福利详情",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/index/fuli",
+			"style": {
+				"navigationBarTitleText": "平台福利",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/video/video",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationBarTextStyle": "white",
+				"navigationStyle": "custom",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/video/info",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/baobei/baobei",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationBarTextStyle": "white",
+				"navigationStyle": "custom",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/mine/userinfo",
+			"style": {
+				"navigationBarTitleText": "个人信息",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/mine/baobei-list",
+			"style": {
+				"navigationBarTitleText": "我的报备",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/mine/baobei-info",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/mine/xieyi",
+			"style": {
+				"navigationBarTitleText": "协议",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/mine/setting",
+			"style": {
+				"navigationBarTitleText": "系统设置",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/mine/yongjin",
+			"style": {
+				"navigationBarTitleText": "佣金明细",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		{
+			"path": "pages/mine/mine",
+			"style": {
+				"navigationBarTitleText": "我的",
+				"navigationBarTextStyle": "white",
+				"navigationBarBackgroundColor": "#1E7DFF"
+			}
+		},
+		// #endif
+		{
+			"path": "pages/h5/rizhao",
+			"style": {
+				"navigationBarTitleText": "日照测评",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/h5/yifang",
+			"style": {
+				"navigationBarTitleText": "一房一价",
+				"navigationStyle": "custom"
+			}
+		}
+	]
+	// #ifdef MP-WEIXIN
+	,"subPackages": [{
+		"root": "pagesA",
+		"pages": [{
+				"path": "index/allHouses",
+				"style": {
+					"navigationBarTitleText": "全部楼盘",
+					"navigationBarTextStyle": "white",
+					"navigationBarBackgroundColor": "#1E7DFF"
+				}
+			},
+			{
+				"path": "index/dataCenter",
+				"style": {
+					"navigationBarTitleText": "数据中心",
+					"navigationBarTextStyle": "white",
+					"navigationBarBackgroundColor": "#1E7DFF"
+				}
+			},
+			{
+				"path": "index/newHouses",
+				"style": {
+					"navigationBarTitleText": "新房底价",
+					"navigationBarTextStyle": "white",
+					"navigationBarBackgroundColor": "#1E7DFF"
+				}
+			},
+			{
+				"path": "index/counter",
+				"style": {
+					"navigationBarTitleText": "房贷计算器",
+					"navigationBarTextStyle": "white",
+					"navigationBarBackgroundColor": "#1F7EFF"
+				}
+			},
+			{
+				"path": "index/counter-info",
+				"style": {
+					"navigationBarTitleText": "还款详情",
+					"navigationBarTextStyle": "white",
+					"navigationBarBackgroundColor": "#1F7EFF"
+				}
+			}
+		]
+	}]
+	// #endif
+	,"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8"
+	},
+	"uniIdRouter": {},
+	"tabBar": {
+		// #ifdef MP-WEIXIN
+		"selectedColor": "#1E7DFF",
+		"color": "#7C7C7C",
+		"list": [{
+				"iconPath": "static/images/index.png",
+				"pagePath": "pages/index/index",
+				"selectedIconPath": "static/images/index1.png",
+				"text": "首页"
+			},
+			{
+				"iconPath": "static/images/video.png",
+				"pagePath": "pages/video/video",
+				"selectedIconPath": "static/images/video1.png",
+				"text": "视频"
+			},
+			{
+				"iconPath": "static/images/baobei.png",
+				"pagePath": "pages/baobei/baobei",
+				"selectedIconPath": "static/images/baobei1.png",
+				"text": "客户报备"
+			},
+			{
+				"iconPath": "static/images/mine.png",
+				"pagePath": "pages/mine/mine",
+				"selectedIconPath": "static/images/mine1.png",
+				"text": "我的"
+			}
+		]
+		// #endif
+	}
+}

+ 435 - 0
pages/baobei/baobei.vue

@@ -0,0 +1,435 @@
+<template>
+	<view class="baobei">
+		<u-navbar :title="config.store_title" title-color="#fff" :background="{background:'#1F7EFF'}" :border-bottom="false" :isBack="false">
+			<view class="u-flex nav-left" slot="left">
+				<u-icon name="map-fill" size="34" color="#fff"></u-icon>
+				<text class="text">{{city || '定位中'}}</text>
+			</view>
+		</u-navbar>
+		<view class="" style="padding: 0 24rpx;">
+			<view class="broker-title">
+				基本信息
+			</view>
+			<view class="broker-row u-flex" @click="quyushow = true">
+				<view class="broker-row-left u-flex">
+					<text>意向区域</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="text" placeholder="请选择您的意向区域" class="input-right" :disabled="true" v-model="quyuname">
+				<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+			</view>
+			<view class="broker-row u-flex" @click="loupanshow = true">
+				<view class="broker-row-left u-flex">
+					<text>意向项目</text>
+				</view>
+				<input type="text" placeholder="请选择您的意向楼盘" class="input-right" :disabled="true" v-model="loupanname">
+				<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>客户姓名</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="text" placeholder="请输入" class="input-right" v-model="kehuname">
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>联系方式</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="number" placeholder="请输入" class="input-right" v-model="kehutel">
+			</view>
+		</view>
+		<u-gap bg-color="#F5F5F5" height="20"></u-gap>
+		<view class="" style="padding: 0 24rpx;">
+			<view class="broker-title">
+				其他信息
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>带看人姓名</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="text" placeholder="请输入" class="input-right" v-model="name">
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>联系方式</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="number" placeholder="请输入" class="input-right" v-model="tel">
+			</view>
+			<view class="broker-row u-flex" v-if="certification_type == 1">
+				<view class="broker-row-left u-flex">
+					<text>中介门店</text>
+				</view>
+				<input type="text" placeholder="请输入" class="input-right" v-model="shop">
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>预约看房日期</text>
+				</view>
+				<biaofunDatetimePicker ref="biaofunDatetimePicker" placeholder="请选择看房日期" class="input-right" :start="$u.timeFormat(new Date().getTime(), 'yyyy-mm-dd hh:MM')" @change="changetime"></biaofunDatetimePicker>
+				<!-- <input type="text" placeholder="请选择看房日期" class="input-right" :disabled="true" v-model="time"> -->
+				<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+			</view>
+		</view>
+		<view class="baobei-down u-flex-col u-col-center">
+			<view class="xieyi u-flex u-row-center">
+				<u-checkbox v-model="xieyi" active-color="#1F7EFF" size="26"></u-checkbox>
+				<view class="text" @click="xieyi = !xieyi">
+					勾选并同意<text @click.stop="totext(3)">《报备活动规则》</text>
+				</view>
+			</view>
+			<view class="tijiao" @click="save">
+				确认
+			</view>
+		</view>
+		<u-popup v-model="show" :maskCloseAble="false" mode="center" border-radius="20">
+			<view class="baobei-popup u-flex-col u-col-center">
+				<image src="../../static/images/baobei-img.png" class="image" mode=""></image>
+				<view class="baobei-tips u-flex-col u-col-center">
+					<text>您还未进行信息认证</text>
+					<text>请您前去认证</text>
+				</view>
+				<view class="baobei-btn u-flex u-row-center">
+					<!-- <text @click="toshenqing(1)">全民经纪人</text> -->
+					<text @click="toshenqing(2)">中介经纪人</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-popup v-model="showover" :closeable="true" mode="center" border-radius="20">
+			<view class="over-popup u-flex-col u-col-center u-row-center">
+				<u-icon name="checkmark-circle-fill" color="#14CB30" size="168"></u-icon>
+				<view class="over-text u-flex-col u-col-center">
+					<text>已提交客户资料,开发商审核中</text>
+					<text>防截客锁定中,请耐心等待</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-select v-model="quyushow" :list="quyulist" label-name="name" value-name="id" @confirm="changequyu"></u-select>
+		<u-select v-model="loupanshow" :list="loupanlist" label-name="name" value-name="id" @confirm="changeloupan"></u-select>
+	</view>
+</template>
+
+<script>
+	import { mapState } from "vuex"
+	import biaofunDatetimePicker from "../../components/biaofun-datetime-picker/biaofun-datetime-picker.vue"
+	export default {
+		data() {
+			return {
+				show: false,
+				xieyi: false,
+				showover: false,
+
+				quyulist: [],
+				quyushow: false,
+				quyuname: '',
+				quyuid: '',
+
+				loupanlist: [],
+				loupanshow: false,
+				loupanname: '',
+				loupanid: '',
+
+				kehuname: '',
+				kehutel: '',
+				name: '',
+				tel: '',
+				shop: '',
+				time: '',
+				certification_type:''
+			}
+		},
+		onLoad() {
+			this.getquyu()
+		},
+		onShow() {
+			uni.setNavigationBarTitle({
+				title: this.config.store_title
+			})
+			if (this.$islogin()) {
+				this.show = true
+			} else {
+				this.getuser()
+			}
+		},
+		computed: {
+			...mapState(['config','city'])
+		},
+		components: {
+			biaofunDatetimePicker
+		},
+		methods: {
+			totext(type){
+				uni.navigateTo({
+					url:"/pages/mine/xieyi?type=" + type
+				})
+			},
+			save() {
+				if (!this.quyuname) {
+					this.$u.toast('请选择您的意向区域')
+					return
+				}
+				if (!this.loupanname) {
+					this.$u.toast('请选择您的意向楼盘')
+					return
+				}
+				if (!this.kehuname) {
+					this.$u.toast('请输入客户姓名')
+					return
+				}
+				if (!this.$u.test.mobile(this.kehutel)) {
+					this.$u.toast('请输入正确的客户联系方式')
+					return
+				}
+				if (!this.name) {
+					this.$u.toast('请输入带看人姓名')
+					return
+				}
+				if (!this.$u.test.mobile(this.tel)) {
+					this.$u.toast('请输入正确的带看人联系方式')
+					return
+				}
+				if (!this.shop && this.certification_type == 1) {
+					this.$u.toast('请输入中介门店')
+					return
+				}
+				if (!this.time) {
+					this.$u.toast('请选择看房日期')
+					return
+				}
+				if (this.time < this.$u.timeFormat(new Date().getTime(), 'yyyy-mm-dd hh:MM:ss')) {
+					this.$u.toast('看房日期不得小于当前时间')
+					return
+				}
+				if (!this.xieyi) {
+					this.$u.toast("请勾选协议")
+					return
+				}
+				this.$u.post('/api/Report/submit_report', {
+					area_id: this.quyuid,
+					property_id: this.loupanid,
+					client_name: this.kehuname,
+					client_phone: this.kehutel,
+					report_name: this.name,
+					report_phone: this.tel,
+					shop: this.shop,
+					time: this.time
+				}).then(res => {
+					if (res.code == 1) {
+						this.showover = true
+						this.quyuid = ''
+						this.quyuname = ''
+						this.loupanid = ''
+						this.loupanname = ''
+						this.kehuname = ''
+						this.kehutel = ''
+						this.name = ''
+						this.tel = ''
+						this.shop = ''
+						this.time = ''
+						this.xieyi = false
+						this.$refs.biaofunDatetimePicker.clearTime()
+					} else {
+						this.$u.toast(res.msg)
+					}
+				})
+			},
+			changetime(e) {
+				this.time = `${e.f1} ${e.f8}:00`
+			},
+			changeloupan(e) {
+				this.loupanid = e[0].value
+				this.loupanname = e[0].label
+			},
+			changequyu(e) {
+				this.quyuid = e[0].value
+				this.quyuname = e[0].label
+				this.$u.post('/api/Report/area_property_list', {
+					area_id: this.quyuid
+				}).then(res => {
+					this.loupanlist = res.data
+				})
+			},
+			getquyu() {
+				this.$u.post('/api/Index/area_list').then(res => {
+					this.quyulist = res.data
+				})
+			},
+			toshenqing(type) {
+				if (this.$islogin()) return
+				uni.navigateTo({
+					url: "/pages/index/broker?type=" + type
+				})
+			},
+			getuser() {
+				uni.showLoading({
+					mask: true,
+					title: "请稍后"
+				})
+				this.$u.post('/api/Member/member_info').then(res => {
+					this.certification_type = res.data.certification_type
+					if (res.data.certification_type == 0) {
+						this.show = true
+					} else {
+						this.show = false
+					}
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	.baobei {
+		.over-popup {
+			width: 650rpx;
+			height: 564rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+
+			.over-text {
+				margin-top: 32rpx;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+			}
+		}
+
+		.baobei-down {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			width: 750rpx;
+
+			.tijiao {
+				margin: 30rpx auto;
+				width: 702rpx;
+				line-height: 82rpx;
+				background: #1F7EFF;
+				border-radius: 8rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+
+			.xieyi {
+				.text {
+					font-size: 20rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+
+					text {
+						color: #1F7EFF;
+					}
+				}
+			}
+		}
+
+		.broker-row {
+			height: 82rpx;
+			border-bottom: 2rpx solid #F5F5F5;
+
+			.input-right {
+				flex: 1;
+				font-size: 24rpx;
+			}
+
+			.broker-row-left {
+				width: 252rpx;
+
+				text:first-child {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+				}
+
+				text:nth-child(2) {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #F83838;
+				}
+			}
+		}
+
+		.broker-title {
+			padding: 24rpx 0;
+			font-size: 28rpx;
+			font-family: PingFangSC-Medium, PingFang SC;
+			font-weight: 500;
+			color: #333333;
+		}
+
+		.baobei-popup {
+			width: 650rpx;
+			height: 806rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			padding-top: 126rpx;
+
+			.baobei-btn {
+				width: 100%;
+				padding: 0 44rpx;
+
+				text:first-child {
+					width: 262rpx;
+					line-height: 100rpx;
+					background: #FFA120;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+
+				text:last-child {
+					width: 262rpx;
+					line-height: 100rpx;
+					background: #1F7EFF;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+			}
+
+			.baobei-tips {
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #C5C5C5;
+				margin-bottom: 98rpx;
+			}
+
+			.image {
+				width: 214rpx;
+				height: 242rpx;
+				margin-bottom: 72rpx;
+			}
+		}
+
+		.nav-left {
+			padding: 0 20rpx;
+
+			.text {
+				font-size: 32rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #FFFFFF;
+				margin-left: 5rpx;
+			}
+		}
+	}
+
+</style>

+ 875 - 0
pages/h5/rizhao.vue

@@ -0,0 +1,875 @@
+<template>
+	<view class="rizhao">
+		<!-- #ifdef MP-WEIXIN -->
+		<!-- <web-view src="https://fangcan.hdlkeji.com/dist/index.html#/"></web-view> -->
+		<web-view :src="`${url}/dist/index.html#/pages/h5/rizhao?id=${id}&token=${token}`"></web-view>
+		<!-- #endif -->
+		<!-- #ifdef H5 -->
+		<view class="" id="objContaier" :style="{width:conWidth + 'px',height:conHeight + 'px'}"></view>
+		<view class="rizhao-louhao u-flex u-flex-wrap">
+			<view class="u-flex u-col-center u-row-center louhao-active" :style="{borderColor:louhao == index ? '#1F7EFF' : '#999999'}" v-for="(item,index) in louhaoList" :key="index" @click="changelou(index)">
+				<text class="text1" :style="{color:louhao == index ? '#1F7EFF' : '#999999'}">{{item.build_num}}#</text>
+				<text class="text2" v-if="louhao == index">✓</text>
+			</view>
+			<!-- <view class="u-flex u-col-center u-row-center louhao-active" @click="zhankai()">
+				<text class="text1">展开</text>
+			</view> -->
+		</view>
+		<view class="louceng-fangxiang u-flex u-row-between u-flex-wrap">
+			<text :class="{text:fangxiang == index}" v-for="(item,index) in fangxianglist" :key="index" @click="changefangxiang(index)">{{item.orientation}}</text>
+		</view>
+		<view class="louceng-jieqi">
+			<view class="jieqi-box u-flex u-row-between u-flex-wrap">
+				<text v-for="(a,b) in jieqilist" :class="b == jieqi ? 'text' : ''" :key="b" @click="changejieqi(b)">{{a.solar_terms}}</text>
+			</view>
+		</view>
+		<view class="time-box u-flex u-flex-wrap">
+			<view class="u-flex time-item" v-for="(a,b) in timelist" :key="b">
+				<text :style="{background:a.color}"></text>
+				<text>{{a.name}}</text>
+			</view>
+		</view>
+		<scroll-view class="louceng-time-table" scroll-x="true" v-if="list.length > 0">
+			<view class="u-flex-col" :style="{width:getwidth() + 'rpx'}">
+				<view class="table-header">
+					{{louhaoList[louhao].build_num}}#{{fangxianglist[fangxiang] ? fangxianglist[fangxiang].orientation : ''}}{{jieqilist[jieqi] ? jieqilist[jieqi].solar_terms : ''}}
+				</view>
+				<view class="body-header u-flex">
+					<view class="header-title u-flex-col u-row-between">
+						<text class="text1">单元</text>
+						<text class="text2">楼层</text>
+						<image src="../../static/images/xiegang.png" mode=""></image>
+					</view>
+					<view class="danyuan-header" v-for="(a,b) in list[0].unit_info" :key="b">
+						<view class="danyuan-title">
+							第{{a.unit}}单元
+						</view>
+						<view class="u-flex">
+							<view class="house-title" v-for="(c,d) in a.house_info" :key="d">
+								{{d + 1}}室
+							</view>
+						</view>
+					</view>
+				</view>
+				<view class="body-item" v-for="(item,index) in list" :key="index">
+					<view class="item-title">{{item.floor}}F</view>
+					<view class="danyuan-item" v-for="(a,b) in item.unit_info" :key="b">
+						<view class="house-item" v-for="(c,d) in a.house_info" :key="d" :style="{background:getcolor(c)}">
+							<view class="house-room u-flex">
+								<text>{{c.room_num}}</text>
+								<text>室</text>
+							</view>
+							<view class="house-allprice">
+								<text>{{c.sunlight_duration}}</text>
+								<text></text>
+							</view>
+							<view class="house-price">
+								<text></text>
+								<text>{{c.sunlight_time}}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+		<view class="" style="padding: 24rpx;">
+			<view v-html="sunshine"></view>
+		</view>
+		<view class="u-flex-col" style="font-size: 28rpx;color: #9DA3AB;padding: 0 24rpx;">
+			<!-- <view class="u-flex" style="margin-bottom: 10rpx;" @click="showmianze1 = true">
+				<text style="margin-right: 10rpx;">日照说明</text>
+				<u-icon name="info-circle-fill"></u-icon>
+			</view> -->
+			<view class="u-flex" style="margin-bottom: 10rpx;" @click="showmianze2 = true">
+				<text style="margin-right: 10rpx;">免责声明</text>
+				<u-icon name="info-circle-fill"></u-icon>
+			</view>
+		</view>
+		<view class="" style="height:170rpx"></view>
+		<view class="rizhao-btn u-flex u-row-between">
+			<text @click="showtocall = true">隐私电话</text>
+			<text @click="tochat">在线咨询</text>
+		</view>
+		<u-popup v-model="showtocall" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">您是否进行呼叫?</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showtocall = false">取消</text>
+					<text @click="tocell">确定</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-popup v-model="showlogin" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">你目前处于未登录状态请前往登录</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showlogin = false">取消</text>
+					<text @click="tologin">确定</text>
+				</view>
+			</view>
+		</u-popup>
+		<!-- <u-popup v-model="showmianze1" mode="bottom" height="900rpx">
+			<view class="" style="padding: 24rpx;">
+				<view v-html="sunshine"></view>
+			</view>
+		</u-popup> -->
+		<u-popup v-model="showmianze2" mode="bottom" height="900rpx">
+			<view class="" style="padding: 24rpx;">
+				<view v-html="disclaimer"></view>
+			</view>
+		</u-popup>
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	var modelUrl = '' //'https://fangcan.hdlkeji.com/dist/fixedly/兔女郎.obj'
+	var modelUrl1 = '' //'https://fangcan.hdlkeji.com/dist/fixedly/兔女郎.mtl'
+	var container
+	var rotating = true;
+
+	import {
+		OBJLoader
+	} from '../../platforms/h5/common/obj/OBJLoader.js'
+	import {
+		MTLLoader
+	} from '../../platforms/h5/common/obj/MTLLoader.js'
+	import {
+		OrbitControls
+	} from '../../platforms/h5/common/OrbitControls.js';
+	import * as THREE from '../../platforms/h5/common/three.js';
+
+	// 材质模型加载器,模型在服务器上
+	const mtlLoader = new MTLLoader()
+	const objLoader = new OBJLoader();
+	export default {
+		data() {
+			return {
+				scene: undefined,
+				light: undefined,
+				camera: undefined,
+				controls: undefined,
+				renderer: undefined,
+				conWidth: uni.getSystemInfoSync().windowWidth,
+				conHeight: uni.getSystemInfoSync().windowWidth - 50,
+
+				showmianze1: false,
+				showmianze2: false,
+				showtocall: false,
+				showlogin: false,
+				id: '',
+				louhaoList: [],
+				louhao: 0,
+				jieqilist: [],
+				jieqi: 0,
+				fangxianglist: [],
+				fangxiang: 0,
+				detail: {},
+				list: [],
+				timelist: [{
+					name: '8h=日照',
+					color: '#61C86A',
+					value: ['08:00']
+				}, {
+					name: '8h>日照>=6h',
+					color: '#9BD56D',
+					value: ['08:00', '06:00']
+				}, {
+					name: '6h>日照>=4h',
+					color: '#E7E2A6',
+					value: ['06:00', '04:00']
+				}, {
+					name: '4h>日照>=2h',
+					color: '#F4CE9E',
+					value: ['04:00', '02:00']
+				}, {
+					name: '2h>日照>=0h',
+					color: '#F5BBB1',
+					value: ['02:00', '00:00']
+				}],
+				sunshine: '',
+				disclaimer: ''
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			uni.setStorageSync("token", option.token || '')
+			this.getdata()
+			this.getlist()
+			this.getconfig()
+		},
+		onShow() {
+
+		},
+		mounted() {
+
+		},
+		methods: {
+			getconfig() {
+				this.$u.post('/api/Index/platform_config').then(res => {
+					const regex = new RegExp('<img', 'gi')
+					this.sunshine = res.data.sunshine.replace(regex, `<img style="max-width: 100%; height: auto"`)
+					this.disclaimer = res.data.disclaimer.replace(regex, `<img style="max-width: 100%; height: auto"`)
+				})
+			},
+			changejieqi(index) {
+				this.jieqi = index
+				this.getdetail()
+			},
+			changefangxiang(index) {
+				this.fangxiang = index
+				this.getdetail()
+			},
+			getcolor(item) {
+				for (let i = 0; i < this.timelist.length; i++) {
+					if (this.timelist[i].value[0] == item.sunlight_duration) {
+						return this.timelist[i].color
+					}
+					if (this.timelist[i].value[1]) {
+						if (this.timelist[i].value[0] > item.sunlight_duration && this.timelist[i].value[1] <= item.sunlight_duration) {
+							return this.timelist[i].color
+						}
+					}
+				}
+			},
+			getwidth() {
+				var width = 0
+				this.list.forEach((item, index) => {
+					if (index == 0) {
+						width = width + 104
+						item.unit_info.forEach(val => {
+							width = width + val.house_info.length * 160
+						})
+					}
+				})
+				return width
+			},
+			getlist() {
+				this.$u.get('/api/Property/sunshine_info', {
+					id: this.id
+				}).then(res => {
+					this.louhaoList = res.data.build_info
+					this.jieqilist = res.data.solar_terms_info
+					this.fangxianglist = res.data.orientation_info
+					this.getdetail()
+				})
+			},
+			getdetail() {
+				this.$u.get('/api/Property/sunshine_detail', {
+					id: this.id,
+					build_num: this.louhaoList[this.louhao] ? this.louhaoList[this.louhao].build_num : '',
+					orientation: this.fangxianglist[this.fangxiang] ? this.fangxianglist[this.fangxiang].orientation : '',
+					solar_terms: this.jieqilist[this.jieqi] ? this.jieqilist[this.jieqi].solar_terms : ''
+				}).then(res => {
+					this.list = res.data
+				})
+			},
+			getdata() {
+				this.$u.post('/api/Property/property_detail', {
+					id: this.id
+				}).then(res => {
+					this.detail = res.data
+					modelUrl = res.data.abbr_url
+					uni.setNavigationBarTitle({
+						title: res.data.name
+					})
+					if (modelUrl) {
+						this.$nextTick(() => {
+							this.setglb()
+							this.animate();
+						})
+					}
+				})
+			},
+			tologin() {
+				wx1.miniProgram.switchTab({
+					url: "/pages/mine/mine"
+				})
+			},
+			tochat() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 2
+					})
+					wx1.miniProgram.navigateTo({
+						url: "/pages/index/chat?hx_username=" + this.detail.hx_username + "&worker_id=" + this.detail.worker_id
+					})
+				} else {
+					this.showlogin = true
+				}
+			},
+			tocell() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 1
+					})
+					window.location.href = `tel:${this.detail.privacy_phone}`
+				} else {
+					this.showlogin = true
+				}
+			},
+			zhankai() {
+
+			},
+			changelou(index) {
+				this.louhao = index
+				this.getdetail()
+			},
+			animate() {
+				requestAnimationFrame(this.animate);
+				if (rotating) {
+					this.scene.rotation.y += -0.0008;
+				} else {
+					this.scene.rotation.y = this.scene.rotation.y;
+				}
+				this.renderer.render(this.scene, this.camera);
+			},
+			init() {
+				// 创建场景
+				this.scene = new THREE.Scene()
+				this.scene.background = new THREE.Color('#B9D3FF')
+				// 渲染
+				container = document.getElementById('objContaier')
+				this.renderer = new THREE.WebGLRenderer({
+					antialias: false,
+					alpha: true
+				})
+				this.renderer.outputEncoding = THREE.sRGBEncoding;
+				this.renderer.setPixelRatio(window.devicePixelRatio);
+				this.renderer.setSize(this.conWidth, this.conHeight);
+				container.appendChild(this.renderer.domElement)
+
+				// 相机
+				this.camera = new THREE.PerspectiveCamera(45, this.conWidth / this.conHeight, 1, 1000000000)
+				//设置视角离原点的位置(眼睛距离模型的距离) 
+
+
+
+				if (modelUrl1) {
+					mtlLoader.load(modelUrl1, (mtl) => {
+						// console.log(JSON.parse(JSON.stringify(mtl)));
+						mtl.preload()
+						for (const material of Object.values(mtl.materials)) {
+							material.side = THREE.DoubleSide;
+						}
+						objLoader.setMaterials(mtl)
+						this.setobj()
+					})
+				} else {
+					this.setobj()
+				}
+			},
+			setobj() {
+				objLoader.load(modelUrl, (root) => {
+					// console.log(JSON.parse(JSON.stringify(root.children)));
+					// 设置旋转中心点
+
+					const box = new THREE.Box3().setFromObject(root);
+					const boxSize = box.getSize(new THREE.Vector3()).length();
+					const boxCenter = box.getCenter(new THREE.Vector3());
+					root.position.x = root.position.x - boxCenter.x
+					root.position.y = root.position.y - boxCenter.y
+					root.position.z = root.position.z - boxCenter.z
+					var num = boxSize * 0.6
+					this.root = root
+					this.camera.position.set(num, num, num)
+					this.camera.lookAt(new THREE.Vector3(0, 0, 0))
+					// 轨道控制
+					this.controls = new OrbitControls(this.camera, this.renderer.domElement)
+					this.controls.target.set(0, 0, 0);
+					this.controls.update();
+
+					// 光源
+					const skyColor = 0xB09595;
+					const groundColor = 0x594C4B;
+					const intensity = 0.5;
+					const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
+					this.scene.add(light);
+					// 直射光
+					const pivotPoint = window.pivotPoint = new THREE.Object3D();
+					const dcolor = 0xffffff;
+					const dlight = new THREE.DirectionalLight(dcolor);
+					// 设置平行光源的产生阴影的范围参数
+					dlight.shadow.camera.near = 0;
+					dlight.shadow.camera.far = 50000;
+					dlight.shadow.camera.left = -20000;
+					dlight.shadow.camera.right = 20000;
+					dlight.shadow.camera.top = 20000;
+					dlight.shadow.camera.bottom = -20000;
+					dlight.distance = 1110;
+					dlight.intensity = 0.1;
+					dlight.shadow.mapSize.height = 2560;
+					dlight.shadow.mapSize.width = 2560;
+					dlight.castShadow = true;
+					let sphereLight = new THREE.SphereGeometry(5, 32, 32); // 创建一个球形mesh,用来存放点光源
+					let sphereLightMaterial = new THREE.MeshBasicMaterial({
+						color: 0xac6c25
+					});
+					let sphereLightMesh = window.sphereLightMesh = new THREE.Mesh(sphereLight, sphereLightMaterial);
+					sphereLightMesh.castShadow = false; //将这个球形mesh的产生阴影和接收阴影参数打开
+					sphereLightMesh.receiveShadow = false; //将这个球形mesh的产生阴影和接收阴影参数打开
+					sphereLightMesh.position.set(-25000, 0, 0); //  设置此球形mesh的位置
+
+					pivotPoint.add(sphereLightMesh); // 将球形mesh添加到点光源里
+					pivotPoint.add(dlight); // 将平行光源添加到点光源里
+					dlight.position.copy(sphereLightMesh.position);
+					// dlight.position.set(num, num, num);
+					this.scene.add(dlight);
+					// 环境光
+					// const ambient = new THREE.AmbientLight(0x333333, 1)
+					// this.scene.add(ambient)
+					// 点光源
+					// const dian = new THREE.PointLight(0x333333, 1, 100);
+					// dian.position.set(num, num, num);
+					// this.scene.add(dian);
+
+					// 辅助坐标线
+					// var axesHelper = new THREE.AxesHelper(150);
+					// this.scene.add(axesHelper);
+					this.scene.add(this.root);
+					// console.log(boxSize, boxCenter);
+				}, (xhr) => {
+					// console.log(xhr.loaded, xhr.total, 'objLoader');
+					// console.log(xhr);
+					// console.log((xhr.loaded / xhr.total * 100) + '% loaded');
+
+				});
+			},
+			setglb() {
+
+				this.init();
+
+				var modelBorder = document.getElementById("objContaier");
+				modelBorder.addEventListener("mouseenter", function(event) {
+					rotating = false;
+				});
+				modelBorder.addEventListener("mouseleave", function(event) {
+					rotating = true;
+				});
+				modelBorder.addEventListener('touchmove', function(e) {
+					rotating = false;
+				}, false);
+				modelBorder.addEventListener('touchstart', function(e) {
+					rotating = false;
+				}, false);
+				modelBorder.addEventListener('touchend', function(e) {
+					rotating = true;
+				}, false);
+
+			}
+		}
+	}
+	// #endif
+	// #ifdef MP-WEIXIN
+	export default {
+		data() {
+			return {
+				id: '',
+				token: uni.getStorageSync("token") || '',
+				url: this.$url
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+		},
+		methods: {
+
+		}
+	}
+	// #endif	
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F5F5F5;
+	}
+
+	.rizhao {
+		.tocall-box {
+			position: relative;
+
+			.tocall-img {
+				width: 650rpx;
+				height: 476rpx;
+			}
+
+			.tocall-btn {
+				position: absolute;
+				bottom: 70rpx;
+				left: 0;
+				width: 100%;
+				padding: 0 48rpx;
+
+				text:first-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #FFA120;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+
+				text:last-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #1F7EFF;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+					text-decoration: none;
+				}
+			}
+
+			.tocall-text {
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+				position: absolute;
+				top: 244rpx;
+				left: 0;
+				z-index: 10;
+				width: 100%;
+			}
+		}
+
+		.rizhao-btn {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			z-index: 10;
+			width: 750rpx;
+			height: 166rpx;
+			background: #FFFFFF;
+			padding: 0 24rpx 54rpx 24rpx;
+
+			text:first-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #FFA120;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+				text-decoration: none;
+			}
+
+			text:last-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #1F7EFF;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+		}
+
+		.louceng-time-table {
+			width: 702rpx;
+			margin: 20rpx auto;
+			background-color: #fff;
+
+			.body-header {
+				height: 124rpx;
+				white-space: nowrap;
+				border-bottom: 1rpx solid #CCCCCC;
+
+				.danyuan-header {
+
+					.danyuan-title {
+						height: 58rpx;
+						line-height: 58rpx;
+						text-align: center;
+						border-bottom: 1rpx solid #CCCCCC;
+						border-right: 1rpx solid #CCCCCC;
+					}
+
+					.house-title {
+						width: 160rpx;
+						height: 64rpx;
+						line-height: 64rpx;
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #333333;
+						text-align: center;
+						border-right: 1rpx solid #CCCCCC;
+					}
+				}
+
+				.header-title {
+					width: 104rpx;
+					height: 124rpx;
+					border-right: 1rpx solid #CCCCCC;
+					padding: 8rpx 12rpx;
+					position: relative;
+
+					image {
+						position: absolute;
+						top: 0;
+						left: 0;
+						width: 100%;
+						height: 100%;
+					}
+
+					text {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #333333;
+					}
+
+					.text1 {
+						width: 100%;
+						text-align: right;
+					}
+
+					.text2 {
+						width: 100%;
+					}
+				}
+			}
+
+			.body-item {
+				height: 160rpx;
+				white-space: nowrap;
+				border-bottom: 1rpx solid #CCCCCC;
+
+				.danyuan-item {
+					display: inline-block;
+					white-space: nowrap;
+					vertical-align: top;
+
+					.house-item {
+						display: inline-flex;
+						flex-direction: column;
+						justify-content: center;
+						width: 160rpx;
+						padding-left: 16rpx;
+						border-right: 1rpx solid #CCCCCC;
+						height: 160rpx;
+
+						.house-mianji {
+							text:first-child {
+								font-size: 24rpx;
+								color: #333333;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+
+						.house-price {
+							text:first-child {
+								font-size: 24rpx;
+								color: #1F7EFF;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+
+						.house-allprice {
+							text:first-child {
+								font-size: 28rpx;
+								color: #333333;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+
+						.house-room {
+							text:first-child {
+								font-size: 24rpx;
+								color: #333333;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+					}
+				}
+
+				.item-title {
+					vertical-align: top;
+					width: 104rpx;
+					text-align: center;
+					line-height: 160rpx;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+					border-right: 1rpx solid #CCCCCC;
+					display: inline-block;
+					min-width: 1rpx;
+				}
+			}
+
+
+			.table-header {
+				width: 100%;
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #333333;
+				line-height: 110rpx;
+				border-bottom: 1rpx solid #CCCCCC;
+
+			}
+		}
+
+		.time-box {
+			width: 702rpx;
+			height: 138rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 24rpx 20rpx 4rpx 20rpx;
+
+			.time-item {
+				margin-bottom: 20rpx;
+
+				text:first-child {
+					width: 32rpx;
+					height: 32rpx;
+					background: #61C86A;
+					border-radius: 8rpx;
+					margin-right: 20rpx;
+				}
+
+				text:last-child {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+					margin-right: 40rpx;
+				}
+			}
+		}
+
+		.louceng-jieqi {
+			background-color: #fff;
+			padding-bottom: 24rpx;
+
+			.jieqi-box {
+				width: 662rpx;
+				height: 164rpx;
+				background: #F5F5F5;
+				border-radius: 20rpx;
+				margin: 0 auto;
+				padding: 24rpx 20rpx 4rpx 20rpx;
+
+				text {
+					margin-bottom: 20rpx;
+					width: 194rpx;
+					line-height: 48rpx;
+					background: #FFFFFF;
+					border-radius: 8rpx;
+					border: 2rpx solid #999999;
+					text-align: center;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+				}
+
+				.text {
+					color: #1F7EFF;
+					border-color: #1F7EFF;
+				}
+			}
+		}
+
+		.louceng-fangxiang {
+			padding: 24rpx 44rpx;
+			background-color: #FFFFFF;
+
+			text {
+				width: 318rpx;
+				line-height: 84rpx;
+				background: #FFFFFF;
+				border-radius: 20rpx;
+				border: 2rpx solid #999999;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+			}
+
+			.text {
+				color: #1F7EFF;
+				border-color: #1F7EFF;
+			}
+		}
+
+		.rizhao-louhao {
+			position: sticky;
+			top: 0;
+			left: 0;
+			z-index: 10;
+			width: 750rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			padding: 0 18rpx 24rpx 18rpx;
+			border-bottom: 2rpx solid #F5F5F5;
+
+			.louhao-active {
+				margin: 0 25rpx;
+				width: 92rpx;
+				height: 40rpx;
+				background: #FFFFFF;
+				border-radius: 8rpx;
+				border: 1rpx solid #999999;
+				position: relative;
+				margin-top: 24rpx;
+
+				.text1 {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+				}
+
+				.text2 {
+					width: 20rpx;
+					line-height: 20rpx;
+					background: #1F7EFF;
+					border-radius: 8rpx 0px 8rpx 0px;
+					text-align: center;
+					position: absolute;
+					bottom: 0;
+					right: 0;
+					z-index: 1;
+					color: #fff;
+					font-size: 18rpx;
+				}
+			}
+		}
+	}
+</style>

+ 811 - 0
pages/h5/yifang.vue

@@ -0,0 +1,811 @@
+<template>
+	<view class="rizhao">
+		<!-- #ifdef MP-WEIXIN -->
+		<web-view :src="`${url}/dist/index.html#/pages/h5/yifang?id=${id}&token=${token}`"></web-view>
+		<!-- <web-view :src="`http://192.168.2.12:8080/?id=${id}&token=${token}`"></web-view> -->
+		<!-- #endif -->
+		<!-- #ifdef H5 -->
+		<view class="objContaier" id="objContaier" :style="{width:conWidth + 'px',height:conHeight + 'px'}"></view>
+		<view class="rizhao-louhao u-flex u-flex-wrap">
+			<view class="u-flex u-col-center u-row-center louhao-active" :style="{borderColor:louhao == index ? '#1F7EFF' : '#999999'}" v-for="(item,index) in louhaoList" :key="index" @click="changelou(index)">
+				<text class="text1" :style="{color:louhao == index ? '#1F7EFF' : '#999999'}">{{item.build_num}}#</text>
+				<text class="text2" v-if="louhao == index">✓</text>
+			</view>
+			<!-- <view class="u-flex u-col-center u-row-center louhao-active" @click="zhankai()">
+				<text class="text1">展开</text>
+			</view> -->
+		</view>
+		<!-- <view class="louceng-fangxiang u-flex u-row-between">
+			<text class="text">南面</text>
+			<text>东西面</text>
+		</view>
+		<view class="louceng-jieqi">
+			<view class="jieqi-box u-flex u-row-between u-flex-wrap">
+				<text v-for="(a,b) in 6" :class="b == 0 ? 'text' : ''" :key="b">大寒</text>
+			</view>
+		</view>
+		<view class="time-box u-flex u-flex-wrap">
+			<view class="u-flex time-item" v-for="(a,b) in 5" :key="b">
+				<text></text>
+				<text>8h=日照</text>
+			</view>
+		</view> -->
+		<scroll-view class="louceng-time-table" scroll-x="true" v-if="list.length > 0">
+			<view class="u-flex-col" :style="{width:getwidth() + 'rpx'}">
+				<view class="table-header">
+					{{louhaoList[louhao].build_num}}#
+				</view>
+				<view class="body-header u-flex">
+					<view class="header-title u-flex-col u-row-between">
+						<text class="text1">单元</text>
+						<text class="text2">楼层</text>
+						<image src="../../static/images/xiegang.png" mode=""></image>
+					</view>
+					<view class="danyuan-header" v-for="(a,b) in list[0].unit_info" :key="b">
+						<view class="danyuan-title">
+							第{{a.unit}}单元
+						</view>
+						<view class="u-flex">
+							<view class="house-title" v-for="(c,d) in a.house_info" :key="d">
+								{{d + 1}}室
+							</view>
+						</view>
+					</view>
+				</view>
+				<view class="body-item" v-for="(item,index) in list" :key="index">
+					<view class="item-title">{{item.floor}}F</view>
+					<view class="danyuan-item" v-for="(a,b) in item.unit_info" :key="b">
+						<view class="house-item" style="background-color: #dafddc;" v-for="(c,d) in a.house_info" :key="d">
+							<view class="house-room u-flex">
+								<text>{{c.room_num}}</text>
+								<text>室</text>
+							</view>
+							<view class="house-allprice">
+								<text>{{(c.record_total_price/10000).toFixed(2)}}</text>
+								<text>万元</text>
+							</view>
+							<view class="house-price">
+								<text>{{c.record_unit_price}}</text>
+								<text>元/㎡</text>
+							</view>
+							<view class="house-mianji">
+								<text>{{c.covered_area}}</text>
+								<text>㎡</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+		<!-- <view class="" v-html="disclaimer"></view> -->
+		<view class="" style="height: 170rpx;"></view>
+		<view class="rizhao-btn u-flex u-row-between">
+			<text @click="showtocall = true">隐私电话</text>
+			<text @click="tochat">在线咨询</text>
+		</view>
+		<u-popup v-model="showtocall" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">您是否进行呼叫?</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showtocall = false">取消</text>
+					<text @click="tocell">确定</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-popup v-model="showlogin" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">你目前处于未登录状态请前往登录</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showlogin = false">取消</text>
+					<text @click="tologin">确定</text>
+				</view>
+			</view>
+		</u-popup>
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	var modelUrl = '' //'https://fangcan.hdlkeji.com/dist/fixedly/兔女郎.obj'
+	var modelUrl1 = '' //'https://fangcan.hdlkeji.com/dist/fixedly/兔女郎.mtl'
+	var container
+	var rotating = true;
+
+	import {
+		OBJLoader
+	} from '../../platforms/h5/common/obj/OBJLoader.js'
+	import {
+		MTLLoader
+	} from '../../platforms/h5/common/obj/MTLLoader.js'
+	import {
+		OrbitControls
+	} from '../../platforms/h5/common/OrbitControls.js';
+	import * as THREE from '../../platforms/h5/common/three.js';
+
+	// 材质模型加载器,模型在服务器上
+	const mtlLoader = new MTLLoader()
+	const objLoader = new OBJLoader();
+	export default {
+		data() {
+			return {
+				scene: undefined,
+				light: undefined,
+				camera: undefined,
+				controls: undefined,
+				renderer: undefined,
+				conWidth: uni.getSystemInfoSync().windowWidth,
+				conHeight: uni.getSystemInfoSync().windowWidth - 50,
+
+
+				showtocall: false,
+
+				id: '',
+				louhaoList: [],
+				louhao: 0,
+				list: [],
+				detail: {},
+				showlogin: false,
+				disclaimer: ''
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			uni.setStorageSync("token", option.token || '')
+			this.getdata()
+			this.getconfig()
+		},
+
+		onShow() {
+
+		},
+		mounted() {
+
+		},
+		methods: {
+			getconfig() {
+				this.$u.post('/api/Index/platform_config').then(res => {
+					const regex = new RegExp('<img', 'gi')
+					this.disclaimer = res.data.disclaimer.replace(regex, `<img style="max-width: 100%; height: auto"`)
+				})
+			},
+			getwidth() {
+				var width = 0
+				this.list.forEach((item, index) => {
+					if (index == 0) {
+						width = width + 104
+						item.unit_info.forEach(val => {
+							width = width + val.house_info.length * 160
+						})
+					}
+				})
+				return width
+			},
+			getlist() {
+				this.$u.post('/api/Property/house_detail', {
+					id: this.id,
+					build_num: this.louhaoList[this.louhao].build_num
+				}).then(res => {
+					this.list = res.data
+				})
+			},
+			getdata() {
+				this.$u.post('/api/Property/property_detail', {
+					id: this.id
+				}).then(res => {
+					this.detail = res.data
+					if (res.data.abbr_url)
+						modelUrl = res.data.abbr_url
+					uni.setNavigationBarTitle({
+						title: res.data.name
+					})
+					if (modelUrl) {
+						this.$nextTick(() => {
+							this.setglb()
+							this.animate();
+						})
+					}
+
+				})
+				this.$u.post('/api/Property/build_info', {
+					id: this.id
+				}).then(res => {
+					this.louhaoList = res.data
+					if (this.louhaoList.length > 0) {
+						this.getlist()
+					}
+				})
+			},
+			tologin() {
+				wx1.miniProgram.switchTab({
+					url: "/pages/mine/mine"
+				})
+			},
+			tochat() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 2
+					})
+					wx1.miniProgram.navigateTo({
+						url: "/pages/index/chat?hx_username=" + this.detail.hx_username + "&worker_id=" + this.detail.worker_id
+					})
+				} else {
+					this.showlogin = true
+				}
+			},
+			tocell() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 1
+					})
+					window.location.href = `tel:${this.detail.privacy_phone}`
+				} else {
+					this.showlogin = true
+				}
+			},
+			zhankai() {
+
+			},
+			changelou(index) {
+				this.louhao = index
+				this.getlist()
+			},
+			animate() {
+				requestAnimationFrame(this.animate);
+				if (rotating) {
+					this.scene.rotation.y += -0.0008;
+				} else {
+					this.scene.rotation.y = this.scene.rotation.y;
+				}
+				this.renderer.render(this.scene, this.camera);
+			},
+			init() {
+				// 创建场景
+				this.scene = new THREE.Scene()
+				this.scene.background = new THREE.Color('#B9D3FF')
+				// 渲染
+				container = document.getElementById('objContaier')
+				this.renderer = new THREE.WebGLRenderer({
+					antialias: false,
+					alpha: true
+				})
+				this.renderer.outputEncoding = THREE.sRGBEncoding;
+				this.renderer.setPixelRatio(window.devicePixelRatio);
+				this.renderer.setSize(this.conWidth, this.conHeight);
+				container.appendChild(this.renderer.domElement)
+
+				// 相机
+				this.camera = new THREE.PerspectiveCamera(45, this.conWidth / this.conHeight, 1, 1000000000)
+				//设置视角离原点的位置(眼睛距离模型的距离) 
+
+
+
+				if (modelUrl1) {
+					mtlLoader.load(modelUrl1, (mtl) => {
+						// console.log(JSON.parse(JSON.stringify(mtl)));
+						mtl.preload()
+						for (const material of Object.values(mtl.materials)) {
+							material.side = THREE.DoubleSide;
+						}
+						objLoader.setMaterials(mtl)
+						this.setobj()
+					})
+				} else {
+					this.setobj()
+				}
+			},
+			setobj() {
+				objLoader.load(modelUrl, (root) => {
+					// console.log(JSON.parse(JSON.stringify(root.children)));
+					// 设置旋转中心点
+
+					const box = new THREE.Box3().setFromObject(root);
+					const boxSize = box.getSize(new THREE.Vector3()).length();
+					const boxCenter = box.getCenter(new THREE.Vector3());
+					root.position.x = root.position.x - boxCenter.x
+					root.position.y = root.position.y - boxCenter.y
+					root.position.z = root.position.z - boxCenter.z
+					var num = boxSize * 0.6
+					this.root = root
+					this.camera.position.set(num, num, num)
+					this.camera.lookAt(new THREE.Vector3(0, 0, 0))
+					// 轨道控制
+					this.controls = new OrbitControls(this.camera, this.renderer.domElement)
+					this.controls.target.set(0, 0, 0);
+					this.controls.update();
+
+					// 光源
+					const skyColor = 0xB09595;
+					const groundColor = 0x594C4B;
+					const intensity = 0.5;
+					const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
+					this.scene.add(light);
+					// 直射光
+					const pivotPoint = window.pivotPoint = new THREE.Object3D();
+					const dcolor = 0xffffff;
+					const dlight = new THREE.DirectionalLight(dcolor);
+					// 设置平行光源的产生阴影的范围参数
+					dlight.shadow.camera.near = 0;
+					dlight.shadow.camera.far = 50000;
+					dlight.shadow.camera.left = -20000;
+					dlight.shadow.camera.right = 20000;
+					dlight.shadow.camera.top = 20000;
+					dlight.shadow.camera.bottom = -20000;
+					dlight.distance = 1110;
+					dlight.intensity = 0.1;
+					dlight.shadow.mapSize.height = 2560;
+					dlight.shadow.mapSize.width = 2560;
+					dlight.castShadow = true;
+					let sphereLight = new THREE.SphereGeometry(5, 32, 32); // 创建一个球形mesh,用来存放点光源
+					let sphereLightMaterial = new THREE.MeshBasicMaterial({
+						color: 0xac6c25
+					});
+					let sphereLightMesh = window.sphereLightMesh = new THREE.Mesh(sphereLight, sphereLightMaterial);
+					sphereLightMesh.castShadow = false; //将这个球形mesh的产生阴影和接收阴影参数打开
+					sphereLightMesh.receiveShadow = false; //将这个球形mesh的产生阴影和接收阴影参数打开
+					sphereLightMesh.position.set(-25000, 0, 0); //  设置此球形mesh的位置
+
+					pivotPoint.add(sphereLightMesh); // 将球形mesh添加到点光源里
+					pivotPoint.add(dlight); // 将平行光源添加到点光源里
+					dlight.position.copy(sphereLightMesh.position);
+					// dlight.position.set(num, num, num);
+					this.scene.add(dlight);
+					// 环境光
+					// const ambient = new THREE.AmbientLight(0x333333, 1)
+					// this.scene.add(ambient)
+					// 点光源
+					// const dian = new THREE.PointLight(0x333333, 1, 100);
+					// dian.position.set(num, num, num);
+					// this.scene.add(dian);
+
+					// 辅助坐标线
+					// var axesHelper = new THREE.AxesHelper(150);
+					// this.scene.add(axesHelper);
+
+					this.scene.add(this.root);
+					// console.log(boxSize, boxCenter);
+				}, (xhr) => {
+					// console.log(xhr.loaded, xhr.total, 'objLoader');
+					// console.log(xhr);
+					// console.log((xhr.loaded / xhr.total * 100) + '% loaded');
+
+				});
+			},
+			setglb() {
+
+				this.init();
+
+				var modelBorder = document.getElementById("objContaier");
+				modelBorder.addEventListener("mouseenter", function(event) {
+					rotating = false;
+				});
+				modelBorder.addEventListener("mouseleave", function(event) {
+					rotating = true;
+				});
+				modelBorder.addEventListener('touchmove', function(e) {
+					rotating = false;
+				}, false);
+				modelBorder.addEventListener('touchstart', function(e) {
+					rotating = false;
+				}, false);
+				modelBorder.addEventListener('touchend', function(e) {
+					rotating = true;
+				}, false);
+
+			}
+		}
+	}
+	// #endif
+	// #ifdef MP-WEIXIN
+	export default {
+		data() {
+			return {
+				id: '',
+				token: uni.getStorageSync("token") || '',
+				url: this.$url
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+		},
+		methods: {
+
+		}
+	}
+	// #endif	
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F5F5F5;
+	}
+
+	.rizhao {
+		.tocall-box {
+			position: relative;
+
+			.tocall-img {
+				width: 650rpx;
+				height: 476rpx;
+			}
+
+			.tocall-btn {
+				position: absolute;
+				bottom: 70rpx;
+				left: 0;
+				width: 100%;
+				padding: 0 48rpx;
+
+				text:first-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #FFA120;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+
+				text:last-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #1F7EFF;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+					text-decoration: none;
+				}
+			}
+
+			.tocall-text {
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+				position: absolute;
+				top: 244rpx;
+				left: 0;
+				z-index: 10;
+				width: 100%;
+			}
+		}
+
+		.rizhao-btn {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			z-index: 10;
+			width: 750rpx;
+			height: 166rpx;
+			background: #FFFFFF;
+			padding: 0 24rpx 54rpx 24rpx;
+
+			text:first-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #FFA120;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+				text-decoration: none;
+			}
+
+			text:last-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #1F7EFF;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+		}
+
+		.louceng-time-table {
+			width: 702rpx;
+			margin: 20rpx auto;
+			background-color: #fff;
+
+			.body-header {
+				height: 124rpx;
+				white-space: nowrap;
+				border-bottom: 1rpx solid #CCCCCC;
+
+				.danyuan-header {
+
+					.danyuan-title {
+						height: 58rpx;
+						line-height: 58rpx;
+						text-align: center;
+						border-bottom: 1rpx solid #CCCCCC;
+						border-right: 1rpx solid #CCCCCC;
+					}
+
+					.house-title {
+						width: 160rpx;
+						height: 64rpx;
+						line-height: 64rpx;
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #333333;
+						text-align: center;
+						border-right: 1rpx solid #CCCCCC;
+					}
+				}
+
+				.header-title {
+					width: 104rpx;
+					height: 124rpx;
+					border-right: 1rpx solid #CCCCCC;
+					padding: 8rpx 12rpx;
+					position: relative;
+
+					image {
+						position: absolute;
+						top: 0;
+						left: 0;
+						width: 100%;
+						height: 100%;
+					}
+
+					text {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #333333;
+					}
+
+					.text1 {
+						width: 100%;
+						text-align: right;
+					}
+
+					.text2 {
+						width: 100%;
+					}
+				}
+			}
+
+			.body-item {
+				height: 160rpx;
+				white-space: nowrap;
+				border-bottom: 1rpx solid #CCCCCC;
+
+				.danyuan-item {
+					display: inline-block;
+					white-space: nowrap;
+					vertical-align: top;
+
+					.house-item {
+						display: inline-flex;
+						flex-direction: column;
+						justify-content: center;
+						width: 160rpx;
+						padding-left: 16rpx;
+						border-right: 1rpx solid #CCCCCC;
+						border-bottom: 1rpx solid #CCCCCC;
+						height: 160rpx;
+
+						.house-mianji {
+							text:first-child {
+								font-size: 24rpx;
+								color: #333333;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+
+						.house-price {
+							text:first-child {
+								font-size: 24rpx;
+								color: #1F7EFF;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+
+						.house-allprice {
+							text:first-child {
+								font-size: 28rpx;
+								color: #FFA120;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+
+						.house-room {
+							text:first-child {
+								font-size: 24rpx;
+								color: #333333;
+							}
+
+							text:last-child {
+								font-size: 18rpx;
+								color: #999999;
+							}
+						}
+					}
+				}
+
+				.item-title {
+					vertical-align: top;
+					width: 104rpx;
+					text-align: center;
+					line-height: 160rpx;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+					border-right: 1rpx solid #CCCCCC;
+					display: inline-block;
+					min-width: 1rpx;
+				}
+			}
+
+
+			.table-header {
+				width: 100%;
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #333333;
+				line-height: 110rpx;
+				border-bottom: 1rpx solid #CCCCCC;
+
+			}
+		}
+
+		.time-box {
+			width: 702rpx;
+			height: 138rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 24rpx 20rpx 4rpx 20rpx;
+
+			.time-item {
+				margin-bottom: 20rpx;
+
+				text:first-child {
+					width: 32rpx;
+					height: 32rpx;
+					background: #61C86A;
+					border-radius: 8rpx;
+					margin-right: 20rpx;
+				}
+
+				text:last-child {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+					margin-right: 40rpx;
+				}
+			}
+		}
+
+		.louceng-jieqi {
+			background-color: #fff;
+			padding-bottom: 24rpx;
+
+			.jieqi-box {
+				width: 662rpx;
+				height: 164rpx;
+				background: #F5F5F5;
+				border-radius: 20rpx;
+				margin: 0 auto;
+				padding: 24rpx 20rpx 4rpx 20rpx;
+
+				text {
+					margin-bottom: 20rpx;
+					width: 194rpx;
+					line-height: 48rpx;
+					background: #FFFFFF;
+					border-radius: 8rpx;
+					border: 2rpx solid #999999;
+					text-align: center;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+				}
+
+				.text {
+					color: #1F7EFF;
+					border-color: #1F7EFF;
+				}
+			}
+		}
+
+		.louceng-fangxiang {
+			padding: 24rpx 44rpx;
+			background-color: #FFFFFF;
+
+			text {
+				width: 318rpx;
+				line-height: 84rpx;
+				background: #FFFFFF;
+				border-radius: 20rpx;
+				border: 2rpx solid #999999;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+			}
+
+			.text {
+				color: #1F7EFF;
+				border-color: #1F7EFF;
+			}
+		}
+
+		.rizhao-louhao {
+			position: sticky;
+			top: 0;
+			left: 0;
+			z-index: 10;
+			width: 750rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			padding: 0 18rpx 24rpx 18rpx;
+			border-bottom: 2rpx solid #F5F5F5;
+
+			.louhao-active {
+				margin: 0 25rpx;
+				width: 92rpx;
+				height: 40rpx;
+				background: #FFFFFF;
+				border-radius: 8rpx;
+				border: 1rpx solid #999999;
+				position: relative;
+				margin-top: 24rpx;
+
+				.text1 {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+				}
+
+				.text2 {
+					width: 20rpx;
+					line-height: 20rpx;
+					background: #1F7EFF;
+					border-radius: 8rpx 0px 8rpx 0px;
+					text-align: center;
+					position: absolute;
+					bottom: 0;
+					right: 0;
+					z-index: 1;
+					color: #fff;
+					font-size: 18rpx;
+				}
+			}
+		}
+	}
+</style>

+ 196 - 0
pages/index/broker.vue

@@ -0,0 +1,196 @@
+<template>
+	<view class="broker">
+		<view class="broker-title">
+			基本信息
+		</view>
+		<view class="broker-row u-flex">
+			<view class="broker-row-left u-flex">
+				<text>姓名</text>
+				<text>*</text>
+			</view>
+			<input type="text" placeholder="请输入" v-model="name" class="input-right">
+		</view>
+		<!-- <view class="broker-row u-flex">
+			<view class="broker-row-left u-flex">
+				<text>身份证号</text>
+				<text>*</text>
+			</view>
+			<input type="idcard" placeholder="请输入" v-model="card" class="input-right">
+		</view> -->
+		<view class="broker-row u-flex">
+			<view class="broker-row-left u-flex">
+				<text>手机号</text>
+				<text>*</text>
+			</view>
+			<input type="number" placeholder="请输入" v-model="tel" class="input-right">
+		</view>
+		<view class="broker-row u-flex">
+			<view class="broker-row-left u-flex">
+				<text>门店</text>
+				<text>*</text>
+			</view>
+			<input type="text" placeholder="请输入" v-model="shop" class="input-right">
+		</view>
+		<view class="broker-btn" @click="save">
+			确认
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				type: 1,
+				name: '',
+				card: '',
+				tel: '',
+				shop: ''
+			}
+		},
+		onLoad(option) {
+			this.type = option.type
+			this.getuser()
+		},
+		onShow() {
+			if (this.type == 1) {
+				uni.setNavigationBarTitle({
+					title: "中介经纪人"
+				})
+			} else {
+				uni.setNavigationBarTitle({
+					title: "全民经纪人"
+				})
+			}
+		},
+		methods: {
+			getuser() {
+				this.$u.post('/api/Member/member_info').then(res => {
+					this.name = res.data.broker_name
+					this.card = res.data.broker_id_card
+					this.tel = res.data.broker_phone
+					this.shop = res.data.broker_shop
+					if (res.data.certification_check_status == 1) {
+						uni.showModal({
+							title: "提示",
+							content: "审核中",
+							success() {
+								uni.navigateBack()
+							}
+						})
+						return
+					}
+					if (res.data.certification_check_status == 2) {
+						uni.showModal({
+							title: "提示",
+							content: "审核已通过",
+							success() {
+								uni.navigateBack()
+							}
+						})
+						return
+					}
+					if (res.data.certification_check_status == 3) {
+						uni.showModal({
+							title: "提示",
+							content: "申请失败"
+						})
+					}
+				})
+			},
+			save() {
+				if (!this.name) {
+					this.$u.toast("请输入姓名")
+					return
+				}
+				// if (!this.$u.test.idCard(this.card)) {
+				// 	this.$u.toast("请输入正确的身份证")
+				// 	return
+				// }
+				if (!this.$u.test.mobile(this.tel)) {
+					this.$u.toast("请输入正确的手机号")
+					return
+				}
+				if (!this.shop) {
+					this.$u.toast("请输入门店")
+					return
+				}
+				uni.showLoading({
+					mask: true,
+					title: "请稍后"
+				})
+				this.$u.post('/api/Member/submit_broker', {
+					type: this.type,
+					name: this.name,
+					id_card: this.card,
+					phone: this.tel,
+					shop: this.shop
+				}).then(res => {
+					this.$u.toast(res.msg)
+					if (res.code == 1) {
+						setTimeout(() => {
+							uni.navigateBack()
+						}, 800)
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.broker {
+		padding: 0 24rpx;
+
+		.broker-btn {
+			width: 702rpx;
+			line-height: 82rpx;
+			background: #1F7EFF;
+			border-radius: 8rpx;
+			text-align: center;
+			font-size: 28rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #FFFFFF;
+			position: fixed;
+			bottom: 226rpx;
+			left: 24rpx;
+		}
+
+		.broker-row {
+			height: 82rpx;
+			border-bottom: 2rpx solid #F5F5F5;
+
+			.input-right {
+				flex: 1;
+				font-size: 24rpx;
+			}
+
+			.broker-row-left {
+				width: 252rpx;
+
+				text:first-child {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+				}
+
+				text:nth-child(2) {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #F83838;
+				}
+			}
+		}
+
+		.broker-title {
+			padding: 24rpx 0;
+			font-size: 28rpx;
+			font-family: PingFangSC-Medium, PingFang SC;
+			font-weight: 500;
+			color: #333333;
+		}
+	}
+</style>

+ 251 - 0
pages/index/chat.vue

@@ -0,0 +1,251 @@
+<template>
+	<view class="chat">
+		<view class="chat-list">
+			<view class="more-btn" @click="tomore">
+				{{showmore ? '点击查看更多' : '没有更多了'}}
+			</view>
+			<view class="chat-item u-flex u-col-top u-row-between" v-for="(item,index) in list" :key="index">
+				<image :src="item.headimg" :style="{opacity:item.type == 2 ? 1 : 0}" class="user-head" mode=""></image>
+				<view class="item-box u-flex-1 u-flex-col" :style="{alignItems: item.type == 2 ? 'flex-start' : 'flex-end'}">
+					<text>{{item.name}}</text>
+					<text :class="item.type == 2 ? 'text1' : 'text2'">{{item.content}}</text>
+				</view>
+				<image :src="item.headimg" :style="{opacity:item.type == 2 ? 0 : 1}" class="user-head" mode=""></image>
+			</view>
+			<view class="chat-item u-flex u-col-top u-row-between" v-for="(item,index) in chatlist" :key="index">
+				<image :src="item.headimg" :style="{opacity:item.type == 2 ? 1 : 0}" class="user-head" mode=""></image>
+				<view class="item-box u-flex-1 u-flex-col" :style="{alignItems: item.type == 2 ? 'flex-start' : 'flex-end'}">
+					<text>{{item.name}}</text>
+					<text :class="item.type == 2 ? 'text1' : 'text2'">{{item.content}}</text>
+				</view>
+				<image :src="item.headimg" :style="{opacity:item.type == 2 ? 0 : 1}" class="user-head" mode=""></image>
+			</view>
+		</view>
+		<view class="" style="height: 170rpx;"></view>
+		<view class="chat-btn u-flex u-row-between">
+			<input type="text" placeholder="请输入" v-model="text">
+			<text @click="send(text)">发送</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				text: '',
+				hx_username: '',
+				worker_id:'',
+				page: 1,
+				list: [],
+				chatlist:[],
+				myname:'',
+				myheadimg:'',
+				workername:'',
+				workerheadimg:'',
+			}
+		},
+		onLoad(option) {
+			this.hx_username = option.hx_username
+			this.worker_id = option.worker_id
+			this.myname = uni.getStorageSync("name")
+			this.myheadimg = uni.getStorageSync("headimg")
+			this.getlist()
+			this.getworker()
+			this.getmsg()
+		},
+		computed:{
+			showmore(){
+				if(this.list.length % 20 == 0 && this.list.length > 0){
+					return true
+				}else{
+					return false
+				}
+			}
+		},
+		methods: {
+			tomore(){
+				if(this.showmore){
+					this.page++
+					this.getlist()
+				}
+			},
+			getmsg(){
+				this.$WebIM.conn.addEventHandler('getmsg', {
+					// 当前用户收到文本消息。
+					onTextMessage: (message) => {
+						console.log(message);
+						this.chatlist.push({
+							content: message.msg,
+							create_at: this.$u.timeFormat(message.time, 'yyyy-mm-dd hh:MM:ss'),
+							headimg: message.from == uni.getStorageSync("hx_username") ? this.myheadimg : this.workerheadimg,
+							name: message.from == uni.getStorageSync("hx_username") ? this.myname : this.workername,
+							type: message.from == uni.getStorageSync("hx_username") ? 1 : 2,
+						})
+						this.$nextTick(() => {
+							uni.pageScrollTo({
+								scrollTop:99999
+							})
+						})
+					},
+				})
+			},
+			getworker(){
+				this.$u.post('/api/News/get_worker_info',{
+					worker_id: this.worker_id
+				}).then(res => {
+					this.workername = res.data.name
+					this.workerheadimg = res.data.headimg
+				})
+			},
+			getlist() {
+				uni.showLoading({
+					mask:true,
+					title:"请稍后"
+				})
+				this.$u.post('/api/News/news_list', {
+					worker_id: this.worker_id,
+					page_num: 20,
+					page: this.page
+				}).then(res => {
+					var list = res.data
+					list.forEach(val => {
+						this.list.unshift(val)
+					})
+					if(this.page == 1){
+						this.$nextTick(() => {
+							uni.pageScrollTo({
+								scrollTop:99999
+							})
+						})
+					}
+					
+				})
+			},
+			send(text) {
+				if (!text) {
+					this.$u.toast("请输入内容")
+					return
+				}
+				this.text = ''
+				let option = {
+					// 消息类型。
+					type: "txt",
+					// 消息内容。
+					msg: text,
+					// 消息发送方:单聊为对方用户 ID,群聊和聊天室分别为群组 ID 和聊天室 ID。
+					from: uni.getStorageSync("hx_username"),
+					// 消息接收方:单聊为对方用户 ID,群聊和聊天室分别为群组 ID 和聊天室 ID。
+					to: this.hx_username,
+					// 会话类型:单聊、群聊和聊天室分别为 `singleChat`、`groupChat` 和 `chatRoom`,默认为单聊。
+					chatType: "singleChat",
+				};
+				// 创建文本消息。
+				let msg = this.$WebIM.message.create(option);
+				// console.log(msg);
+				this.$WebIM.conn.send(msg).then(res => {
+					this.$u.post('/api/News/send_news', {
+						worker_id: this.worker_id,
+						content: text
+					}).then(res => {
+						this.chatlist.push({
+							content: msg.msg,
+							create_at: this.$u.timeFormat(msg.time, 'yyyy-mm-dd hh:MM:ss'),
+							headimg: this.myheadimg,
+							name: this.myname,
+							type: 1,
+						})
+						this.$nextTick(() => {
+							uni.pageScrollTo({
+								scrollTop:99999
+							})
+						})
+						
+						// console.log(this.chatlist);
+					})
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F2F2F2;
+	}
+
+	.chat {
+		.chat-list {
+			padding: 24rpx;
+			.more-btn{
+				padding: 10rpx 0;
+				text-align: center;
+			}
+			.chat-item {
+				margin-bottom: 44rpx;
+
+				.item-box {
+					text:first-child {
+						font-size: 28rpx;
+						font-family: PingFangSC-Medium, PingFang SC;
+						font-weight: 500;
+						color: #666666;
+						padding: 8rpx 24rpx;
+					}
+
+					text:last-child {
+						padding: 20rpx;
+						border-radius: 20rpx;
+					}
+
+					.text1 {
+						background-color: #fff;
+					}
+
+					.text2 {
+						background-color: #1F7EFF;
+						color: #fff;
+					}
+				}
+
+				.user-head {
+					width: 80rpx;
+					height: 80rpx;
+					border-radius: 100rpx;
+				}
+			}
+		}
+
+		.chat-btn {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			width: 750rpx;
+			height: 166rpx;
+			background: #FFFFFF;
+			z-index: 10;
+			padding: 0 24rpx 60rpx 24rpx;
+
+			input {
+				width: 574rpx;
+				height: 58rpx;
+				background: #F2F2F2;
+				border-radius: 30rpx;
+				padding: 0 28rpx;
+				box-sizing: border-box;
+			}
+
+			text {
+				width: 102rpx;
+				line-height: 58rpx;
+				background: #1F7EFF;
+				border-radius: 30rpx;
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+		}
+	}
+</style>

+ 36 - 0
pages/index/fuli-info.vue

@@ -0,0 +1,36 @@
+<template>
+	<view class="fuli-info">
+		<u-parse :html="content"></u-parse>
+	</view>
+</template>
+
+<script>
+	export default {
+		data(){
+			return{
+				id:'',
+				content:''
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			this.getdata()
+		},
+		methods:{
+			getdata(){
+				this.$u.post("/api/welfare/welfare_detail",{
+					id:this.id
+				}).then(res => {
+					const regex = new RegExp('<img', 'gi')
+					this.content = res.data.content.replace(regex, `<img style="max-width: 100%; height: auto"`)
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.fuli-info{
+		padding: 24rpx;
+	}
+</style>

+ 78 - 0
pages/index/fuli.vue

@@ -0,0 +1,78 @@
+<template>
+	<view class="fuli">
+		<view class="swiper-box">
+			<u-swiper :list="swiperlisi" name="logo" height="320" @click="tobanner" border-radius="20"></u-swiper>
+		</view>
+		<view class="u-flex u-row-between u-flex-wrap list-box">
+			<image :src="item.logo" v-for="(item,index) in list" :key="index" @click="toinfo(item)" mode="aspectFill"></image>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data(){
+			return{
+				swiperlisi: [],
+				list:[],
+				page:1
+			}
+		},
+		onLoad() {
+			this.getbanner()
+			this.getlist()
+		},
+		onReachBottom() {
+			if(this.list.length % 20 == 0){
+				this.page++
+				this.getlist()
+			}
+		},
+		methods:{
+			tobanner(e) {
+				if (this.swiperlisi[e].url) {
+					uni.navigateTo({
+						url: this.swiperlisi[e].url
+					})
+				}
+			},
+			getlist(){
+				this.$u.post('/api/welfare/welfare_list',{
+					page:this.page,
+					page_num:20
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			},
+			getbanner() {
+				this.$u.post('/api/Index/banner',{
+					location:3
+				}).then(res => {
+					this.swiperlisi = res.data
+				})
+			},
+			toinfo(item){
+				uni.navigateTo({
+					url:"./fuli-info?id=" + item.id
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.fuli{
+		.swiper-box{
+			margin: 24rpx;
+		}
+		.list-box{
+			padding: 0 24rpx;
+			image{
+				width: 340rpx;
+				height: 110rpx;
+				border-radius: 10rpx;
+				margin-bottom: 20rpx;
+			}
+		}
+	}
+</style>

+ 602 - 0
pages/index/houses-info.vue

@@ -0,0 +1,602 @@
+<template>
+	<view class="houses-info">
+		<view class="index-bg"></view>
+		<view class="swiper-box">
+			<u-swiper :list="swiperlisi" height="367" border-radius="20"></u-swiper>
+		</view>
+		<view class="houses-tabs u-flex u-row-between">
+			<view class="tabs-item u-flex-col u-col-center" @click="showdijia = true">
+				<image src="../../static/images/tabs1.png" mode=""></image>
+				<text>底价详情</text>
+			</view>
+			<!-- <view class="tabs-item u-flex-col u-col-center" @click="torizhao">
+				<image src="../../static/images/tabs2.png" mode=""></image>
+				<text>日照测评</text>
+			</view> -->
+			<view class="tabs-item u-flex-col u-col-center" @click="toyifang">
+				<image src="../../static/images/tabs3.png" mode=""></image>
+				<text>一房一价</text>
+			</view>
+			<view class="tabs-item u-flex-col u-col-center" @click="tovideo">
+				<image src="../../static/images/tabs4.png" mode=""></image>
+				<text>抖音视频</text>
+			</view>
+		</view>
+		<view class="houses-box">
+			<view class="box-header u-flex u-row-between">
+				<text>{{detail.name}}</text>
+				<text @click="toinfo()">详情>></text>
+			</view>
+			<view class="box-tips">
+				备案名:{{detail.record_name}}
+			</view>
+			<view class="u-flex price">
+				<text>参考均价</text>
+				<text>{{detail.avg_price}}/㎡</text>
+			</view>
+			<view class="houses-data u-flex u-flex-wrap">
+				<view class="data-item u-flex-col">
+					<text>{{detail.floor_area}}</text>
+					<text>占地面积</text>
+				</view>
+				<view class="data-item u-flex-col">
+					<text>{{detail.covered_area}}</text>
+					<text>总建筑面积</text>
+				</view>
+				<view class="data-item u-flex-col">
+					<text>{{detail.house_min_area}}㎡-{{detail.house_max_area}}㎡</text>
+					<text>户型面积</text>
+				</view>
+				<view class="data-item u-flex-col">
+					<text>{{detail.greening_rate}}</text>
+					<text>绿化率</text>
+				</view>
+				<view class="data-item u-flex-col">
+					<text>{{detail.plot_ratio}}</text>
+					<text>容积率</text>
+				</view>
+			</view>
+			<view class="houses-label u-flex u-flex-wrap">
+				<text v-for="(item,inde) in detail.trait" :key="index">{{item}}</text>
+			</view>
+			<view class="kaifa-box">
+				<view style="margin-bottom: 24rpx;">开盘日期: {{detail.sale_time}}</view>
+				<view>开发商: {{detail.developers}}</view>
+			</view>
+		</view>
+		<view class="address-box u-flex">
+			<u-icon name="map-fill" color="#131415"></u-icon>
+			<text class="text">{{detail.project_address}}</text>
+		</view>
+		<view class="map-box" v-if="detail.latitude && detail.longitude">
+			<view class="map-title">
+				区域地图
+			</view>
+			<map @click="tomap" style="width: 100%;height: 376rpx;" :latitude="latitude" :longitude="longitude" :markers="[{id:1,latitude:latitude,longitude:longitude,width:20,height:30}]"></map>
+		</view>
+		<view class="huxing-box" v-if="detail.house_type.length > 0">
+			<view class="huixng-title">
+				在售户型
+			</view>
+			<view class="scroll-box1" style="position: relative;">
+				<u-icon name="arrow-left" style="position: absolute;top: 100rpx;left: -20rpx;z-index: 1;"></u-icon>
+				<scroll-view scroll-x="true" class="huxing-list">
+					<view class="huxing-item" v-for="(item,index) in detail.house_type" :key="index">
+						<image :src="item.logo" @click="openimg(item)" class="huxing-img" mode=""></image>
+						<view class="name">
+							{{item.house_type_name}}
+						</view>
+						<view class="jianyi">
+							建面:约{{item.area}}㎡
+						</view>
+					</view>
+				</scroll-view>
+				<u-icon name="arrow-right" style="position: absolute;top: 100rpx;right: -20rpx;z-index: 1;"></u-icon>
+			</view>
+
+		</view>
+		<view class="" style="height: 170rpx;"></view>
+		<view class="houses-btn u-flex u-row-between">
+			<text @click="opentel">隐私电话</text>
+			<text @click="tochat">在线咨询</text>
+		</view>
+		<u-popup v-model="showdijia" mode="center" background="rgba(0,0,0,0)">
+			<view class="dijia-box u-flex-col u-col-center">
+				<image class="dijia-img" src="../../static/images/dijia-bg.png" mode=""></image>
+				<view class="u-flex-col u-col-center dijia-text">
+					<text>{{detail.discounts_policy}}</text>
+				</view>
+				<u-icon name="close-circle-fill" color="#fff" size="100" @click="showdijia = false"></u-icon>
+			</view>
+		</u-popup>
+		<u-popup v-model="showtocall" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">您是否进行呼叫?</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showtocall = false">取消</text>
+					<text @click="totel">确定</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-popup v-model="showlogin" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">你目前处于未登录状态请前往登录</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showlogin = false">取消</text>
+					<text @click="tologin">确定</text>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				swiperlisi: [],
+				showdijia: false,
+				showtocall: false,
+				id: '',
+				detail: {
+					house_type: []
+				},
+				showlogin: false
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			this.getdata()
+		},
+		computed: {
+			latitude() {
+				return Number(this.detail.latitude) > Number(this.detail.longitude) ? this.detail.longitude : this.detail.latitude
+			},
+			longitude() {
+				return Number(this.detail.latitude) > Number(this.detail.longitude) ? this.detail.latitude : this.detail.longitude
+			}
+		},
+		methods: {
+			tomap() {
+				uni.openLocation({
+					latitude: Number(this.detail.latitude),
+					longitude: Number(this.detail.longitude)
+				})
+			},
+			openimg(item) {
+				uni.previewImage({
+					urls: [item.logo]
+				})
+			},
+			tologin() {
+				uni.switchTab({
+					url: "/pages/mine/mine"
+				})
+			},
+			opentel() {
+				if (uni.getStorageSync("token")) {
+					this.showtocall = true
+				} else {
+					this.showlogin = true
+				}
+
+			},
+			getdata() {
+				this.$u.post('/api/Property/property_detail', {
+					id: this.id
+				}).then(res => {
+					this.swiperlisi = res.data.banner
+					this.detail = res.data
+				})
+			},
+			tovideo() {
+				uni.navigateTo({
+					url: "./video?id=" + this.id
+				})
+			},
+			toinfo() {
+				uni.navigateTo({
+					url: "./info?id=" + this.id
+				})
+			},
+			tochat() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 2
+					})
+					uni.navigateTo({
+						url: "./chat?hx_username=" + this.detail.hx_username + "&worker_id=" + this.detail.worker_id
+					})
+				} else {
+					this.showlogin = true
+				}
+			},
+			toyifang() {
+				uni.navigateTo({
+					url: "/pages/h5/yifang?id=" + this.id
+				})
+			},
+			torizhao() {
+				uni.navigateTo({
+					url: "/pages/h5/rizhao?id=" + this.id
+				})
+			},
+			totel() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 1
+					})
+					uni.makePhoneCall({
+						phoneNumber: this.detail.privacy_phone
+					})
+				} else {
+					this.showlogin = true
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.houses-info {
+		position: relative;
+		padding: 1rpx 0;
+
+		.tocall-box {
+			position: relative;
+
+			.tocall-img {
+				width: 650rpx;
+				height: 476rpx;
+			}
+
+			.tocall-btn {
+				position: absolute;
+				bottom: 70rpx;
+				left: 0;
+				width: 100%;
+				padding: 0 48rpx;
+
+				text:first-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #FFA120;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+
+				text:last-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #1F7EFF;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+			}
+
+			.tocall-text {
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+				position: absolute;
+				top: 244rpx;
+				left: 0;
+				z-index: 10;
+				width: 100%;
+			}
+		}
+
+		.dijia-box {
+			position: relative;
+
+			.dijia-text {
+				position: absolute;
+				z-index: 10;
+				top: 120rpx;
+				left: 0;
+				width: 100%;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #EC9234;
+				padding: 0 150rpx;
+			}
+
+			.dijia-img {
+				width: 650rpx;
+				height: 520rpx;
+				margin-bottom: 52rpx;
+			}
+		}
+
+		.houses-btn {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			width: 750rpx;
+			height: 166rpx;
+			background: #FFFFFF;
+			padding: 0 24rpx 54rpx 24rpx;
+			z-index: 1;
+
+			text:first-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #FFA120;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+
+			text:last-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #1F7EFF;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+		}
+
+		.huxing-box {
+			width: 702rpx;
+			// height: 450rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx 30rpx 20rpx;
+
+			.huxing-list {
+				white-space: nowrap;
+
+				.huxing-item {
+					margin-right: 20rpx;
+					display: inline-block;
+
+					.jianyi {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+					}
+
+					.name {
+						margin: 24rpx 0 10rpx 0;
+						font-size: 24rpx;
+						font-family: PingFangSC-Medium, PingFang SC;
+						font-weight: 500;
+						color: #333333;
+					}
+
+					.huxing-img {
+						width: 206rpx;
+						height: 206rpx;
+					}
+				}
+			}
+
+			.huixng-title {
+				padding: 24rpx 0;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #131415;
+			}
+		}
+
+		.map-box {
+			width: 702rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx 24rpx 20rpx;
+
+			.map-title {
+				padding: 24rpx 0;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #131415;
+			}
+		}
+
+		.address-box {
+			width: 702rpx;
+			height: 82rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 22rpx;
+
+			.text {
+				flex: 1;
+				margin-left: 10rpx;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #131415;
+			}
+		}
+
+		.houses-box {
+			width: 702rpx;
+			height: 594rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx;
+
+			.kaifa-box {
+				padding: 24rpx 20rpx;
+				background: #F5F5F5;
+				border-radius: 20rpx;
+
+				view {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #666666;
+				}
+			}
+
+			.houses-label {
+				padding: 24rpx 0 14rpx 0;
+
+				text {
+					line-height: 40rpx;
+					background: #F5F5F5;
+					border-radius: 8rpx;
+					padding: 0 10rpx;
+					font-size: 20rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+					margin-right: 10rpx;
+					margin-bottom: 10rpx;
+				}
+			}
+
+			.houses-data {
+				padding: 20rpx 0;
+				// height: 126rpx;
+				border-bottom: 2rpx solid #F5F5F5;
+
+				.data-item {
+					// width: 134rpx;
+					// flex: 1;
+					margin-right: 22rpx;
+
+					// margin-right: 10rpx;
+					text:first-child {
+						font-size: 30rpx;
+						font-family: PingFangSC-Medium, PingFang SC;
+						font-weight: 500;
+						color: #131415;
+						margin-bottom: 8rpx;
+						white-space: nowrap;
+					}
+
+					text:last-child {
+						font-size: 18rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+					}
+				}
+
+				.data-item:last-child {
+					margin-right: 0;
+				}
+			}
+
+			.price {
+				padding-bottom: 24rpx;
+				border-bottom: 2rpx solid #F5F5F5;
+
+				text:first-child {
+					font-size: 20rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+					margin-right: 8rpx;
+				}
+
+				text:last-child {
+					font-size: 32rpx;
+					font-family: DINAlternate-Bold, DINAlternate;
+					font-weight: bold;
+					color: #FF3B30;
+				}
+			}
+
+			.box-tips {
+				font-size: 24rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #666666;
+				margin-bottom: 20rpx;
+			}
+
+			.box-header {
+				padding: 24rpx 0 12rpx 0;
+
+				text:first-child {
+					font-size: 36rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #131415;
+				}
+
+				text:last-child {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #1F7EFF;
+				}
+			}
+		}
+
+		.houses-tabs {
+			width: 702rpx;
+			height: 218rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+
+			.tabs-item {
+				flex: 1;
+
+				image {
+					width: 84rpx;
+					height: 84rpx;
+					margin-bottom: 20rpx;
+				}
+
+				text {
+					// margin-top: -34rpx;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #131415;
+				}
+			}
+		}
+
+		.swiper-box {
+			width: 702rpx;
+			margin: 20rpx auto;
+		}
+
+		.index-bg {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 750rpx;
+			height: 556rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			z-index: -1;
+		}
+	}
+</style>

+ 454 - 0
pages/index/index.vue

@@ -0,0 +1,454 @@
+<template>
+	<view class="index-index">
+		<view class="index-bg"></view>
+		<u-navbar :title="config.store_title" title-color="#fff" :isFixed="false" :background="{background:'rgba(0,0,0,0)'}" :border-bottom="false" :isBack="false">
+			<view class="u-flex nav-left" slot="left">
+				<u-icon name="map-fill" size="34" color="#fff"></u-icon>
+				<text class="text">{{city || '定位中'}}</text>
+			</view>
+		</u-navbar>
+		<view class="renqi-box u-flex">
+			<u-icon name="heart-fill" color="#fff" size="26"></u-icon>
+			<text class="text">累计人气值 {{popularity}}</text>
+		</view>
+		<view class="search-box u-flex u-row-between" @click="tosearch">
+			<u-icon name="search" color="#CCCCCC" size="36"></u-icon>
+			<input type="text" placeholder="搜索楼盘" class="input" :disabled="true">
+			<text class="text">搜索</text>
+		</view>
+		<view class="swiper-box">
+			<u-swiper :list="swiperlisi" name="logo" height="280" border-radius="20" @click="tobanner"></u-swiper>
+		</view>
+		<view class="tabs-list" v-if="tabslist.length > 0">
+			<gf-scroll :list="tabslist" @click="tourl"></gf-scroll>
+		</view>
+		<view class="list-box" v-if="dijialist.length > 0">
+			<view class="list-title u-flex u-row-between">
+				<view class="u-flex title-left">
+					<text>底价人气热盘</text>
+					<image src="../../static/images/hot.png" mode=""></image>
+				</view>
+				<view class="more" @click="toall(1)">
+					查看更多
+				</view>
+			</view>
+			<view class="u-flex u-row-between u-flex-wrap">
+				<view class="list-item" v-for="(item,index) in dijialist" :key="index" @click="toinfo(item)">
+					<image class="item-img" :src="item.logo" mode=""></image>
+					<view class="item-name u-line-1">
+						{{item.name}}
+					</view>
+					<view class="u-flex price">
+						<text>参考均价</text>
+						<text v-if="Number(item.avg_price)">{{item.avg_price}}/㎡</text>
+						<text v-else>{{item.avg_price}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="list-box" v-if="tuijianlist.length > 0">
+			<view class="list-title u-flex u-row-between">
+				<view class="u-flex title-left">
+					<text>推荐楼盘</text>
+				</view>
+				<view class="more" @click="toall(5)">
+					查看更多
+				</view>
+			</view>
+			<view class="u-flex u-row-between u-flex-wrap">
+				<view class="list-item" v-for="(item,index) in tuijianlist" :key="index" @click="toinfo(item)">
+					<image class="item-img" :src="item.logo" mode=""></image>
+					<view class="item-name u-line-1">
+						{{item.name}}
+					</view>
+					<view class="u-flex price">
+						<text>参考均价</text>
+						<text v-if="Number(item.avg_price)">{{item.avg_price}}/㎡</text>
+						<text v-else>{{item.avg_price}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+		<u-popup v-model="showguanggao" mode="center" border-radius="20" background="rgba(0,0,0,0)">
+			<view class="guanggao-box">
+				<u-swiper :list="guanggaolist" bgColor="rgba(0,0,0,0)" name="logo" height="974" border-radius="20" :autoplay="false"></u-swiper>
+				<view class="close-box u-flex">
+					<text style="border-right: 1rpx solid #fff;">{{s}}s</text>
+					<text @click="closeguanggao">关闭</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-popup v-model="showgzh" mode="center" border-radius="20">
+			<view style="font-size: 0;">
+				<image style="width: 650rpx;height: 650rpx;" :src="config.service_code" mode=""></image>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	import {
+		mapGetters,
+		mapState
+	} from 'vuex'
+	export default {
+		data() {
+			return {
+				swiperlisi: [],
+				tabslist: [],
+				showguanggao: false,
+				guanggaolist: [],
+				s: 5,
+				time: null,
+				showgzh: false,
+				popularity: 0,
+				dijialist: [],
+				tuijianlist: []
+			}
+		},
+		onLoad() {
+			this.getbanner()
+			this.gettabs()
+			this.getguanggao()
+			this.getlist()
+		},
+		onShow() {
+			this.getpopularity()
+		},
+		computed: {
+			...mapGetters(['config']),
+			...mapState(['city'])
+		},
+		methods: {
+			getlist() {
+				this.$u.post('/api/Index/property_list', {
+					page: 1,
+					page_num: 10,
+					type: 1,
+				}).then(res => {
+					this.dijialist = res.data
+				})
+				this.$u.post('/api/Index/property_list', {
+					page: 1,
+					page_num: 10,
+					type: 5,
+				}).then(res => {
+					this.tuijianlist = res.data
+				})
+			},
+			daojishi() {
+				this.s = 5
+				this.time = setInterval(() => {
+					if (this.s == 0) {
+						this.showguanggao = false
+						clearInterval(this.time)
+					} else {
+						this.s--
+					}
+				}, 1000)
+			},
+			getguanggao() {
+				this.$u.post('/api/Index/adv_list').then(res => {
+					this.guanggaolist = res.data
+					this.showguanggao = true
+					this.daojishi()
+				})
+			},
+			gettabs() {
+				this.$u.post("/api/Index/diamond_list").then(res => {
+					this.tabslist = []
+					res.data.forEach(val => {
+						if (val.id == 11 && this.config.intermediary_show == 1) {
+							this.tabslist.push(val)
+						} else if (val.id == 12 && this.config.people_show == 1) {
+							this.tabslist.push(val)
+						} else {
+							this.tabslist.push(val)
+						}
+					})
+				})
+			},
+			tobanner(e) {
+				if (this.swiperlisi[e].url) {
+					uni.navigateTo({
+						url: this.swiperlisi[e].url
+					})
+				}
+			},
+			getbanner() {
+				this.$u.post('/api/Index/banner').then(res => {
+					this.swiperlisi = res.data
+				})
+			},
+			getpopularity() {
+				this.$u.post('/api/Index/add_popularity').then(res => {
+					this.$store.commit('setpopularity', res.data)
+					this.popularity = res.data
+				})
+			},
+			tosearch() {
+				uni.navigateTo({
+					url: "./search"
+				})
+			},
+			toinfo(item) {
+				uni.navigateTo({
+					url: "./houses-info?id=" + item.id
+				})
+			},
+			tourl(item) {
+				console.log(item);
+				if (item.id == 1) {
+					uni.navigateTo({
+						url: "/pagesA/index/newHouses?title="+item.title
+					})
+				}
+				if (item.id == 2) {
+					uni.navigateTo({
+						url: "/pagesA/index/allHouses?type=3"
+					})
+				}
+				if (item.id == 3) {
+					uni.navigateTo({
+						url: "/pagesA/index/allHouses?type=4"+'&title='+item.title
+					})
+				}
+				if (item.id == 4) {
+					uni.navigateTo({
+						url: "/pagesA/index/dataCenter?title="+item.title
+					})
+				}
+				if (item.id == 5) {
+					this.showgzh = true
+				}
+				if (item.id == 6) {
+					uni.navigateTo({
+						url: "./fuli"
+					})
+				}
+				if (item.id == 7) {
+					uni.navigateTo({
+						url: "./news?title="+item.title
+					})
+				}
+				if (item.id == 8) {
+					uni.navigateTo({
+						url: "/pagesA/index/counter"
+					})
+				}
+				if (item.id == 9) {
+					uni.navigateTo({
+						url: "/pagesA/index/allHouses?title="+item.title
+					})
+				}
+				if (item.id == 10) {
+					uni.navigateTo({
+						url: "./pk-list"
+					})
+				}
+				if (item.id == 11) {
+					if (this.$islogin()) return
+					uni.navigateTo({
+						url: "./broker?type=1"
+					})
+				}
+				if (item.id == 12) {
+					if (this.$islogin()) return
+					uni.navigateTo({
+						url: "./broker?type=2"
+					})
+				}
+			},
+			toall(type) {
+				uni.navigateTo({
+					url: "/pagesA/index/allHouses?type=" + type
+				})
+			},
+			closeguanggao() {
+				this.showguanggao = false
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.index-index {
+		position: relative;
+		z-index: 1;
+
+		.guanggao-box {
+			width: 650rpx;
+			height: 974rpx;
+			position: relative;
+
+			.close-box {
+				position: absolute;
+				top: 24rpx;
+				right: 24rpx;
+				z-index: 1;
+				width: 150rpx;
+				height: 50rpx;
+				background: rgba(0, 0, 0, 0.5);
+				border-radius: 8rpx;
+				border: 2rpx solid #FFFFFF;
+
+				text {
+					flex: 1;
+					line-height: 30rpx;
+					text-align: center;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #FFFFFF;
+				}
+			}
+		}
+
+		.list-box {
+			width: 702rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx;
+
+			.list-item {
+				width: 320rpx;
+				margin-bottom: 24rpx;
+
+				.price {
+					text:first-child {
+						font-size: 20rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+						margin-right: 10rpx;
+					}
+
+					text:last-child {
+						font-size: 32rpx;
+						font-family: DINAlternate-Bold, DINAlternate;
+						font-weight: bold;
+						color: #FF3B30;
+					}
+				}
+
+				.item-name {
+					font-size: 28rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #131415;
+					margin: 10rpx 0;
+				}
+
+				.item-img {
+					width: 320rpx;
+					height: 240rpx;
+					border-radius: 20rpx;
+				}
+			}
+
+			.list-title {
+				padding: 24rpx 0;
+
+				.more {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #666666;
+				}
+
+				.title-left {
+					text {
+						font-size: 34rpx;
+						font-family: PingFangSC-Medium, PingFang SC;
+						font-weight: 500;
+						color: #131415;
+						margin-right: 14rpx;
+					}
+
+					image {
+						width: 26rpx;
+						height: 32rpx;
+					}
+				}
+			}
+		}
+
+		.tabs-list {
+			padding: 0 24rpx;
+			margin-bottom: 20rpx;
+		}
+
+		.swiper-box {
+			padding: 0 24rpx;
+			margin-bottom: 20rpx;
+		}
+
+		.search-box {
+			width: 702rpx;
+			height: 72rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 0 auto 20rpx auto;
+			padding-left: 24rpx;
+
+			.input {
+				flex: 1;
+				margin: 0 10rpx;
+			}
+
+			.text {
+				padding: 0 24rpx;
+				border-left: 1rpx solid #E5E7ED;
+				line-height: 36rpx;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #1E7DFF;
+			}
+		}
+
+		.renqi-box {
+			width: 750rpx;
+			height: 56rpx;
+			background: rgba(256, 256, 256, 0.1);
+			padding: 0 24rpx;
+			margin-bottom: 24rpx;
+
+			.text {
+				font-size: 24rpx;
+				font-family: Helvetica;
+				color: #FFFFFF;
+				margin-left: 10rpx;
+			}
+		}
+
+		.nav-left {
+			padding: 0 20rpx;
+
+			.text {
+				font-size: 32rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #FFFFFF;
+				margin-left: 5rpx;
+			}
+		}
+
+		.index-bg {
+			width: 750rpx;
+			height: 732rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			position: absolute;
+			top: 1;
+			left: 0;
+			z-index: -1;
+		}
+	}
+
+</style>

+ 338 - 0
pages/index/info.vue

@@ -0,0 +1,338 @@
+<template>
+	<view class="info1">
+		<view class="info1-title">
+			基本情况
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">备案名称</text>
+			<text class="u-flex-1">{{detail.record_name}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">销售状态</text>
+			<text class="u-flex-1">{{detail.sale_state}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">售楼位置</text>
+			<view class="u-flex u-col-top">
+				<u-icon name="map-fill" color="#1F7EFF" size="30" style="margin-top: 2rpx;margin-right: 10rpx;"></u-icon>
+				<text class="u-flex-1">{{detail.sales_office}}</text>
+			</view>
+		</view>
+		<!-- <view class="u-flex info1-row">
+			<text class="text">楼盘位置</text>
+			<view class="u-flex u-col-top">
+				<u-icon name="map-fill" color="#1F7EFF" size="30" style="margin-top: 2rpx;margin-right: 10rpx;"></u-icon>
+				<text class="u-flex-1">上城区三关塘路与华家竹园路交汇处向西200米</text>
+			</view>
+		</view> -->
+		<view class="u-flex info1-row">
+			<text class="text">装修状况</text>
+			<text class="u-flex-1">{{detail.fitment_state}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">交房时间</text>
+			<text class="u-flex-1">{{detail.delivery_time}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">建筑层高</text>
+			<text class="u-flex-1">{{detail.floor_height}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">户型面积</text>
+			<text class="u-flex-1">{{detail.house_min_area}}㎡-{{detail.house_max_area}}㎡</text>
+		</view>
+		<view class="u-flex info1-row" v-if="detail.trait">
+			<text class="text">楼盘类型</text>
+			<text class="u-flex-1" v-if="detail.trait.length > 0">{{detail.trait.join(' ')}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">物业费用</text>
+			<text class="u-flex-1">{{detail.property_cost}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">物业公司</text>
+			<text class="u-flex-1">{{detail.property_company}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">开盘时间</text>
+			<text class="u-flex-1">{{detail.sale_time}}</text>
+		</view>
+		<view class="xian"></view>
+		<view class="info1-title">
+			总体情况
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">项目名称</text>
+			<text class="u-flex-1">{{detail.name}}</text>
+		</view>
+		<!-- <view class="u-flex info1-row">
+			<text class="text">项目简介</text>
+			<text class="u-flex-1">上城区三关塘路与华家竹园路交汇处向西200米</text>
+		</view> -->
+		<view class="u-flex info1-row">
+			<text class="text">项目地址</text>
+			<view class="u-flex u-col-top" @click="tomap">
+				<u-icon name="map-fill" color="#1F7EFF" size="30" style="margin-top: 2rpx;margin-right: 10rpx;"></u-icon>
+				<text class="u-flex-1">{{detail.project_address}}</text>
+			</view>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">总户数量</text>
+			<text class="u-flex-1">{{detail.resident_num}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">车位配比</text>
+			<text class="u-flex-1">{{detail.stall_ratio}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">总栋数</text>
+			<text class="u-flex-1">{{detail.tower_num}}栋</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">绿化率</text>
+			<text class="u-flex-1">{{detail.greening_rate}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">容积率</text>
+			<text class="u-flex-1">{{detail.plot_ratio}}%</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">占地面积</text>
+			<text class="u-flex-1">{{detail.floor_area}}</text>
+		</view>
+		<view class="u-flex info1-row">
+			<text class="text">总建面积</text>
+			<text class="u-flex-1">{{detail.covered_area}}</text>
+		</view>
+		<view class="xian"></view>
+		<view class="info1-title">
+			楼盘简介
+		</view>
+		<view class="jianjie">
+			{{detail.project_synopsis}}
+		</view>
+		<view class="" style="height: 170rpx;"></view>
+		<view class="houses-btn u-flex u-row-between">
+			<text @click="opentel">隐私电话</text>
+			<text @click="tochat">在线咨询</text>
+		</view>
+		<u-popup v-model="showtocall" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">您是否进行呼叫?</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showtocall = false">取消</text>
+					<text @click="totel">确定</text>
+				</view>
+			</view>
+		</u-popup>
+		<u-popup v-model="showlogin" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">你目前处于未登录状态请前往登录</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showlogin = false">取消</text>
+					<text @click="tologin">确定</text>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	export default {
+
+		data() {
+			return {
+				showtocall: false,
+				id: '',
+				detail: {},
+				showlogin: false
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			this.getdata()
+		},
+		methods: {
+			tomap() {
+				uni.openLocation({
+					latitude: Number(this.detail.latitude),
+					longitude: Number(this.detail.longitude)
+				})
+			},
+			tologin() {
+				uni.switchTab({
+					url: "/pages/mine/mine"
+				})
+			},
+			opentel() {
+				if (uni.getStorageSync("token")) {
+					this.showtocall = true
+				} else {
+					this.showlogin = true
+				}
+			},
+			getdata() {
+				this.$u.get('/api/Property/property_detail', {
+					id: this.id
+				}).then(res => {
+					this.detail = res.data
+				})
+			},
+			totel() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 1
+					})
+					uni.makePhoneCall({
+						phoneNumber: this.detail.privacy_phone
+					})
+				} else {
+					this.showlogin = true
+				}
+			},
+			tochat() {
+				if (uni.getStorageSync("token")) {
+					this.$u.post('/api/Member/consult_record', {
+						type: 2
+					})
+					uni.navigateTo({
+						url: "./chat?hx_username=" + this.detail.hx_username + "&worker_id=" + this.detail.worker_id
+					})
+				} else {
+					this.showlogin = true
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	.info1 {
+		padding: 16rpx 24rpx;
+
+		.tocall-box {
+			position: relative;
+
+			.tocall-img {
+				width: 650rpx;
+				height: 476rpx;
+			}
+
+			.tocall-btn {
+				position: absolute;
+				bottom: 70rpx;
+				left: 0;
+				width: 100%;
+				padding: 0 48rpx;
+
+				text:first-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #FFA120;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+
+				text:last-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: #1F7EFF;
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+			}
+
+			.tocall-text {
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+				position: absolute;
+				top: 244rpx;
+				left: 0;
+				z-index: 10;
+				width: 100%;
+			}
+		}
+
+		.houses-btn {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			width: 750rpx;
+			height: 166rpx;
+			background: #FFFFFF;
+			padding: 0 24rpx 54rpx 24rpx;
+			z-index: 1;
+
+			text:first-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #FFA120;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+
+			text:last-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #1F7EFF;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+		}
+
+		.xian {
+			width: 702rpx;
+			height: 2rpx;
+			background: #CCCCCC;
+			margin: 16rpx auto;
+		}
+
+		.info1-row {
+			margin-bottom: 24rpx;
+			font-size: 24rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #666666;
+
+			.text {
+				width: 140rpx;
+			}
+		}
+
+		.jianjie {
+			font-size: 24rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #666666;
+		}
+
+		.info1-title {
+			padding: 24rpx 0;
+			font-size: 28rpx;
+			font-family: PingFangSC-Medium, PingFang SC;
+			font-weight: 500;
+			color: #333333;
+		}
+	}
+</style>

+ 75 - 0
pages/index/news-info.vue

@@ -0,0 +1,75 @@
+<template>
+	<view class="news-info">
+		<web-view v-if="detail.url" :src="detail.url"></web-view>
+		<view v-if="detail.id && !detail.url">
+			<view class="news-title">
+				{{detail.title}}
+			</view>
+			<view class="u-flex news-header">
+				<image :src="detail.head_image" class="head" mode=""></image>
+				<text class="name">{{detail.source}} {{detail.create_at}} 发表于{{detail.publish}}</text>
+			</view>
+			<u-parse :html="detail.content" :show-with-animation="true" :lazy-load="true"></u-parse>
+			<!-- <view class="" v-html="detail.content"></view> -->
+		</view>
+
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				id: '',
+				detail: {}
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			this.getdata()
+		},
+		methods: {
+			getdata() {
+				this.$u.post('/api/Headline/headline_detail', {
+					id: this.id
+				}).then(res => {
+					this.detail = res.data
+					const regex = new RegExp('<img', 'gi')
+					this.detail.content = res.data.content.replace(regex, `<img style="max-width: 100%; height: auto"`)
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.news-info {
+		padding: 24rpx;
+
+		.news-header {
+			margin-bottom: 40rpx;
+
+			.head {
+				width: 36rpx;
+				height: 36rpx;
+				border-radius: 100rpx;
+				margin-right: 10rpx;
+			}
+
+			.name {
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+			}
+		}
+
+		.news-title {
+			font-size: 36rpx;
+			font-family: PingFangSC-Medium, PingFang SC;
+			font-weight: 500;
+			color: #333333;
+			margin-bottom: 24rpx;
+		}
+	}
+</style>

+ 166 - 0
pages/index/news.vue

@@ -0,0 +1,166 @@
+<template>
+	<view class="newHouses">
+		<view class="newHouses-bg"></view>
+		<view class="renqi-box u-flex">
+			<u-icon name="heart-fill" color="#fff" size="26"></u-icon>
+			<text class="text">累计人气值 {{popularity}}</text>
+		</view>
+		<view class="swiper-box">
+			<u-swiper :list="swiperlisi" height="376" name="logo" @click="tobanner" border-radius="20"></u-swiper>
+		</view>
+		<view class="newHouses-list">
+			<view class="newHouses-title u-flex u-row-between">
+				<text>全部楼盘</text>
+				<image src="../../static/images/shaixuan.png" @click="opensearch" mode=""></image>
+			</view>
+			<view class="u-flex u-row-between u-flex-wrap">
+				<view v-for="(item,index) in list" :key="index">
+					<gf-goods :data="item"></gf-goods>
+				</view>
+			</view>
+		</view>
+		<gf-search ref="search" @shaixuan="shaixuan"></gf-search>
+	</view>
+</template>
+
+<script>
+	import {
+		mapState,
+		mapActions,
+		mapMutations,
+		mapGetters
+	} from 'vuex'
+	export default {
+		data() {
+			return {
+				title: '',
+				swiperlisi: [],
+				page: 1,
+				list: [],
+				params: {}
+			}
+		},
+		onLoad(options) {
+			this.title = options.title
+			this.getlist()
+			this.getbanner()
+		},
+		onShow() {
+			if(this.title!=''){
+				uni.setNavigationBarTitle({
+					title: this.title //这是修改后的导航栏文字
+				})
+			}
+		},
+		computed: {
+			...mapState(["popularity"])
+		},
+		onReachBottom() {
+			if (this.list.length % 20 == 0) {
+				this.page++
+				this.getlist()
+			}
+		},
+		methods: {
+			shaixuan(e) {
+				this.params = e
+				this.page = 1
+				this.list = []
+				this.getlist()
+			},
+			tobanner(e) {
+				if (this.swiperlisi[e].url) {
+					uni.navigateTo({
+						url: this.swiperlisi[e].url
+					})
+				}
+			},
+			getbanner() {
+				this.$u.post('/api/Index/banner', {
+					location: 2
+				}).then(res => {
+					this.swiperlisi = res.data
+				})
+			},
+			getlist() {
+				console.log(this.params);
+				this.$u.post('/api/Index/property_list', {
+					page: this.page,
+					page_num: 20,
+					cate_name: this.title,
+					...this.params
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			},
+			opensearch() {
+				this.$refs.search.show = true
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.newHouses {
+		position: relative;
+		z-index: 1;
+
+		.newHouses-list {
+			width: 702rpx;
+			background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 38%, rgba(255, 255, 255, 0) 100%);
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx;
+
+			.newHouses-title {
+				padding: 20rpx 0;
+
+				text {
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #131415;
+				}
+
+				image {
+					width: 32rpx;
+					height: 32rpx;
+				}
+			}
+		}
+
+		.swiper-box {
+			padding: 0 24rpx;
+			margin-bottom: 20rpx;
+		}
+
+		.renqi-box {
+			width: 750rpx;
+			height: 56rpx;
+			background: rgba(256, 256, 256, 0.1);
+			padding: 0 24rpx;
+			margin-bottom: 24rpx;
+
+			.text {
+				font-size: 24rpx;
+				font-family: Helvetica;
+				color: #FFFFFF;
+				margin-left: 10rpx;
+			}
+		}
+
+		.newHouses-bg {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 750rpx;
+			height: 556rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			z-index: -1;
+		}
+	}
+</style>

+ 392 - 0
pages/index/pk-info.vue

@@ -0,0 +1,392 @@
+<template>
+	<view class="pk-info">
+		<scroll-view scroll-y="true" scroll-x="true" class="pk-scroll-w">
+			<view class="pk-scroll-w-box">
+				<view class="pk-header u-flex-col u-col-center">
+					<view class="pk-left-w">
+						<view class="pk-left">
+							<image src="../../static/images/pk.png" mode="aspectFit"></image>
+						</view>
+					</view>
+					<view class="pk-item" v-for="(item,index) in list" :key="index">
+						<image :src="item.logo" class="pk-item-img" mode=""></image>
+						<view class="pk-item-box">
+							<view class="item-name u-line-2">
+								{{item.name}}
+							</view>
+							<view class="label-box u-flex">
+								<text v-for="(a,b) in item.trait" :key="b" v-if="b < 2">{{a}}</text>
+							</view>
+							<view class="price-box u-flex">
+								<text>参考均价</text>
+								<text>{{item.avg_price}}/㎡</text>
+							</view>
+						</view>
+					</view>
+				</view>
+				<view class="pk-title u-flex">
+					<text>基本情况</text>
+				</view>
+				<view class="pk-table">
+					<view class="pk-table-row u-flex" v-for="(item,index) in jibenlist" :key="index">
+						<view class="row-left u-flex">
+							<text>{{item.title}}</text>
+						</view>
+						<text class="row-text" v-for="(a,b) in list" :key="b" :style="{width:b == 0 ? '276rpx' : '264rpx'}">{{list[b][item.value]}}</text>
+					</view>
+				</view>
+				<view class="pk-title u-flex">
+					<text>总体情况</text>
+				</view>
+				<view class="pk-table">
+					<view class="pk-table-row u-flex" v-for="(item,index) in zongtilist" :key="index">
+						<view class="row-left u-flex">
+							<text>{{item.title}}</text>
+						</view>
+						<text class="row-text" v-for="(a,b) in list" :key="b" :style="{width:b == 0 ? '276rpx' : '264rpx'}">{{list[b][item.value]}}</text>
+					</view>
+				</view>
+				<view class="pk-title u-flex">
+					<text>项目介绍</text>
+				</view>
+				<view class="pk-table">
+					<view class="pk-table-row u-flex">
+						<view class="row-left u-flex">
+							<text>项目简介</text>
+						</view>
+						<text class="row-text" v-for="(a,b) in list" :key="b" :style="{width:b == 0 ? '276rpx' : '264rpx'}">{{a.project_synopsis}}</text>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				list: [],
+				jibenlist: [{
+						title: '备案名',
+						value: 'record_name'
+					},
+					{
+						title: '销售状态',
+						value: 'sale_state'
+					},
+					{
+						title: '开盘时间',
+						value: 'sale_time'
+					},
+					{
+						title: '物业类别',
+						value: 'property_class'
+					},
+					{
+						title: '产权年限',
+						value: 'property_deadline'
+					},
+					{
+						title: '装修状况',
+						value: 'fitment_state'
+					},
+					{
+						title: '物业公司',
+						value: 'property_company'
+					},
+					{
+						title: '交房时间',
+						value: 'delivery_time'
+					},
+					{
+						title: '项目地址',
+						value: 'project_address'
+					}
+				],
+				zongtilist: [{
+						title: '占地面积(㎡)',
+						value: 'floor_area'
+					},
+					{
+						title: '总占地面积(㎡)',
+						value: 'covered_area'
+					},
+					{
+						title: '容积率',
+						value: 'plot_ratio'
+					},
+					{
+						title: '绿化率(%)',
+						value: 'greening_rate'
+					},
+					{
+						title: '总栋数',
+						value: 'tower_num'
+					},
+					{
+						title: '总户数',
+						value: 'resident_num'
+					},
+					{
+						title: '层高',
+						value: 'floor_height'
+					},
+					{
+						title: '户型面积',
+						value: 'house_type_area'
+					},
+					{
+						title: '总车位数',
+						value: 'stall_num'
+					},
+					{
+						title: '车位配比',
+						value: 'stall_ratio'
+					},
+					{
+						title: '人车分流',
+						value: 'car_shunt'
+					},
+					{
+						title: '物业费用',
+						value: 'property_cost'
+					},
+					{
+						title: '学校',
+						value: 'school'
+					},
+					{
+						title: '小区内配套',
+						value: 'plot_mating'
+					},
+					{
+						title: '外立面',
+						value: 'external_wall'
+					},
+					{
+						title: '楼层状况',
+						value: 'floor_case'
+					},
+					{
+						title: '预售许可证',
+						value: 'permit_presale'
+					},
+					{
+						title: '开发商',
+						value: 'developers'
+					},
+					{
+						title: '售楼中心地址',
+						value: 'sales_office'
+					}
+				],
+				ids: []
+			}
+		},
+		onLoad(option) {
+			this.ids = JSON.parse(option.ids)
+			this.getdata()
+		},
+		methods: {
+			getdata() {
+				this.$u.post('/api/Property/property_comparison',{
+					id_info:this.ids.join(",")
+				}).then(res => {
+					this.list = res.data
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.pk-info {
+		.pk-scroll-w {
+			width: 750rpx;
+			height: 100vh;
+
+			.pk-scroll-w-box {
+				padding: 0 24rpx 24rpx 0;
+
+				.pk-table {
+					margin-bottom: 24rpx;
+
+					.pk-table-row {
+						align-items: stretch;
+						white-space: nowrap;
+
+						.row-text {
+							min-height: 82rpx;
+							padding: 24rpx 40rpx;
+							width: 264rpx;
+							border-left: 2rpx solid #fff;
+							border-bottom: 2rpx solid #fff;
+							background-color: #F5F5F5;
+							font-size: 24rpx;
+							font-family: PingFangSC-Regular, PingFang SC;
+							font-weight: 400;
+							color: #333333;
+							display: inline-block;
+							flex-shrink: 0;
+							white-space: pre-wrap;
+						}
+
+						.row-left {
+							padding-left: 24rpx;
+							background-color: #fff;
+							border-bottom: 2rpx solid #fff;
+							min-height: 82rpx;
+							display: inline-flex;
+							flex-shrink: 0;
+							position: sticky;
+							left: 0;
+
+							text {
+								width: 124rpx;
+								background: #CCCCCC;
+								padding: 24rpx 14rpx;
+								height: 100%;
+								font-size: 24rpx;
+								font-family: PingFangSC-Regular, PingFang SC;
+								font-weight: 400;
+								color: #333333;
+								text-align: center;
+								white-space: pre-wrap;
+							}
+						}
+					}
+				}
+
+				.pk-title {
+					padding: 0 0 20rpx 0;
+					font-size: 28rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #333333;
+
+					text {
+						padding: 0 24rpx;
+						position: sticky;
+						left: 0;
+					}
+				}
+
+				.pk-header {
+					white-space: nowrap;
+					position: sticky;
+					top: 0;
+					z-index: 100;
+					background-color: #fff;
+					// padding: 24rpx 0 24rpx 24rpx;
+					writing-mode: vertical-lr;
+
+					.pk-item {
+						display: inline-block;
+						width: 240rpx;
+						height: 320rpx;
+						background: #FFFFFF;
+						box-shadow: 0px 4rpx 8rpx 0px rgba(0, 0, 0, 0.1);
+						border-radius: 8rpx;
+						margin-left: 24rpx;
+						flex-shrink: 0;
+						writing-mode: horizontal-tb;
+
+						.pk-item-img {
+							width: 240rpx;
+							height: 160rpx;
+						}
+
+						.pk-item-box {
+							padding: 0 12rpx;
+
+							.price-box {
+								margin-bottom: 8rpx;
+
+								text:first-child {
+									font-size: 18rpx;
+									font-family: PingFangSC-Regular, PingFang SC;
+									font-weight: 400;
+									color: #999999;
+									margin-right: 8rpx;
+								}
+
+								text:last-child {
+									font-size: 18rpx;
+									font-family: DINAlternate-Bold, DINAlternate;
+									font-weight: bold;
+									color: #FF3B30;
+								}
+							}
+
+							.label-box {
+								margin-bottom: 8rpx;
+
+								text {
+									line-height: 32rpx;
+									background: #F5F5F5;
+									border-radius: 8rpx;
+									padding: 0 12rpx;
+									font-size: 18rpx;
+									font-family: PingFangSC-Regular, PingFang SC;
+									font-weight: 400;
+									color: #999999;
+									margin-right: 10rpx;
+									max-width: 50%;
+									overflow: hidden;
+									text-overflow:ellipsis;
+									white-space: nowrap;
+								}
+							}
+
+							.item-name {
+								height: 68rpx;
+								font-size: 24rpx;
+								font-family: PingFangSC-Regular, PingFang SC;
+								font-weight: 400;
+								color: #131415;
+								margin-bottom: 8rpx;
+								white-space: pre-wrap;
+							}
+						}
+					}
+
+					.pk-left-w {
+						position: sticky;
+						left: 0;
+						background-color: #fff;
+						padding: 24rpx 0 24rpx 24rpx;
+						width: 124rpx;
+						box-sizing: content-box;
+
+						.pk-left {
+
+							width: 124rpx;
+							height: 320rpx;
+							background: linear-gradient(132deg, #FFDD7B 0%, #FFA120 100%);
+							box-shadow: 0px 4rpx 8rpx 0rpx rgba(0, 0, 0, 0.1);
+							border-radius: 8px;
+							display: inline-block;
+
+							image {
+								width: 124rpx;
+								height: 320rpx;
+							}
+						}
+					}
+
+				}
+
+				.u-flex-stretch {
+					align-items: stretch;
+				}
+
+				.pk-scroll-w-left {
+					width: 124rpx;
+					height: 100%;
+					background-color: red;
+				}
+			}
+		}
+	}
+</style>

+ 282 - 0
pages/index/pk-list.vue

@@ -0,0 +1,282 @@
+<template>
+	<view class="pk-list">
+		<view class="index-bg"></view>
+		<view class="search-box u-flex u-row-between" @click="tosearch">
+			<u-icon name="search" color="#CCCCCC" size="36"></u-icon>
+			<input type="text" placeholder="搜索楼盘" class="input" :disabled="true">
+			<text class="text">搜索</text>
+		</view>
+		<view class="pk-box">
+			<view class="box-title">
+				全部楼盘
+			</view>
+			<u-checkbox-group active-color="#1E7DFF" shape="circle" @change="changepk">
+				<view class="pk-item u-flex u-row-between" v-for="(item,index) in list" :key="index">
+					<image :src="item.logo" class="pk-img" mode="aspectFill"></image>
+					<view class="pk-center u-flex-1">
+						<u-icon name="trash" class="del-btn" @click="del(index)"></u-icon>
+						<view class="item-name u-line-1">
+							{{item.name}}
+						</view>
+						<view class="item-tips">
+							<text class="u-line-1" style="white-space: nowrap;" v-if="item.house_type">{{item.house_type.join('·')}}·</text>
+							<text style="white-space: nowrap;">{{item.house_min_area || 0}}-{{item.house_max_area || 0}}㎡</text>
+						</view>
+						<view class="item-tips">
+							{{item.area}}
+						</view>
+						<view class="price-box u-flex">
+							<text>参考均价</text>
+							<text>{{item.avg_price}}/㎡</text>
+						</view>
+						<view class="u-flex label-box">
+							<text v-for="(a,b) in item.trait" :key="b" v-if="b < 2">{{a}}</text>
+						</view>
+					</view>
+					<u-checkbox :name="item.id" v-model="item.checked"></u-checkbox>
+				</view>
+			</u-checkbox-group>
+			<view style="height: 60vh;" v-if="list.length == 0">
+				<u-empty text="请点击上方搜索楼盘" mode="list"></u-empty>
+			</view>
+		</view>
+		<view class="" style="height: 170rpx;"></view>
+		<view class="pk-down-btn u-flex u-row-between">
+			<text @click="clear">重置</text>
+			<text @click="toinfo">PK</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				value: false,
+				list: [],
+				ids: []
+			}
+		},
+		onShow() {
+			this.list = uni.getStorageSync("pklist") || []
+			this.list.forEach(val => {
+				val.checked = false
+			})
+			this.list = JSON.parse(JSON.stringify(this.list))
+		},
+		methods: {
+			del(index) {
+				uni.showModal({
+					content: "确定删除?",
+					success: (e) => {
+						if (e.confirm) {
+							this.$u.toast("删除成功")
+							this.list.splice(index, 1)
+							uni.setStorageSync("pklist", this.list)
+							this.clear()
+						}
+					}
+				})
+			},
+			clear() {
+				this.ids = []
+				this.list.forEach(val => {
+					val.checked = false
+				})
+			},
+			changepk(e) {
+				this.ids = e
+			},
+			toinfo() {
+				if (this.ids.length < 2) {
+					this.$u.toast("至少选择两项")
+					return
+				}
+				if (this.ids.length > 3) {
+					this.$u.toast("至多选择三项")
+					return
+				}
+				uni.navigateTo({
+					url: "./pk-info?ids=" + JSON.stringify(this.ids)
+				})
+			},
+			tosearch() {
+				uni.navigateTo({
+					url: "./pk-search"
+				})
+			}
+		}
+
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.pk-list {
+		position: relative;
+		z-index: 1;
+		padding: 1rpx 0;
+
+		.pk-down-btn {
+			width: 750rpx;
+			height: 166rpx;
+			background: #FFFFFF;
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			padding: 0 24rpx 54rpx 24rpx;
+			z-index: 10;
+
+			text:first-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #B2B2B2;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+
+			text:last-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #FFA120;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+		}
+
+		.pk-box {
+			width: 702rpx;
+			// height: 1760rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 0 auto 20rpx auto;
+			padding: 0 20rpx;
+
+			.pk-item {
+				margin-bottom: 32rpx;
+				position: relative;
+
+				.del-btn {
+					position: absolute;
+					top: 20rpx;
+					right: 0;
+					z-index: 10;
+				}
+
+				.pk-img {
+					width: 280rpx;
+					height: 240rpx;
+					border-radius: 20rpx;
+					margin-right: 20rpx;
+				}
+
+				.pk-center {
+
+
+					.label-box {
+						text {
+							margin-right: 10rpx;
+							padding: 0 12rpx;
+							line-height: 40rpx;
+							height: 40rpx;
+							background: #F5F5F5;
+							border-radius: 8rpx;
+							font-size: 18rpx;
+							font-family: PingFangSC-Regular, PingFang SC;
+							font-weight: 400;
+							color: #999999;
+						}
+					}
+
+					.price-box {
+						margin-bottom: 8rpx;
+
+						text:first-child {
+							font-size: 20rpx;
+							font-family: PingFangSC-Regular, PingFang SC;
+							font-weight: 400;
+							color: #999999;
+							margin-right: 8rpx;
+						}
+
+						text:last-child {
+							font-size: 32rpx;
+							font-family: DINAlternate-Bold, DINAlternate;
+							font-weight: bold;
+							color: #FF3B30;
+						}
+					}
+
+					.item-name {
+						font-size: 28rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #131415;
+						margin: 10rpx 0;
+					}
+
+					.item-tips {
+						font-size: 18rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+						margin-bottom: 12rpx;
+					}
+				}
+			}
+
+			.box-title {
+				padding: 24rpx 0;
+				font-size: 34rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #131415;
+			}
+		}
+
+		.search-box {
+			width: 702rpx;
+			height: 72rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 24rpx auto;
+			padding-left: 24rpx;
+
+			.input {
+				flex: 1;
+				margin: 0 10rpx;
+			}
+
+			.text {
+				padding: 0 24rpx;
+				border-left: 1rpx solid #E5E7ED;
+				line-height: 36rpx;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #1E7DFF;
+			}
+		}
+
+		.index-bg {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 750rpx;
+			height: 556rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			z-index: -1;
+		}
+	}
+</style>

+ 183 - 0
pages/index/pk-search.vue

@@ -0,0 +1,183 @@
+<template>
+	<view class="pk-search">
+		<view class="search-box u-flex u-row-between">
+			<u-icon name="search" color="#CCCCCC" size="36"></u-icon>
+			<input type="text" placeholder="搜索楼盘" class="input" v-model="keyword1">
+			<text class="text" @click="tosearch">搜索</text>
+		</view>
+		<view class="lishi-box">
+			<view class="lishi-title">
+				历史搜索
+			</view>
+			<view class="u-flex u-flex-wrap">
+				<text v-for="(item,index) in lishilist" :key="index" class="lishi-item" @click="lishisearch(item)">{{item}}</text>
+			</view>
+		</view>
+		<view class="search-list">
+			<view class="search-item u-flex u-row-between" v-for="(item,index) in list" :key="index">
+				<text>{{item.name}}</text>
+				<text @click="addpk(item)">添加</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				lishilist: [],
+				keyword:'',
+				keyword1:'',
+				page:1,
+				list:[]
+			}
+		},
+		onLoad() {
+			this.lishilist = uni.getStorageSync("lishi") || []
+			this.getlist()
+		},
+		onReachBottom() {
+			if(this.list.length % 20 == 0){
+				this.page++
+				this.getlist()
+			}
+		},
+		methods: {
+			lishisearch(item){
+				this.keyword = item
+				this.keyword1 = item
+				this.page = 1
+				this.list = []
+				this.getlist()
+			},
+			addpk(item){
+				var list = uni.getStorageSync("pklist") || []
+				var index = list.findIndex(value => value.id == item.id)
+				console.log(index);
+				if(index == -1){
+					list.unshift(item)
+					uni.setStorageSync("pklist",list)
+					uni.navigateBack()
+				}else{
+					uni.showModal({
+						content:"该楼盘已添加!"
+					})
+				}
+			},
+			setlishi() {
+				if (this.keyword) {
+					if (this.lishilist.indexOf(this.keyword) == -1) {
+						this.lishilist.unshift(this.keyword)
+						uni.setStorageSync("lishi", this.lishilist)
+					}
+				}
+			},
+			tosearch() {
+				this.keyword = this.$u.trim(this.keyword1)
+				this.setlishi()
+				this.page = 1
+				this.list = []
+				this.getlist()
+			},
+			getlist(){
+				this.$u.post('/api/Index/property_list', {
+					page: this.page,
+					page_num: 20,
+					keyword: this.keyword
+				}).then(res => {
+					if (this.page == 1) {
+						this.list = res.data
+					} else {
+						this.list = this.list.concat(res.data)
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.pk-search {
+		.search-list {
+			padding: 0 24rpx;
+
+			.search-item {
+				padding: 18rpx 0;
+				border-bottom: 1rpx solid #F2F2F2;
+
+				text:first-child {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+				}
+
+				text:last-child {
+					width: 80rpx;
+					line-height: 46rpx;
+					background: #1F7EFF;
+					border-radius: 8rpx;
+					text-align: center;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #FFFFFF;
+				}
+			}
+		}
+
+		.lishi-box {
+			padding: 0 24rpx;
+
+			.lishi-item {
+				line-height: 44rpx;
+				background: #E5E5E5;
+				border-radius: 22rpx;
+				padding: 0 30rpx;
+				font-size: 18rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+				margin-bottom: 20rpx;
+				margin-right: 20rpx;
+			}
+
+			.lishi-title {
+				font-size: 24rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #333333;
+				margin-bottom: 14rpx;
+			}
+		}
+
+		.search-box {
+			width: 702rpx;
+			height: 72rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 24rpx auto;
+			padding-left: 24rpx;
+			border: 2rpx solid #CCCCCC;
+			position: sticky;
+			top: 0;
+			left: 0;
+
+			.input {
+				flex: 1;
+				margin: 0 10rpx;
+			}
+
+			.text {
+				padding: 0 24rpx;
+				border-left: 1rpx solid #E5E7ED;
+				line-height: 36rpx;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #1E7DFF;
+			}
+		}
+	}
+</style>

+ 214 - 0
pages/index/search.vue

@@ -0,0 +1,214 @@
+<template>
+	<view class="search1">
+		<view class="search-box u-flex u-row-between">
+			<u-icon name="search" color="#CCCCCC" size="36"></u-icon>
+			<input type="text" placeholder="搜索楼盘" class="input" v-model="keyword1">
+			<text class="text" @click="tosearch">搜索</text>
+		</view>
+		<view class="lishi-box" v-if="list.length == 0">
+			<view class="lishi-title">
+				历史搜索
+			</view>
+			<view class="u-flex u-flex-wrap">
+				<text v-for="(item,index) in lishilist" :key="index" class="lishi-item" @click="lishisearch(item)">{{item}}</text>
+			</view>
+			<!-- <view class="news-item u-flex u-row-between" v-for="(item,index) in 2" :key="index" @click="toinfo">
+				<image src="https://dummyimage.com/140x110" class="image" mode=""></image>
+				<view class="item-right">
+					<view class="name u-line-2">
+						已取证!差价1.5W+/㎡!戏水池、会所、嵌入式冰箱,冠军推荐!
+					</view>
+					<view class="u-flex u-row-between">
+						<view class="time u-flex">
+							<u-icon name="eye-fill" color="#CCCCCC" size="26"></u-icon>
+							<text class="look">12345人浏览</text>
+							<text class="time-text">2023-03-25</text>
+						</view>
+						<view class="user u-flex">
+							<image src="https://dummyimage.com/24x24" class="head" mode=""></image>
+							<text class="user-name">温州购房通</text>
+						</view>
+					</view>
+				</view>
+			</view> -->
+		</view>
+		<view class="list-box" style="padding: 0 24rpx;">
+			<view class="list-item" v-for="(item,index) in list" :key="index">
+				<gf-goods :data="item" type="1"></gf-goods>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { mapState } from "vuex"
+	export default {
+		data() {
+			return {
+				lishilist: [],
+				keyword1: '',
+				keyword: '',
+				page: 1,
+				list: []
+			}
+		},
+		onLoad() {
+			this.lishilist = uni.getStorageSync("lishi") || []
+		},
+		onShow() {
+			uni.setNavigationBarTitle({
+				title: this.config.store_title
+			})
+		},
+		computed: {
+			...mapState(['config', 'city'])
+		},
+		onReachBottom() {
+			if(this.list.length % 20 == 0){
+				this.page++
+				this.getlist()
+			}
+		},
+		methods: {
+			lishisearch(item){
+				this.keyword = item
+				this.keyword1 = item
+				this.page = 1
+				this.list = []
+				this.getlist()
+			},
+			tosearch() {
+				this.keyword = this.$u.trim(this.keyword1)
+				this.setlishi()
+				this.getlist()
+			},
+			setlishi() {
+				if (this.keyword) {
+					if (this.lishilist.indexOf(this.keyword) == -1) {
+						this.lishilist.unshift(this.keyword)
+						uni.setStorageSync("lishi", this.lishilist)
+					}
+				}
+			},
+			getlist() {
+				this.$u.post('/api/Index/property_list', {
+					page: this.page,
+					page_num: 20,
+					keyword: this.keyword
+				}).then(res => {
+					if (this.page == 1) {
+						this.list = res.data
+					} else {
+						this.list = this.list.concat(res.data)
+					}
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	.search1 {
+		.lishi-box {
+			padding: 0 24rpx;
+
+			.news-item {
+				padding: 32rpx 0;
+				border-bottom: 2rpx solid #CCCCCC;
+
+				.image {
+					width: 140rpx;
+					height: 110rpx;
+					border-radius: 10rpx;
+				}
+
+				.item-right {
+					flex: 1;
+					margin-left: 14rpx;
+
+					.name {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #333333;
+						margin-bottom: 16rpx;
+					}
+
+					.user {
+						.user-name {
+							font-size: 18rpx;
+							font-family: PingFangSC-Regular, PingFang SC;
+							font-weight: 400;
+							color: #999999;
+						}
+
+						.head {
+							width: 24rpx;
+							height: 24rpx;
+							border-radius: 100rpx;
+							margin-right: 10rpx;
+						}
+					}
+
+					.time {
+						font-size: 18rpx;
+						font-weight: 400;
+						color: #999999;
+
+						.look {
+							margin: 0 10rpx;
+						}
+					}
+				}
+			}
+
+			.lishi-item {
+				line-height: 44rpx;
+				background: #E5E5E5;
+				border-radius: 22rpx;
+				padding: 0 30rpx;
+				font-size: 18rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+				margin-bottom: 20rpx;
+				margin-right: 20rpx;
+			}
+
+			.lishi-title {
+				font-size: 24rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #333333;
+				margin-bottom: 14rpx;
+			}
+		}
+
+		.search-box {
+			width: 702rpx;
+			height: 72rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 24rpx auto;
+			padding-left: 24rpx;
+			border: 2rpx solid #CCCCCC;
+
+			.input {
+				flex: 1;
+				margin: 0 10rpx;
+			}
+
+			.text {
+				padding: 0 24rpx;
+				border-left: 1rpx solid #E5E7ED;
+				line-height: 36rpx;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #1E7DFF;
+			}
+		}
+	}
+
+</style>

+ 113 - 0
pages/index/video.vue

@@ -0,0 +1,113 @@
+<template>
+	<view class="video1 u-flex u-row-between u-flex-wrap">
+		<view class="video-item" v-for="(item,index) in list" :key="index" @click="playmp4(item)">
+			<view class="image-box">
+				<image :src="item.logo" class="image" mode="aspectFill"></image>
+				<image src="../../static/images/video.png" class="play" mode=""></image>
+				<!-- <text class="text">03.25</text> -->
+			</view>
+			<view class="video-name u-line-1">
+				{{item.title}}
+			</view>
+			<view class="time">
+				{{item.create_at}}
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				id: '',
+				page: 1,
+				list: []
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			this.getdata()
+		},
+		onReachBottom() {
+			if(this.list.length % 20 == 0){
+				this.page++
+				this.getdata()
+			}
+		},
+		methods: {
+			playmp4(item){
+				uni.navigateTo({
+					url:"/pages/video/info?mp4=" + encodeURIComponent(item.video)
+				})
+			},
+			getdata() {
+				this.$u.post('/api/Property/video_list', {
+					id: this.id,
+					page: this.page,
+					page_num: 20
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.video1 {
+		padding: 24rpx;
+
+		.video-item {
+			width: 340rpx;
+			margin-bottom: 18rpx;
+
+			.video-name {
+				font-size: 20rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #333333;
+				margin: 14rpx 0;
+			}
+
+			.time {
+				font-size: 20rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #333333;
+			}
+
+			.image-box {
+				width: 340rpx;
+				height: 272rpx;
+				position: relative;
+
+				.play {
+					width: 64rpx;
+					height: 64rpx;
+					position: absolute;
+					top: 50%;
+					left: 50%;
+					transform: translate(-50%, -50%);
+				}
+
+				.image {
+					width: 340rpx;
+					height: 272rpx;
+					border-radius: 10rpx;
+				}
+
+				.text {
+					position: absolute;
+					z-index: 10;
+					right: 10rpx;
+					bottom: 10rpx;
+					font-size: 20rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #FFFFFF;
+				}
+			}
+		}
+	}
+</style>

+ 298 - 0
pages/mine/baobei-info.vue

@@ -0,0 +1,298 @@
+<template>
+	<view class="baobei-info">
+		<view class="info-header u-flex-col u-col-center u-row-center">
+			<view class="image-box u-flex u-row-between">
+				<view class="image u-flex u-row-center" v-for="(item,index) in tabs" :key="index">
+					<image src="../../static/images/jindu1.png" v-if="index < status" mode=""></image>
+					<image src="../../static/images/jindu.png" v-else mode=""></image>
+				</view>
+			</view>
+			<view class="jindu-box u-flex u-row-between">
+				<view class="item-bg" :style="{width:130 * status + 'rpx'}"></view>
+				<view class="item" v-for="(item,index) in tabs" :key="index">
+					<text :class="index < status ? 'text' : ''">{{item}}</text>
+				</view>
+			</view>
+		</view>
+		<u-gap bg-color="#F5F5F5" height="20"></u-gap>
+		<view class="" style="padding: 0 24rpx;width: 702rpx;margin: 0 auto;border-radius: 20rpx;background-color: #fff;">
+			<view class="broker-title">
+				基本信息
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>意向区域</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="text" placeholder="请选择您的意向区域" v-model="detail.area_name" class="input-right" :disabled="true">
+				<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>意向项目</text>
+				</view>
+				<input type="text" placeholder="请选择您的意向楼盘" v-model="detail.property_name" class="input-right" :disabled="true">
+				<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>客户姓名</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="text" placeholder="请输入" class="input-right" v-model="detail.client_name" :disabled="true">
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>联系方式</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="number" placeholder="请输入" class="input-right" v-model="detail.client_phone" :disabled="true">
+			</view>
+		</view>
+		<u-gap bg-color="#F5F5F5" height="20"></u-gap>
+		<view class="" style="padding: 0 24rpx;width: 702rpx;margin: 0 auto;border-radius: 20rpx;background-color: #fff;">
+			<view class="broker-title">
+				其他信息
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>带看人姓名</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="text" placeholder="请输入" class="input-right" v-model="detail.report_name" :disabled="true">
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>联系方式</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="number" placeholder="请输入" class="input-right" v-model="detail.report_phone" :disabled="true">
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>中介门店</text>
+					<!-- <text>*</text> -->
+				</view>
+				<input type="text" placeholder="请输入" class="input-right" v-model="detail.shop" :disabled="true">
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>预约看房日期</text>
+				</view>
+				<input type="text" placeholder="请选择看房日期" class="input-right" v-model="detail.time" :disabled="true">
+				<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+			</view>
+			<view class="broker-row u-flex">
+				<view class="broker-row-left u-flex">
+					<text>审核状态</text>
+				</view>
+				<view class="input-right">
+					<text style="color: rgba(240, 127, 48, 1);" v-if="detail.status == 1">审核中</text>
+					<text style="color: rgba(68, 220, 37, 1);" v-if="detail.status == 2">已通过</text>
+					<text style="color: rgba(220, 75, 37, 1);" v-if="detail.status == 3">审核失败</text>
+					<text style="color: rgba(204, 204, 204, 1)" v-if="detail.status == 4">已失效</text>
+				</view>
+			</view>
+		</view>
+		<u-gap bg-color="#F5F5F5" height="20"></u-gap>
+		<view class="" style="padding: 0 24rpx;width: 702rpx;margin: 0 auto;border-radius: 20rpx;background-color: #fff;">
+			<view class="broker-title">
+				报备流程
+			</view>
+			<view class="u-flex" style="padding-bottom: 40rpx;">
+				<text style="width: 22rpx;height: 22rpx;background: #F07F30;border-radius: 100rpx;margin-right: 24rpx;"></text>
+				<view class="u-flex-col u-col-top" style="width: 348rpx;background: #F2F2F2;border-radius: 10rpx;padding: 24rpx;">
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(240, 127, 48, 1);" v-if="detail.status == 1">审核中</text>
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(68, 220, 37, 1);" v-if="detail.status == 2 && detail.flow_status == 1">报备</text>
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(68, 220, 37, 1);" v-if="detail.status == 2 && detail.flow_status == 2">到访</text>
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(68, 220, 37, 1);" v-if="detail.status == 2 && detail.flow_status == 3">认购</text>
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(68, 220, 37, 1);" v-if="detail.status == 2 && detail.flow_status == 4">签约</text>
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(68, 220, 37, 1);" v-if="detail.status == 2 && detail.flow_status == 5">结佣</text>
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(220, 75, 37, 1);" v-if="detail.status == 3">审核失败</text>
+					<text style="margin-bottom: 6rpx;font-size: 24rpx;color: rgba(204, 204, 204, 1)" v-if="detail.status == 4">已失效</text>
+					<text style="font-size: 20rpx;color: #999999;">{{detail.explain}}</text>
+				</view>
+			</view>
+		</view>
+		<view style="height: 200rpx;"></view>
+		<view class="baobei-info-btn" @click="show = true" v-if="detail.status == 2">
+			二维码展示
+		</view>
+		<u-popup v-model="show" mode="center" background="rgba(0,0,0,0)">
+			<view class="code-popup u-flex-col u-col-center">
+				<image src="../../static/images/code.png" class="code" mode=""></image>
+				<u-icon name="close-circle-fill" size="72" color="#fff" @click="show = false"></u-icon>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	import { mapState } from 'vuex'
+	export default {
+		data() {
+			return {
+				show: false,
+				id: '',
+				detail: {},
+				tabs: ['报备', '到访', '认购', '签约', '结佣'],
+				status: 0
+			}
+		},
+		onLoad(option) {
+			this.id = option.id
+			this.getdata()
+		},
+		onShow() {
+			uni.setNavigationBarTitle({
+				title: this.config.store_title
+			})
+		},
+		computed:{
+			...mapState(['config'])
+		},
+		methods: {
+			getdata() {
+				this.$u.post('/api/Report/report_detail', {
+					id: this.id
+				}).then(res => {
+					this.detail = res.data
+					if(this.detail.status == 2){
+						this.status = res.data.flow_status
+					}
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	page{
+		background-color: rgba(245, 245, 245, 1);
+	}
+	.code-popup {
+		.code {
+			width: 564rpx;
+			height: 564rpx;
+			border-radius: 20rpx;
+			margin-bottom: 60rpx;
+		}
+	}
+
+	.baobei-info {
+		.info-header {
+			width: 702rpx;
+			height: 180rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+		
+			.jindu-box {
+				border-radius: 20rpx;
+				overflow: hidden;
+				position: relative;
+				z-index: 1;
+				background-color: rgba(229, 229, 229, 1);
+		
+				.item-bg {
+					background-color: rgba(31, 126, 255, 1);
+					border-radius: 20rpx;
+					height: 54rpx;
+					position: absolute;
+					top: 0;
+					left: 0;
+					z-index: -1;
+				}
+		
+				.item {
+					width: 130rpx;
+		
+					// position: relative;
+					// z-index: 1;
+					// border-radius: 20rpx;
+					// overflow: hidden;
+					text {
+						display: inline-block;
+						width: 130rpx;
+						line-height: 54rpx;
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+						text-align: center;
+					}
+		
+					.text {
+						color: #fff;
+					}
+				}
+			}
+		
+			.image-box {
+				margin-bottom: 6rpx;
+		
+				.image {
+					width: 130rpx;
+		
+					image {
+						width: 67rpx;
+						height: 64rpx;
+					}
+				}
+			}
+		}
+		.baobei-info-btn {
+			width: 702rpx;
+			line-height: 82rpx;
+			background: #1F7EFF;
+			border-radius: 8rpx;
+			position: fixed;
+			bottom: 50rpx;
+			left: 24rpx;
+			text-align: center;
+			font-size: 28rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #FFFFFF;
+			z-index: 100;
+		}
+
+		.broker-row {
+			height: 82rpx;
+			border-bottom: 2rpx solid #F5F5F5;
+
+			.input-right {
+				flex: 1;
+				font-size: 24rpx;
+			}
+
+			.broker-row-left {
+				width: 252rpx;
+
+				text:first-child {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999999;
+				}
+
+				text:nth-child(2) {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #F83838;
+				}
+			}
+		}
+
+		.broker-title {
+			padding: 24rpx 0;
+			font-size: 28rpx;
+			font-family: PingFangSC-Medium, PingFang SC;
+			font-weight: 500;
+			color: #333333;
+		}
+	}
+
+</style>

+ 85 - 0
pages/mine/baobei-list.vue

@@ -0,0 +1,85 @@
+<template>
+	<view class="baobei-list">
+		<view class="baobei-item u-flex u-row-between" v-for="(item,index) in list" :key="index" @click="toinfo(item)">
+			<view class="left u-flex-col u-flex-1">
+				<text>意向项目:{{item.property_name}}</text>
+				<text>意向区域:{{item.area_name}}</text>
+				<text>预约看房时间:{{item.time}}</text>
+				<text>报备时间:{{item.create_at}}</text>
+			</view>
+			<view class="right" style="background-color: rgba(240, 127, 48, 1);" v-if="item.status == 1">审核中</view>
+			<view class="right" style="background-color: rgba(68, 220, 37, 1);" v-if="item.status == 2">已通过</view>
+			<view class="right" style="background-color: rgba(220, 75, 37, 1);" v-if="item.status == 3">审核失败</view>
+			<view class="right" style="background-color: rgba(204, 204, 204, 1);" v-if="item.status == 4">已失效</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				list: [],
+				page: 1
+			}
+		},
+		onLoad() {
+			this.getdata()
+		},
+		methods: {
+			getdata() {
+				this.$u.post('/api/Report/report_list', {
+					page: this.page,
+					page_num: 20
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			},
+			toinfo(item) {
+				uni.navigateTo({
+					url: "./baobei-info?id=" + item.id
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	page {
+		background-color: rgba(245, 245, 245, 1);
+	}
+
+	.baobei-list {
+
+		.baobei-item {
+			padding: 20rpx 20rpx 10rpx 20rpx;
+			background-color: #fff;
+			width: 702rpx;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+
+			.right {
+				width: 160rpx;
+				line-height: 60rpx;
+				border-radius: 10rpx;
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+
+			.left {
+				text {
+					margin-bottom: 10rpx;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #666666;
+				}
+			}
+		}
+	}
+
+</style>

+ 323 - 0
pages/mine/mine.vue

@@ -0,0 +1,323 @@
+<template>
+	<view class="mine">
+		<view class="index-bg"></view>
+		<view class="mine-tips" v-if="userinfo.certification_type != 0">
+			<u-notice-bar mode="horizontal" bg-color="rgba(0,0,0,0)" :list="[`${userinfo.certification_type == 1 ? config.intermediary_explain : config.people_explain}`]" padding="0" :volume-icon="false" color="#fff" font-size="22"></u-notice-bar>
+		</view>
+		<view class="user-box u-flex">
+			<image v-if="userinfo.headimg" :src="userinfo.headimg" class="user-head" mode=""></image>
+			<image v-else src="../../static/images/head.png" class="user-head" mode=""></image>
+			<view class="user-center u-flex-1 u-flex-col u-col-top">
+				<text class="text1" v-if="userinfo.name">{{userinfo.name}}</text>
+				<button open-type="getPhoneNumber" class="text1" @getphonenumber="login" v-else>点击进行登录</button>
+				<text class="text2" v-if="userinfo.certification_type == 0">未认证</text>
+				<text class="text2" v-if="userinfo.certification_type == 1">中介经纪人</text>
+				<text class="text2" v-if="userinfo.certification_type == 2">全民经纪人</text>
+			</view>
+			<u-icon name="edit-pen-fill" color="#fff" @click="toinfo" size="36"></u-icon>
+		</view>
+		<view class="yongjin-box">
+			<view class="price u-flex u-row-center">
+				<image src="../../static/images/qianbao.png" class="img" mode=""></image>
+				<view class="text u-flex-col">
+					<text>{{userinfo.balance || '0.00'}}</text>
+					<text>佣金总额(元)</text>
+				</view>
+			</view>
+			<view class="mingxi" @click="toyongjin">
+				佣金明细
+			</view>
+		</view>
+		<view class="tabs-box u-flex">
+			<view class="tabs-item u-flex-col u-col-center" @click="tourl(1)" v-if="(userinfo.certification_type == 1 || userinfo.certification_type == 0) && config.intermediary_show == 1">
+				<image src="../../static/images/mine-tabs1.png" mode=""></image>
+				<text>中介经纪人</text>
+			</view>
+			<view class="tabs-item u-flex-col u-col-center" @click="tourl(2)" v-if="(userinfo.certification_type == 2 || userinfo.certification_type == 0) && config.people_show == 1">
+				<image src="../../static/images/mine-tabs2.png" mode=""></image>
+				<text>全民经纪人</text>
+			</view>
+			<view class="tabs-item u-flex-col u-col-center" @click="tobaobei">
+				<image src="../../static/images/mine-tabs3.png" mode=""></image>
+				<text>我的报备</text>
+			</view>
+		</view>
+		<view class="setting-box u-flex u-row-between" @click="tosetting">
+			<text>系统设置</text>
+			<u-icon name="arrow-right"></u-icon>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		mapState
+	} from "vuex"
+	export default {
+		data() {
+			return {
+				userinfo: {
+					certification_type: 0
+				}
+			}
+		},
+		onLoad() {
+
+		},
+		computed: {
+			...mapState(['config'])
+		},
+		onShow() {
+			if (uni.getStorageSync("token")) {
+				this.getuser()
+			} else {
+				this.userinfo = {
+					certification_type: 0
+				}
+			}
+		},
+		methods: {
+			tourl(type) {
+				if (this.$islogin()) return
+				uni.navigateTo({
+					url: "/pages/index/broker?type=" + type
+				})
+			},
+			login(e) {
+				if (e.detail.code) {
+					var phoneCode = e.detail.code
+					uni.login({
+						success: (code) => {
+							this.$u.post('/api/Login/login', {
+								code: code.code
+							}).then(res => {
+								if (res.code == 1) {
+									uni.setStorageSync("token", res.data.token)
+									if (!res.data.phone || 1) {
+										this.$u.post('/api/Member/bind_Phone', {
+											code: phoneCode
+										}).then(res => {
+											if (res.code == 1) {
+												this.$u.toast("登录成功")
+												this.$u.post('/api/Member/member_info').then(res => {
+													uni.setStorageSync("hx_username", res.data.hx_username)
+													this.$WebIM.conn.open({
+														user: res.data.hx_username,
+														pwd: "999999",
+													}).then(() => {
+														console.log("login success");
+													}).catch((reason) => {
+														console.log("login fail", reason);
+													});
+												})
+												this.getuser()
+											} else {
+												this.$u.toast(res.msg)
+											}
+										})
+									} else {
+										this.$u.toast("登录成功")
+										this.getuser()
+									}
+								} else {
+									this.$u.toast(res.msg)
+								}
+							})
+						}
+					})
+				}
+			},
+			getuser() {
+				this.$u.post('/api/Member/member_info').then(res => {
+					uni.setStorageSync("name", res.data.name)
+					uni.setStorageSync("headimg", res.data.headimg)
+					this.userinfo = res.data
+				})
+			},
+			tosetting() {
+				if (this.$islogin()) return
+				uni.navigateTo({
+					url: "./setting"
+				})
+			},
+			tobaobei() {
+				if (this.$islogin()) return
+				uni.navigateTo({
+					url: "./baobei-list"
+				})
+			},
+			toyongjin() {
+				if (this.$islogin()) return
+				uni.navigateTo({
+					url: "./yongjin"
+				})
+			},
+			toinfo() {
+				if (this.$islogin()) return
+				uni.navigateTo({
+					url: "./userinfo"
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.mine {
+		position: relative;
+		z-index: 1;
+
+		.setting-box {
+			width: 702rpx;
+			height: 82rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 0 auto;
+			padding: 0 24rpx;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #333333;
+		}
+
+		.tabs-box {
+			width: 702rpx;
+			height: 218rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+
+			.tabs-item {
+				width: 25%;
+
+				image {
+					width: 84rpx;
+					height: 84rpx;
+					margin-bottom: 20rpx;
+					border-radius: 20rpx;
+				}
+
+				text {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #131415;
+				}
+			}
+		}
+
+		.yongjin-box {
+			width: 702rpx;
+			background: #FFFFFF;
+			border-radius: 20rpx;
+			margin: 0 auto;
+			padding: 0 50rpx;
+
+			.mingxi {
+				line-height: 82rpx;
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #333333;
+			}
+
+			.price {
+				height: 226rpx;
+				border-bottom: 2rpx solid rgba(245, 245, 245, 1);
+
+				.img {
+					width: 94rpx;
+					height: 94rpx;
+					margin-right: 34rpx;
+				}
+
+				.text {
+					text:first-child {
+						font-size: 48rpx;
+						font-family: PingFangSC-Medium, PingFang SC;
+						font-weight: 500;
+						color: #333333;
+					}
+
+					text:last-child {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+					}
+				}
+			}
+		}
+
+		.user-box {
+			padding: 48rpx 24rpx 82rpx 24rpx;
+
+			.user-center {
+				margin: 0 32rpx;
+
+				.text1 {
+					font-size: 32rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+					margin: 0 0 20rpx 0;
+					padding: 0;
+					border: none;
+					background-color: rgba(0, 0, 0, 0);
+					line-height: 1.5;
+					text-align: left;
+				}
+
+				.text1::after {
+					margin: 0;
+					padding: 0;
+					border: none;
+				}
+
+				.text2 {
+					padding: 0 20rpx;
+					min-width: 102rpx;
+					line-height: 40rpx;
+					border-radius: 24rpx;
+					border: 2rpx solid #FFFFFF;
+					text-align: center;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #FFFFFF;
+				}
+			}
+
+			.user-head {
+				width: 120rpx;
+				height: 120rpx;
+				border-radius: 100rpx;
+			}
+		}
+
+		.mine-tips {
+			width: 750rpx;
+			line-height: 56rpx;
+			background: rgba(255, 255, 255, 0.1);
+			padding: 0 24rpx;
+			font-size: 22rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #FFFFFF;
+		}
+
+		.index-bg {
+			width: 750rpx;
+			height: 556rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			position: absolute;
+			top: 1;
+			left: 0;
+			z-index: -1;
+		}
+	}
+</style>

+ 124 - 0
pages/mine/setting.vue

@@ -0,0 +1,124 @@
+<template>
+	<view class="setting">
+		<view class="item u-flex u-row-between" @click="totext(1)">
+			<text class="text">用户协议</text>
+			<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+		</view>
+		<view class="item u-flex u-row-between" @click="totext(2)">
+			<text class="text">隐私政策</text>
+			<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+		</view>
+		<view class="item u-flex u-row-between" @click="showtocall = true">
+			<text class="text">退出登录</text>
+			<u-icon name="arrow-right" color="#CCCCCC"></u-icon>
+		</view>
+		<u-popup v-model="showtocall" mode="center" background="rgba(0,0,0,0)">
+			<view class="tocall-box u-flex-col u-col-center">
+				<image class="tocall-img" src="../../static/images/popup1-1.png" mode=""></image>
+				<view class="u-flex-col u-col-center tocall-text">确定退出登录?</view>
+				<view class="u-flex u-row-between tocall-btn">
+					<text @click="showtocall = false">取消</text>
+					<text @click="logout">确定</text>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	export default {
+		data(){
+			return{
+				showtocall:false
+			}
+		},
+		onLoad() {
+			
+		},
+		methods:{
+			logout(){
+				uni.clearStorageSync()
+				this.$WebIM.conn.close()
+				this.$u.toast("退出成功")
+				setTimeout(() => {
+					uni.navigateBack()
+				},800)
+			},
+			totext(type){
+				uni.navigateTo({
+					url:"./xieyi?type=" + type
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.setting{
+		padding: 24rpx;
+		.tocall-box {
+			position: relative;
+		
+			.tocall-img {
+				width: 650rpx;
+				height: 476rpx;
+			}
+		
+			.tocall-btn {
+				position: absolute;
+				bottom: 70rpx;
+				left: 0;
+				width: 100%;
+				padding: 0 48rpx;
+		
+				text:first-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: rgba(31, 126, 255, 1);
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+				}
+		
+				text:last-child {
+					width: 254rpx;
+					line-height: 80rpx;
+					background: rgba(204, 204, 204, 1);
+					border-radius: 20rpx;
+					text-align: center;
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #FFFFFF;
+					text-decoration: none;
+				}
+			}
+		
+			.tocall-text {
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #999999;
+				position: absolute;
+				top: 244rpx;
+				left: 0;
+				z-index: 10;
+				width: 100%;
+			}
+		}
+		.item{
+			padding: 24rpx 0;
+			border-bottom: 2rpx solid rgba(245, 245, 245, 1);
+			.text{
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #333333;
+			}
+		}
+	}
+</style>

+ 141 - 0
pages/mine/userinfo.vue

@@ -0,0 +1,141 @@
+<template>
+	<view class="userinfo u-flex-col u-col-center">
+		<view class="user-head" @click="changehead">
+			<image v-if="userinfo.headimg" :src="userinfo.headimg" class="head" mode=""></image>
+			<image v-else src="../../static/images/head.png" class="head" mode=""></image>
+			<image src="../../static/images/xiangji.png" class="change" mode=""></image>
+		</view>
+		<input type="text" placeholder="请输入昵称" class="input" v-model="userinfo.name">
+		<view class="user-btn" @click="save">
+			确认
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				userinfo: {
+					headimg:'',
+					name:''
+				},
+				ossdata:{}
+			}
+		},
+		onLoad() {
+			this.getuser()
+			this.getoss()
+		},
+		methods: {
+			getoss(){
+				this.$u.post('/api/Upload/getSignedUrl').then(res => {
+					this.ossdata = res.data
+				})
+			},
+			changehead(){
+				uni.chooseImage({
+					count: 1,
+					success: (img) => {
+						var key = this.ossdata.key + new Date().getTime() + Math.floor(Math.random() * 150) + '.png'
+						uni.uploadFile({
+							url: this.ossdata.host, //输入你的bucketname.endpoint
+							filePath: img.tempFilePaths[0],
+							name: 'file',
+							formData: {
+								key: key,
+								policy: this.ossdata.policy, // 输入你获取的的policy
+								OSSAccessKeyId: this.ossdata.OSSAccessKeyId, // 输入你的AccessKeyId
+								success_action_status: '200', // 让服务端返回200,不然,默认会返回204
+								signature: this.ossdata.Signature, // 输入你获取的的signature
+							},
+							success: (res) => {
+								if (res.statusCode == 200) {
+									this.userinfo.headimg = this.ossdata.host + '/' + key
+									this.$u.post('/api/Member/edit_member_info',{
+										type:1,
+										headimg:this.userinfo.headimg
+									}).then(res => {
+										this.$u.toast(res.msg)
+									})
+								}
+							},
+							fail: (err) => {
+								console.log(err);
+							}
+						})
+					}
+				})
+			},
+			save(){
+				if(!this.userinfo.name){
+					this.$u.toast("请输入昵称")
+					return
+				}
+				this.$u.post('/api/Member/edit_member_info',{
+					type:2,
+					name:this.userinfo.name
+				}).then(res => {
+					this.$u.toast(res.msg)
+				})
+			},
+			getuser() {
+				this.$u.post('/api/Member/member_info').then(res => {
+					this.userinfo = res.data
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	.userinfo {
+		.user-btn {
+			position: fixed;
+			bottom: 200rpx;
+			left: 24rpx;
+			width: 702rpx;
+			line-height: 82rpx;
+			background: #1F7EFF;
+			border-radius: 8rpx;
+			text-align: center;
+			font-size: 28rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #FFFFFF;
+		}
+
+		.input {
+			width: 400rpx;
+			height: 100rpx;
+			text-align: center;
+			border-bottom: 2rpx solid rgba(216, 216, 216, 1);
+		}
+
+		.user-head {
+			width: 256rpx;
+			height: 256rpx;
+			border-radius: 100%;
+			position: relative;
+			margin: 102rpx 0 30rpx 0;
+
+			.head {
+				width: 256rpx;
+				height: 256rpx;
+				border-radius: 100%;
+			}
+
+			.change {
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%, -50%);
+				width: 78rpx;
+				height: 78rpx;
+				z-index: 10;
+			}
+		}
+	}
+
+</style>

+ 63 - 0
pages/mine/xieyi.vue

@@ -0,0 +1,63 @@
+<template>
+	<view style="padding: 24rpx;" v-html="content">
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data(){
+			return{
+				type:1,
+				content:''
+			}
+		},
+		onLoad(option) {
+			this.type = option.type
+			this.getdata()
+		},
+		onShow() {
+			if(this.type == 1){
+				uni.setNavigationBarTitle({
+					title:'用户协议'
+				})
+			}
+			if(this.type == 2){
+				uni.setNavigationBarTitle({
+					title:'	隐私政策'
+				})
+			}
+			if(this.type == 3){
+				uni.setNavigationBarTitle({
+					title:'	报备活动规则'
+				})
+			}
+		},
+		methods:{
+			getdata(){
+				if(this.type == 1){
+					this.$u.post('/api/Index/platform_config').then(res => {
+						const regex = new RegExp('<img', 'gi')
+						this.content = res.data.agreement.replace(regex, `<img style="max-width: 100%; height: auto"`)
+					})
+				}
+				if(this.type == 2){
+					this.$u.post('/api/Index/platform_config').then(res => {
+						const regex = new RegExp('<img', 'gi')
+						this.content = res.data.privacy_policy.replace(regex, `<img style="max-width: 100%; height: auto"`)
+					})
+				}
+				if(this.type == 3){
+					this.$u.post('/api/Index/platform_config').then(res => {
+						const regex = new RegExp('<img', 'gi')
+						this.content = res.data.report_rule.replace(regex, `<img style="max-width: 100%; height: auto"`)
+					})
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	
+</style>

+ 109 - 0
pages/mine/yongjin.vue

@@ -0,0 +1,109 @@
+<template>
+	<view class="yongjin">
+		<view class="yongjin-item u-flex" v-for="(item,index) in list" :key="index">
+			<view class="left">
+				¥
+			</view>
+			<view class="right u-flex-1">
+				<view class="u-flex u-row-between right1">
+					<text>{{item.explain}}</text>
+					<text>{{item.type == 1 ? '+' : '-'}}{{item.change_amount}}</text>
+				</view>
+				<view class="u-flex u-row-between right2">
+					<text>{{item.create_at}}</text>
+					<text>我的佣金 {{item.amount}}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				page: 1,
+				list: []
+			}
+		},
+		onLoad() {
+			this.getlist()
+		},
+		onReachBottom() {
+			if (this.list.length % 20 == 0) {
+				this.page++
+				this.getlist()
+			}
+		},
+		methods: {
+			getlist() {
+				this.$u.post('/api/Member/balance_list', {
+					page: this.page,
+					page_num: 20
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	.yongjin {
+		.yongjin-item {
+			height: 164rpx;
+			border-bottom: 2rpx solid rgba(245, 245, 245, 1);
+			padding: 0 24rpx;
+
+			.right {
+				.right2 {
+					text:first-child {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+					}
+
+					text:last-child {
+						font-size: 24rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #999999;
+					}
+				}
+
+				.right1 {
+					margin-bottom: 4rpx;
+
+					text:first-child {
+						font-size: 28rpx;
+						font-family: PingFangSC-Medium, PingFang SC;
+						font-weight: 500;
+						color: #333333;
+					}
+
+					text:last-child {
+						font-size: 28rpx;
+						font-family: PingFangSC-Medium, PingFang SC;
+						font-weight: 500;
+						color: #333333;
+					}
+				}
+			}
+
+			.left {
+				text-align: center;
+				width: 100rpx;
+				line-height: 100rpx;
+				border-radius: 100rpx;
+				font-size: 60rpx;
+				color: #fff;
+				font-weight: bold;
+				background-color: rgba(247, 164, 107, 1);
+				margin-right: 32rpx;
+			}
+		}
+	}
+
+</style>

+ 25 - 0
pages/video/info.vue

@@ -0,0 +1,25 @@
+<template>
+	<view style="font-size: 0;width: 750rpx;height: 100vh;">
+		<video :src="mp4" style="width: 750rpx;height: 100vh;" controls="true"></video>
+	</view>
+</template>
+
+<script>
+	export default {
+		data(){
+			return{
+				mp4:''
+			}
+		},
+		onLoad(option) {
+			this.mp4 = decodeURIComponent(option.mp4)
+		},
+		methods:{
+			
+		}
+	}
+</script>
+
+<style lang="scss">
+	
+</style>

+ 140 - 0
pages/video/video.vue

@@ -0,0 +1,140 @@
+<template>
+	<view class="video-list">
+		<u-navbar :title="config.store_title" title-color="#fff" :background="{background:'#1F7EFF'}" :border-bottom="false" :isBack="false">
+			<view class="u-flex nav-left" slot="left">
+				<u-icon name="map-fill" size="34" color="#fff"></u-icon>
+				<text class="text">{{city || '定位中'}}</text>
+			</view>
+		</u-navbar>
+		<view class="video1 u-flex u-row-between u-flex-wrap">
+			<view class="video-item" v-for="(item,index) in list" :key="index" @click="toinfo(item)">
+				<view class="image-box">
+					<image :src="item.logo" class="image" mode="aspectFill"></image>
+					<image src="../../static/images/video.png" class="play" mode=""></image>
+					<!-- <text class="text">03.25</text> -->
+				</view>
+				<view class="video-name u-line-1">
+					{{item.title}}
+				</view>
+				<view class="time">
+					{{item.create_at}}
+				</view>
+			</view>
+		</view>
+	</view>
+
+</template>
+
+<script>
+	import { mapState } from "vuex"
+	export default {
+		data() {
+			return {
+				page:1,
+				list:[]
+			}
+		},
+		onLoad() {
+			this.getlist()
+		},
+		onShow() {
+			uni.setNavigationBarTitle({
+				title: this.config.store_title
+			})
+		},
+		computed: {
+			...mapState(['config', 'city'])
+		},
+		methods: {
+			toinfo(item){
+				uni.navigateTo({
+					url:"./info?mp4=" + encodeURIComponent(item.video)
+				})
+			},
+			getlist(){
+				this.$u.post('/api/Index/video_list',{
+					page:this.page,
+					page_num:20
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	.video-list {
+		.nav-left {
+			padding: 0 20rpx;
+
+			.text {
+				font-size: 32rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #FFFFFF;
+				margin-left: 5rpx;
+			}
+		}
+
+		.video1 {
+			padding: 24rpx;
+
+			.video-item {
+				width: 340rpx;
+				margin-bottom: 18rpx;
+
+				.video-name {
+					font-size: 20rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+					margin: 14rpx 0;
+					// height: 56rpx;
+				}
+
+				.time {
+					font-size: 20rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+				}
+
+				.image-box {
+					width: 340rpx;
+					height: 272rpx;
+					position: relative;
+
+					.play {
+						width: 64rpx;
+						height: 64rpx;
+						position: absolute;
+						top: 50%;
+						left: 50%;
+						transform: translate(-50%, -50%);
+					}
+
+					.image {
+						width: 340rpx;
+						height: 272rpx;
+						background-color: rgba(0,0,0,0.1);
+						border-radius: 10rpx;
+					}
+
+					.text {
+						position: absolute;
+						z-index: 10;
+						right: 10rpx;
+						bottom: 10rpx;
+						font-size: 20rpx;
+						font-family: PingFangSC-Regular, PingFang SC;
+						font-weight: 400;
+						color: #FFFFFF;
+					}
+				}
+			}
+		}
+	}
+
+</style>

+ 242 - 0
pagesA/components/echarts-uniapp/echarts-uniapp.vue

@@ -0,0 +1,242 @@
+<template>
+	<!-- #ifdef MP-WEIXIN || MP-TOUTIAO -->
+	<canvas type="2d" class="echarts" :canvas-id="canvasId" :id="canvasId" @touchstart="touchStart"
+		@touchmove="touchMove" @touchend="touchEnd" />
+	<!-- #endif -->
+	<!-- #ifndef MP-WEIXIN || MP-TOUTIAO -->
+	<canvas class="echarts" :canvas-id="canvasId" :id="canvasId" @touchstart="touchStart" @touchmove="touchMove"
+		@touchend="touchEnd" />
+	<!-- #endif -->
+
+</template>
+<script>
+	
+	/**
+	 * echartsForUniApp echart兼容uni-app
+	 * @description echart兼容uni-app
+	 * @property {Object} option 图表数据
+	 * @property {String} canvasId 画布id
+	 * @example <echarts ref="echarts" :option="option" canvasId="echarts"></echarts>
+	 */
+	import WxCanvas from './wx-canvas.js';
+	import * as echarts from './echarts.min.js';	
+	
+	var chartList = {}
+	export default {
+		props: {
+			canvasId: {
+				type: String,
+				default: 'echarts'
+			},
+			option: {
+				type: Object,
+				default: () => {
+					return {}
+				}
+			},
+		},
+		watch: {
+			option(newValue, oldValue) {
+				if(newValue.series){
+					this.initChart(newValue)
+				}
+			}
+		},
+		data() {
+			return {
+				ctx:null
+			}
+		},
+		
+		mounted() {
+			// Disable prograssive because drawImage doesn't support DOM as parameter
+			// See https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.drawImage.html
+			echarts.registerPreprocessor(option => {
+				if (option && option.series) {
+					if (option.series.length > 0) {
+						option.series.forEach(series => {
+							series.progressive = 0;
+						});
+					} else if (typeof option.series === 'object') {
+						option.series.progressive = 0;
+					}
+				}
+			});
+
+		},
+
+		methods: {
+			getCanvasAttr2d() {
+				return new Promise((resolve, reject) => {
+					const query = uni.createSelectorQuery().in(this)
+					query
+						.select('#' + this.canvasId)
+						.fields({
+							node: true,
+							size: true
+						})
+						.exec(res => {
+							const canvasNode = res[0].node
+							this.canvasNode = canvasNode
+							const canvasDpr = uni.getSystemInfoSync().pixelRatio
+							const canvasWidth = res[0].width
+							const canvasHeight = res[0].height
+							this.ctx = canvasNode.getContext('2d')
+			
+							const canvas = new WxCanvas(this.ctx, this.canvasId, true, canvasNode)
+							echarts.setCanvasCreator(() => {
+								return canvas
+							})
+							resolve({
+								canvas,
+								canvasWidth,
+								canvasHeight,
+								canvasDpr
+							})
+						})
+				});
+			},
+			getCanvasAttr() {
+				return new Promise((resolve, reject) => {
+					this.ctx = uni.createCanvasContext(this.canvasId, this);
+					var canvas = new WxCanvas(this.ctx, this.canvasId, false);
+					echarts.setCanvasCreator(() => {
+						return canvas;
+					});
+					const canvasDpr = 1
+					var query = uni.createSelectorQuery()
+						// #ifndef MP-ALIPAY
+						.in(this)
+					// #endif
+					query.select('#' + this.canvasId).boundingClientRect(res => {
+						const canvasWidth = res.width
+						const canvasHeight = res.height
+						resolve({
+							canvas,
+							canvasWidth,
+							canvasHeight,
+							canvasDpr
+						})
+					}).exec();
+				});
+			},
+			// #ifdef H5
+			//H5绘制图表
+			initChart(option) {
+				this.ctx = uni.createCanvasContext(this.canvasId, this);
+				chartList[this.canvasId] = echarts.init(document.getElementById(this.canvasId));
+				chartList[this.canvasId].setOption(option?option:this.option);
+			},
+			//H5生成图片
+			canvasToTempFilePath(opt) {
+				const base64 = chartList[this.canvasId].getDataURL()
+				opt.success && opt.success({tempFilePath:base64})
+			},
+			// #endif
+			// #ifndef H5
+			//绘制图表
+			async initChart(option) {
+				// #ifdef MP-WEIXIN || MP-TOUTIAO 
+				const canvasAttr = await this.getCanvasAttr2d();
+				// #endif
+				// #ifndef MP-WEIXIN || MP-TOUTIAO
+				const canvasAttr = await this.getCanvasAttr();
+				// #endif
+				const {
+					canvas,
+					canvasWidth,
+					canvasHeight,
+					canvasDpr
+				} = canvasAttr
+				chartList[this.canvasId] = echarts.init(canvas, null, {
+					width: canvasWidth,
+					height: canvasHeight,
+					devicePixelRatio: canvasDpr // new
+				});
+				canvas.setChart(chartList[this.canvasId]);
+				chartList[this.canvasId].setOption(option?option:this.option);
+			},
+			//生成图片
+			canvasToTempFilePath(opt) {
+				// #ifdef MP-WEIXIN || MP-TOUTIAO
+				var query = uni.createSelectorQuery()
+				// #ifndef MP-ALIPAY
+					.in(this)
+				// #endif
+				query.select('#' + this.canvasId).fields({ node: true, size: true }).exec(res => {
+					const canvasNode = res[0].node
+					opt.canvas = canvasNode
+					uni.canvasToTempFilePath(opt, this)
+				})
+				// #endif
+				// #ifndef MP-WEIXIN || MP-TOUTIAO
+				if (!opt.canvasId) {
+					opt.canvasId = this.canvasId;
+				}
+				this.ctx.draw(true, () => {
+					uni.canvasToTempFilePath(opt, this);
+				});
+				// #endif
+			},
+			// #endif
+			
+			touchStart(e) {
+				if (chartList[this.canvasId] && e.touches.length > 0) {
+					var touch = e.touches[0];
+					var handler = chartList[this.canvasId].getZr().handler;
+					handler.dispatch('mousedown', {
+						zrX: touch.x,
+						zrY: touch.y
+					});
+					handler.dispatch('mousemove', {
+						zrX: touch.x,
+						zrY: touch.y
+					});
+					handler.processGesture(wrapTouch(e), 'start');
+				}
+			},
+			touchMove(e) {
+				if (chartList[this.canvasId] && e.touches.length > 0) {
+					var touch = e.touches[0];
+					var handler = chartList[this.canvasId].getZr().handler;
+					handler.dispatch('mousemove', {
+						zrX: touch.x,
+						zrY: touch.y
+					});
+					handler.processGesture(wrapTouch(e), 'change');
+				}
+			},
+
+			touchEnd(e) {
+				if (chartList[this.canvasId]) {
+					const touch = e.changedTouches ? e.changedTouches[0] : {};
+					var handler = chartList[this.canvasId].getZr().handler;
+					handler.dispatch('mouseup', {
+						zrX: touch.x,
+						zrY: touch.y
+					});
+					handler.dispatch('click', {
+						zrX: touch.x,
+						zrY: touch.y
+					});
+					handler.processGesture(wrapTouch(e), 'end');
+				}
+			}
+		}
+	}
+
+	function wrapTouch(event) {
+		for (let i = 0; i < event.touches.length; ++i) {
+			const touch = event.touches[i];
+			touch.offsetX = touch.x;
+			touch.offsetY = touch.y;
+		}
+		return event;
+	}
+</script>
+<style lang="scss" scoped>
+	.echarts {
+		width: 100%;
+		height: 100%;
+	}
+</style>

File diff suppressed because it is too large
+ 0 - 0
pagesA/components/echarts-uniapp/echarts.min.js


+ 105 - 0
pagesA/components/echarts-uniapp/wx-canvas.js

@@ -0,0 +1,105 @@
+export default class WxCanvas {
+  constructor(ctx, canvasId, isNew, canvasNode) {
+    this.ctx = ctx;
+    this.canvasId = canvasId;
+    this.chart = null;
+    this.isNew = isNew
+    if (isNew) {
+      this.canvasNode = canvasNode;
+    }
+    else {
+      this._initStyle(ctx);
+    }
+
+    // this._initCanvas(zrender, ctx);
+
+    this._initEvent();
+  }
+
+  getContext(contextType) {
+    if (contextType === '2d') {
+      return this.ctx;
+    }
+  }
+
+  // canvasToTempFilePath(opt) {
+  //   if (!opt.canvasId) {
+  //     opt.canvasId = this.canvasId;
+  //   }
+  //   return wx.canvasToTempFilePath(opt, this);
+  // }
+
+  setChart(chart) {
+    this.chart = chart;
+  }
+
+  attachEvent() {
+    // noop
+  }
+
+  detachEvent() {
+    // noop
+  }
+
+  _initCanvas(zrender, ctx) {
+    zrender.util.getContext = function () {
+      return ctx;
+    };
+
+    zrender.util.$override('measureText', function (text, font) {
+      ctx.font = font || '12px sans-serif';
+      return ctx.measureText(text);
+    });
+  }
+
+  _initStyle(ctx) {
+    ctx.createRadialGradient = () => {
+      return ctx.createCircularGradient(arguments);
+    };
+  }
+
+  _initEvent() {
+    this.event = {};
+    const eventNames = [{
+      wxName: 'touchStart',
+      ecName: 'mousedown'
+    }, {
+      wxName: 'touchMove',
+      ecName: 'mousemove'
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'mouseup'
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'click'
+    }];
+
+    eventNames.forEach(name => {
+      this.event[name.wxName] = e => {
+        const touch = e.touches[0];
+        this.chart.getZr().handler.dispatch(name.ecName, {
+          zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
+          zrY: name.wxName === 'tap' ? touch.clientY : touch.y
+        });
+      };
+    });
+  }
+
+  set width(w) {
+    if (this.canvasNode) this.canvasNode.width = w
+  }
+  set height(h) {
+    if (this.canvasNode) this.canvasNode.height = h
+  }
+
+  get width() {
+    if (this.canvasNode)
+      return this.canvasNode.width
+    return 0
+  }
+  get height() {
+    if (this.canvasNode)
+      return this.canvasNode.height
+    return 0
+  }
+}

+ 167 - 0
pagesA/index/allHouses.vue

@@ -0,0 +1,167 @@
+<template>
+	<view class="newHouses">
+		<view class="newHouses-bg"></view>
+		<view class="renqi-box u-flex">
+			<u-icon name="heart-fill" color="#fff" size="26"></u-icon>
+			<text class="text">累计人气值 {{popularity}}</text>
+		</view>
+		<view class="swiper-box">
+			<u-swiper :list="swiperlisi" height="376" name="logo" @click="tobanner" border-radius="20"></u-swiper>
+		</view>
+		<view class="newHouses-list">
+			<view class="newHouses-title u-flex u-row-between">
+				<text>全部楼盘</text>
+				<image src="../../static/images/shaixuan.png" @click="opensearch" mode=""></image>
+			</view>
+			<view class="u-flex u-row-between u-flex-wrap">
+				<view v-for="(item,index) in list" :key="index">
+					<gf-goods :data="item"></gf-goods>
+				</view>
+			</view>
+		</view>
+		<gf-search ref="search" @shaixuan="shaixuan"></gf-search>
+	</view>
+</template>
+
+<script>
+	import {
+		mapState,
+		mapActions,
+		mapMutations,
+		mapGetters
+	} from 'vuex'
+	export default {
+		data() {
+			return {
+				title:'',
+				swiperlisi: [],
+				page: 1,
+				list: [],  
+				params: {}
+			}
+		},
+		onLoad(options) {
+			this.title = options.title
+			this.getlist()
+			this.getbanner()
+		},
+		onShow() {
+			if(this.title!=''){
+				uni.setNavigationBarTitle({
+					title: this.title //这是修改后的导航栏文字
+				})
+			}
+		},
+		computed: {
+			...mapState(["popularity"])
+		},
+		onReachBottom() {
+			if (this.list.length % 20 == 0) {
+				this.page++
+				this.getlist()
+			}
+		},
+		methods: {
+			shaixuan(e) {
+				this.params = e
+				this.page = 1
+				this.list = []
+				this.getlist()
+			},
+			tobanner(e) {
+				if (this.swiperlisi[e].url) {
+					uni.navigateTo({
+						url: this.swiperlisi[e].url
+					})
+				}
+			},
+			getbanner() {
+				this.$u.post('/api/Index/banner', {
+					location: 2
+				}).then(res => {
+					this.swiperlisi = res.data
+				})
+			},
+			getlist() {
+				console.log(this.params);
+				this.$u.post('/api/Index/property_list', {
+					page: this.page,
+					page_num: 20,
+					type: 2,
+					cate_name:this.title,
+					...this.params
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			},
+			opensearch() {
+				this.$refs.search.show = true
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.newHouses {
+		position: relative;
+		z-index: 1;
+
+		.newHouses-list {
+			width: 702rpx;
+			background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 38%, rgba(255, 255, 255, 0) 100%);
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx;
+
+			.newHouses-title {
+				padding: 20rpx 0;
+
+				text {
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #131415;
+				}
+
+				image {
+					width: 32rpx;
+					height: 32rpx;
+				}
+			}
+		}
+
+		.swiper-box {
+			padding: 0 24rpx;
+			margin-bottom: 20rpx;
+		}
+
+		.renqi-box {
+			width: 750rpx;
+			height: 56rpx;
+			background: rgba(256, 256, 256, 0.1);
+			padding: 0 24rpx;
+			margin-bottom: 24rpx;
+
+			.text {
+				font-size: 24rpx;
+				font-family: Helvetica;
+				color: #FFFFFF;
+				margin-left: 10rpx;
+			}
+		}
+
+		.newHouses-bg {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 750rpx;
+			height: 556rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			z-index: -1;
+		}
+	}
+</style>

+ 221 - 0
pagesA/index/counter-info.vue

@@ -0,0 +1,221 @@
+<template>
+	<view class="counter-info" v-if="show">
+		<view class="info-box" v-if="type == 3">
+			<view class="">
+				<view class="box-title">
+					贷款总计
+				</view>
+				<view class="box-list u-flex u-row-between u-flex-wrap">
+					<text class="text" style="width: 55%;">首月月供:¥{{detail.general_info.first_monthly_payment.toFixed(2)}}元</text>
+					<text class="text u-flex-1">贷款年限:{{detail.general_info.age_limit}}年</text>
+					<text class="text" style="width: 55%;">贷款总额:¥{{detail.general_info.calculate_amount_all.toFixed(2)}}元</text>
+					<text class="text u-flex-1">累计利息:¥{{detail.general_info.interest_total.toFixed(2)}}元</text>
+					<text class="text" style="width: 55%;">累计还款总额:¥{{detail.general_info.repayment_amount_all.toFixed(2)}}元</text>
+				</view>
+			</view>
+			<view class="">
+				<view class="box-title">
+					公积金贷款
+				</view>
+				<view class="box-list u-flex u-row-between u-flex-wrap">
+					<text class="text" style="width: 55%;">首月月供:¥{{detail.accumulation_info.first_monthly_payment.toFixed(2)}}元</text>
+					<text class="text u-flex-1">贷款年限:{{detail.general_info.age_limit}}年</text>
+					<text class="text" style="width: 55%;">贷款总额:¥{{detail.accumulation_info.calculate_amount_all.toFixed(2)}}元</text>
+					<text class="text u-flex-1">累计利息:¥{{detail.accumulation_info.interest_total.toFixed(2)}}元</text>
+					<text class="text" style="width: 55%;">累计还款总额:¥{{detail.accumulation_info.repayment_amount_all.toFixed(2)}}元</text>
+				</view>
+			</view>
+			<view class="">
+				<view class="box-title">
+					商业贷款
+				</view>
+				<view class="box-list u-flex u-row-between u-flex-wrap">
+					<text class="text" style="width: 55%;">首月月供:¥{{detail.business_info.first_monthly_payment.toFixed(2)}}元</text>
+					<text class="text u-flex-1">贷款年限:{{detail.general_info.age_limit}}年</text>
+					<text class="text" style="width: 55%;">贷款总额:¥{{detail.business_info.calculate_amount_all.toFixed(2)}}元</text>
+					<text class="text u-flex-1">累计利息:¥{{detail.business_info.interest_total.toFixed(2)}}元</text>
+					<text class="text" style="width: 55%;">累计还款总额:¥{{detail.business_info.repayment_amount_all.toFixed(2)}}元</text>
+				</view>
+			</view>
+			
+			<view class="box-btn" @click="toback">
+				重新计算
+			</view>
+		</view>
+		<view class="info-box" v-else>
+			<view class="box-title">
+				贷款总计
+			</view>
+			<view class="box-list u-flex u-row-between u-flex-wrap">
+				<text class="text" style="width: 55%;">首月月供:¥{{Number(detail.first_monthly_payment).toFixed(2)}}元</text>
+				<text class="text u-flex-1">贷款年限:{{detail.age_limit}}年</text>
+				<text class="text" style="width: 55%;">贷款总额:¥{{detail.calculate_amount.toFixed(2)}}元</text>
+				<text class="text u-flex-1">累计利息:¥{{detail.interest_total.toFixed(2)}}元</text>
+				<text class="text" style="width: 55%;">累计还款总额:¥{{detail.repayment_amount_all.toFixed(2)}}元</text>
+			</view>
+			<view class="box-btn" @click="toback">
+				重新计算
+			</view>
+		</view>
+		<view class="info-list">
+			<view class="list-header u-flex u-row-between">
+				<text>期数</text>
+				<text>月供(元)</text>
+				<text>本金(元)</text>
+				<text>利息(元)</text>
+			</view>
+			<view class="list-item u-flex u-row-between" v-for="(item,index) in detail.repayment_info" :key="index">
+				<text>第{{item.periods}}期</text>
+				<text>{{item.monthly_payment}}</text>
+				<text>{{item.principal}}</text>
+				<text>{{item.interest}}</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				params: {},
+				type: 1,
+				detail: {
+					general_info:{},
+					accumulation_info:{},
+					business_info:{},
+					repayment_info:[]
+				},
+				show:false
+			}
+		},
+		onLoad(option) {
+			this.params = JSON.parse(decodeURIComponent(option.params))
+			this.type = option.type
+			this.getdata()
+		},
+		methods: {
+			toback() {
+				uni.navigateBack()
+			},
+			getdata() {
+				uni.showLoading({
+					mask:true,
+					title:"请稍后"
+				})
+				if (this.type == 1 || this.type == 2) {
+					this.$u.post('/api/Calculator/not_group_loans', this.params).then(res => {
+						if (res.code == 1) {
+							this.detail = res.data
+						} else {
+							this.$u.toast(res.msg)
+						}
+						this.show = true
+					})
+				} else {
+					this.$u.post('/api/Calculator/group_loans', this.params).then(res => {
+						if (res.code == 1) {
+							this.detail = res.data
+						} else {
+							this.$u.toast(res.msg)
+						}
+						this.show = true
+					})
+				}
+
+			}
+		}
+	}
+
+</script>
+
+<style lang="scss">
+	.counter-info {
+		position: relative;
+		z-index: 1;
+		padding: 1rpx 0;
+		background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 284rpx, #F6F6F6 100%);
+		min-height: 100vh;
+
+		.info-list {
+			width: 722rpx;
+			// height: 650rpx;
+			background: #FFFFFF;
+			border-radius: 10rpx;
+			margin: 20rpx auto;
+			padding: 20rpx 10rpx 1rpx 10rpx;
+
+			.list-item {
+				margin-bottom: 20rpx;
+
+				text {
+					flex: 1;
+					text-align: center;
+					line-height: 66rpx;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+				}
+			}
+
+			.list-header {
+				height: 66rpx;
+				background: #C6DDFD;
+				border-radius: 10rpx;
+				margin-bottom: 20rpx;
+
+				text {
+					flex: 1;
+					text-align: center;
+					line-height: 66rpx;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+				}
+			}
+		}
+
+		.info-box {
+			width: 702rpx;
+			// height: 390rpx;
+			background: #FFFFFF;
+			border-radius: 10rpx;
+			margin: 108rpx auto 20rpx auto;
+			padding: 0 24rpx 1rpx 24rpx;
+
+			.box-btn {
+				width: 100%;
+				line-height: 80rpx;
+				background: linear-gradient(143deg, #8DBDFF 0%, #1F7EFF 100%);
+				border-radius: 40rpx;
+				text-align: center;
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+				margin: 40rpx auto;
+			}
+
+			.box-list {
+				.text {
+					margin-bottom: 20rpx;
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #333333;
+				}
+			}
+
+			.box-title {
+				padding: 24rpx 0;
+				font-size: 28rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #333333;
+			}
+		}
+	}
+
+</style>

+ 695 - 0
pagesA/index/counter.vue

@@ -0,0 +1,695 @@
+<template>
+	<view class="counter">
+		<view class="dataCenter-header u-flex u-row-between">
+			<view class="header-item u-flex-col u-col-center" v-for="(item,index) in tabs" :key="index" @click="change(index)">
+				<text :class="current == index ? 'active1' : 'active'">{{item}}</text>
+				<image :style="{opacity:current == index ? 1 : 0}" src="../../static/images/dataCenter1-1.png" mode=""></image>
+			</view>
+		</view>
+		<view class="" v-if="current == 0">
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">还款方式:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<u-radio-group v-model="denge" class="u-flex-1" size="24">
+					<view class="row-right u-flex u-row-between">
+						<u-radio :name="1" label-size="24">等额本息</u-radio>
+						<u-radio :name="2" label-size="24">等额本金</u-radio>
+					</view>
+				</u-radio-group>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">计算方式:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<u-radio-group v-model="jisuan" class="u-flex-1" size="24">
+					<view class="row-right u-flex u-row-between">
+						<u-radio :name="1" label-size="24">贷款总额</u-radio>
+						<u-radio :name="2" label-size="24">单价面积</u-radio>
+					</view>
+				</u-radio-group>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款总额:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1" v-if="jisuan == 1">
+					<input type="digit" placeholder="请输入" class="input" v-model="shangyeprice">
+					<text class="text">万元</text>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1" v-if="jisuan == 2">
+					<input type="digit" placeholder="请输入" class="input" v-model="shangyeprice">
+					<text class="text" style="margin-right: 10rpx;">元/㎡</text>
+					x
+					<input type="digit" placeholder="请输入" class="input" v-model="shangyemianji">
+					<text class="text">㎡</text>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex" v-if="jisuan == 2" @click="shoufushow = true">
+				<view class="u-flex row-left">
+					<text class="text">首付成数:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="text" placeholder="请选择" class="input" v-model="shoufuname" :disabled="true">
+					<u-icon name="arrow-right" color="#ccc" size="24"></u-icon>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款利率:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="digit" placeholder="请输入" class="input" v-model="shangyelilv">
+					<text class="text">%</text>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex" @click="qixianshow = true">
+				<view class="u-flex row-left">
+					<text class="text">贷款期限:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="text" placeholder="请选择" class="input" v-model="qixianname" :disabled="true">
+					<u-icon name="arrow-right" color="#ccc" size="24"></u-icon>
+				</view>
+			</view>
+		</view>
+		<view class="" v-if="current == 1">
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">还款方式:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<u-radio-group v-model="denge" class="u-flex-1" size="24">
+					<view class="row-right u-flex u-row-between">
+						<u-radio :name="1" label-size="24">等额本息</u-radio>
+						<u-radio :name="2" label-size="24">等额本金</u-radio>
+					</view>
+				</u-radio-group>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">计算方式:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<u-radio-group v-model="jisuan" class="u-flex-1" size="24">
+					<view class="row-right u-flex u-row-between">
+						<u-radio :name="1" label-size="24">贷款总额</u-radio>
+						<u-radio :name="2" label-size="24">单价面积</u-radio>
+					</view>
+				</u-radio-group>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款总额:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1" v-if="jisuan == 1">
+					<input type="digit" placeholder="请输入" class="input" v-model="gongjijinprice">
+					<text class="text">万元</text>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1" v-if="jisuan == 2">
+					<input type="digit" placeholder="请输入" class="input" v-model="gongjijinprice">
+					<text class="text" style="margin-right: 10rpx;">元/㎡</text>
+					x
+					<input type="digit" placeholder="请输入" class="input" v-model="gongjijinmianji">
+					<text class="text">㎡</text>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex" v-if="jisuan == 2" @click="shoufushow = true">
+				<view class="u-flex row-left">
+					<text class="text">首付成数:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="text" placeholder="请选择" class="input" v-model="shoufuname" :disabled="true">
+					<u-icon name="arrow-right" color="#ccc" size="24"></u-icon>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款利率:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="digit" placeholder="请输入" class="input" v-model="gongjijinlilv">
+					<text class="text">%</text>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex" @click="qixianshow = true">
+				<view class="u-flex row-left">
+					<text class="text">贷款期限:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="text" placeholder="请选择" class="input" v-model="qixianname" :disabled="true">
+					<u-icon name="arrow-right" color="#ccc" size="24"></u-icon>
+				</view>
+			</view>
+		</view>
+		<view class="" v-if="current == 2">
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">还款方式:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<u-radio-group v-model="denge" class="u-flex-1" size="24">
+					<view class="row-right u-flex u-row-between">
+						<u-radio :name="1" label-size="24">等额本息</u-radio>
+						<u-radio :name="2" label-size="24">等额本金</u-radio>
+					</view>
+				</u-radio-group>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">计算方式:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<u-radio-group v-model="jisuan" class="u-flex-1" size="24" @change="changejisuan">
+					<view class="row-right u-flex u-row-between">
+						<u-radio :name="1" label-size="24">房屋总价</u-radio>
+						<u-radio :name="2" label-size="24">单价面积</u-radio>
+					</view>
+				</u-radio-group>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">房屋总价:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1" v-if="jisuan == 1">
+					<input type="digit" placeholder="请输入" class="input" v-model="zuheprice" @input="changezuhe">
+					<text class="text">万元</text>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1" v-if="jisuan == 2">
+					<input type="digit" placeholder="请输入" class="input" v-model="zuheprice" @input="changezuhe">
+					<text class="text" style="margin-right: 10rpx;">元/㎡</text>
+					x
+					<input type="digit" placeholder="请输入" class="input" v-model="zuhemianji" @input="changezuhe">
+					<text class="text">㎡</text>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">首付成数:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<u-radio-group v-model="shoufu" class="u-flex-1" size="24" @change="changechengshu">
+					<view class="row-right u-flex u-row-between">
+						<u-radio :name="1" label-size="24">首套</u-radio>
+						<u-radio :name="2" label-size="24">二套</u-radio>
+						<view class="row-right u-flex u-row-between" style="width: 30%;" @click="shoufushow = true">
+							<input type="text" placeholder="请选择" class="input" v-model="shoufuname" :disabled="true">
+							<u-icon name="arrow-right" color="#ccc" size="24"></u-icon>
+						</view>
+					</view>
+				</u-radio-group>
+			</view>
+			<view class="jisuan-row u-flex" @click="qixianshow = true">
+				<view class="u-flex row-left">
+					<text class="text">贷款期限:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="text" placeholder="请选择" class="input" v-model="qixianname" :disabled="true">
+					<u-icon name="arrow-right" color="#ccc" size="24"></u-icon>
+				</view>
+			</view>
+			<u-gap height="20" bg-color="#F6F6F6"></u-gap>
+			<view class="tabs-title">
+				公积金贷款信息
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款总额:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="number" placeholder="请输入" class="input" v-model="gongjijinprice" @input="changezuhe2">
+					<text class="text">万元</text>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款利率:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="number" placeholder="请输入" class="input" v-model="gongjijinlilv">
+					<text class="text">%</text>
+				</view>
+			</view>
+			<u-gap height="20" bg-color="#F6F6F6"></u-gap>
+			<view class="tabs-title">
+				商业贷款信息
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款总额:</text>
+					<!-- <u-icon name="question-circle-fill" color="#CCCCCC"></u-icon> -->
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="number" placeholder="请输入" class="input" v-model="shangyeprice" @input="changezuhe1">
+					<text class="text">万元</text>
+				</view>
+			</view>
+			<view class="jisuan-row u-flex">
+				<view class="u-flex row-left">
+					<text class="text">贷款利率:</text>
+					<u-icon name="question-circle-fill" color="#CCCCCC"></u-icon>
+				</view>
+				<view class="row-right u-flex u-row-between u-flex-1">
+					<input type="number" placeholder="请输入" class="input" v-model="shangyelilv">
+					<text class="text">%</text>
+				</view>
+			</view>
+		</view>
+		<view class="down-btn u-flex u-row-between">
+			<text @click="chongzhi">重置</text>
+			<text @click="toinfo">计算</text>
+		</view>
+		<u-select v-model="qixianshow" :list="qixianlist" label-name="name" value-name="value" @confirm="changeqixian"></u-select>
+		<u-select v-model="shoufushow" :list="shoufulist" label-name="name" value-name="value" @confirm="changeshoufu"></u-select>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				tabs: ['商业贷款', '公积金贷款', '组合贷款'],
+				current: 0,
+				denge: 1,
+				jisuan: 1,
+				shoufu: 1,
+
+
+				shangyeprice: '',
+				shangyemianji: '',
+				shangyelilv: '',
+
+				gongjijinprice: '',
+				gongjijinmianji: '',
+				gongjijinlilv: '',
+
+				zuheprice: '',
+				zuhemianji: '',
+
+				qixianname: '30年(360期)',
+				qixianvalue: '360',
+				qixianlist: [],
+				qixianshow: false,
+
+				shoufuname: '',
+				shoufuvalue: '',
+				shoufulist: [],
+				shoufushow: false,
+
+				config: {}
+			}
+		},
+		onLoad() {
+			this.getdata()
+		},
+		computed: {
+
+		},
+		methods: {
+			chongzhi() {
+				this.shangyeprice = ''
+				this.shangyemianji = ''
+				this.shangyelilv = this.config.business_interest_rate
+
+				this.gongjijinprice = ''
+				this.gongjijinmianji = ''
+				this.gongjijinlilv = this.config.accumulation_interest_rate
+
+				this.zuheprice = ''
+				this.zuhemianji = ''
+
+				this.qixianname = '30年(360期)'
+				this.qixianvalue = 360
+
+				this.shoufuname = `${this.config.first_home_divide}成`
+				this.shoufuvalue = this.config.first_home_divide
+
+				this.denge = 1
+				this.jisuan = 1
+				this.shoufu = 1
+			},
+			changejisuan() {
+				this.changezuhe()
+			},
+			changechengshu(e) {
+				if (e == 1) {
+					this.shoufuname = `${this.config.first_home_divide}成`
+					this.shoufuvalue = this.config.first_home_divide
+				} else {
+					this.shoufuname = `${this.config.second_home_divide}成`
+					this.shoufuvalue = this.config.second_home_divide
+				}
+				this.changezuhe()
+			},
+			changezuhe() {
+				if (this.zuheprice > 0) {
+					if (this.jisuan == 1) {
+						this.shangyeprice = (this.zuheprice * (1 - this.shoufuvalue / 10)).toFixed(2)
+					} else {
+						this.shangyeprice = ((this.zuheprice * this.zuhemianji) / 10000 * (1 - this.shoufuvalue / 10)).toFixed(2)
+					}
+					this.gongjijinprice = ''
+				} else {
+					this.gongjijinprice = ''
+					this.shangyeprice = ''
+				}
+			},
+			changezuhe1(e) {
+				if (this.zuheprice > 0) {
+					if (this.jisuan == 1) {
+						var zuheprice = (this.zuheprice * (1 - this.shoufuvalue / 10)).toFixed(2)
+					} else {
+						var zuheprice = ((this.zuheprice * this.zuhemianji) / 10000 * (1 - this.shoufuvalue / 10)).toFixed(2)
+					}
+					if (this.shangyeprice > zuheprice) {
+						this.$u.toast(`商业贷款必须小于${zuheprice}万元`)
+						this.gongjijinprice = ''
+						return
+					}
+					this.gongjijinprice = (zuheprice - this.shangyeprice).toFixed(2)
+				}
+			},
+			changezuhe2(e) {
+				if (this.zuheprice > 0) {
+					if (this.jisuan == 1) {
+						var zuheprice = (this.zuheprice * (1 - this.shoufuvalue / 10)).toFixed(2)
+					} else {
+						var zuheprice = ((this.zuheprice * this.zuhemianji) / 10000 * (1 - this.shoufuvalue / 10)).toFixed(2)
+					}
+					if (this.gongjijinprice > zuheprice) {
+						this.$u.toast(`公积金贷款必须小于${zuheprice}万元`)
+						this.shangyeprice = ''
+						return
+					}
+					this.shangyeprice = (zuheprice - this.gongjijinprice).toFixed(2)
+				}
+			},
+			changeshoufu(e) {
+				this.shoufuname = e[0].label
+				this.shoufuvalue = e[0].value
+				if (this.current == 2) {
+					this.changezuhe()
+				}
+			},
+			changeqixian(e) {
+				this.qixianname = e[0].label
+				this.qixianvalue = e[0].value
+			},
+			getdata() {
+				this.$u.post('/api/Calculator/divide_list').then(res => {
+					res.data.forEach(val => {
+						this.shoufulist.push({
+							name: `${val.name}成`,
+							value: val.name
+						})
+					})
+				})
+				for (let i = 1; i <= 30; i++) {
+					this.qixianlist.push({
+						name: `${i}年(${i * 12}期)`,
+						value: i * 12
+					})
+				}
+				this.$u.post('/api/Index/platform_config').then(res => {
+					if (res.code == 1) {
+						this.config = res.data
+						this.shangyelilv = this.config.business_interest_rate
+						this.gongjijinlilv = this.config.accumulation_interest_rate
+						this.shoufuname = `${this.config.first_home_divide}成`
+						this.shoufuvalue = this.config.first_home_divide
+					}
+				})
+			},
+			change(index) {
+				this.current = index
+				this.shangyelilv = this.config.business_interest_rate
+				this.gongjijinlilv = this.config.accumulation_interest_rate
+				this.shoufu = 1
+				this.shoufuname = `${this.config.first_home_divide}成`
+				this.shoufuvalue = this.config.first_home_divide
+			},
+			toinfo() {
+				if (this.current == 0) {
+					if (this.jisuan == 1) {
+						if (!this.shangyeprice) {
+							this.$u.toast("请输入贷款总额")
+							return
+						}
+					}
+					if (this.jisuan == 2) {
+						if (!this.shangyeprice || !this.shangyemianji) {
+							this.$u.toast("请输入单价面积")
+							return
+						}
+						if (!this.shoufuname) {
+							this.$u.toast("请选择首付成数")
+							return
+						}
+					}
+					if (!this.shangyelilv) {
+						this.$u.toast("请输入贷款利率")
+						return
+					}
+					var params = {
+						repay_type: this.denge,
+						calculate_amount: this.jisuan == 1 ? (this.shangyeprice * 10000) : (this.shangyeprice * this.shangyemianji * (1 - this.shoufuvalue / 10)),
+						interest_rate: this.shangyelilv,
+						time_limit: this.qixianvalue
+					}
+					this.$u.post('/api/Calculator/not_group_loans', params).then(res => {
+						if (res.code == 1) {
+							uni.navigateTo({
+								url: "./counter-info?params=" + encodeURIComponent(JSON.stringify(params)) + "&type=1"
+							})
+						} else {
+							this.$u.toast(res.msg)
+						}
+					})
+				}
+				if (this.current == 1) {
+					if (this.jisuan == 1) {
+						if (!this.gongjijinprice) {
+							this.$u.toast("请输入贷款总额")
+							return
+						}
+					}
+					if (this.jisuan == 2) {
+						if (!this.gongjijinprice || !this.gongjijinmianji) {
+							this.$u.toast("请输入单价面积")
+							return
+						}
+						if (!this.shoufuname) {
+							this.$u.toast("请选择首付成数")
+							return
+						}
+					}
+					if (!this.gongjijinlilv) {
+						this.$u.toast("请输入贷款利率")
+						return
+					}
+					var params = {
+						repay_type: this.denge,
+						calculate_amount: this.jisuan == 1 ? (this.gongjijinprice * 10000) : (this.gongjijinprice * this.gongjijinmianji * (1 - this.shoufuvalue / 10)),
+						interest_rate: this.gongjijinlilv,
+						time_limit: this.qixianvalue
+					}
+					this.$u.post('/api/Calculator/not_group_loans', params).then(res => {
+						if (res.code == 1) {
+							uni.navigateTo({
+								url: "./counter-info?params=" + encodeURIComponent(JSON.stringify(params)) + "&type=2"
+							})
+						} else {
+							this.$u.toast(res.msg)
+						}
+					})
+				}
+				if (this.current == 2) {
+					if (this.jisuan == 1) {
+						if (!this.zuheprice) {
+							this.$u.toast("请输入房屋总价")
+							return
+						}
+					}
+					if (this.jisuan == 2) {
+						if (!this.zuheprice || !this.zuhemianji) {
+							this.$u.toast("请输入单价面积")
+							return
+						}
+						if (!this.shoufuname) {
+							this.$u.toast("请选择首付成数")
+							return
+						}
+					}
+					if (!this.shangyelilv) {
+						this.$u.toast("请输入商业贷款利率")
+						return
+					}
+					if (!this.gongjijinlilv) {
+						this.$u.toast("请输入公积金贷款利率")
+						return
+					}
+					var all = Number(this.gongjijinprice) + Number(this.shangyeprice)
+					if (this.jisuan == 1) {
+						var zuheprice = (this.zuheprice * (1 - this.shoufuvalue / 10)).toFixed(2)
+					} else {
+						var zuheprice = ((this.zuheprice * this.zuhemianji) / 10000 * (1 - this.shoufuvalue / 10)).toFixed(2)
+					}
+					if (all > zuheprice) {
+						this.$u.toast(`贷款金额不得大于${zuheprice}万元`)
+						return
+					}
+					var params = {
+						repay_type: this.denge,
+						business_calculate_amount: this.jisuan == 1 ? (this.shangyeprice * 10000) : (this.shangyeprice * this.shangyemianji * (1 - this.shoufuvalue / 10)),
+						business_interest_rate: this.shangyelilv,
+						accumulation_calculate_amount: this.jisuan == 1 ? (this.gongjijinprice * 10000) : (this.gongjijinprice * this.gongjijinmianji * (1 - this.shoufuvalue / 10)),
+						accumulation_interest_rate: this.gongjijinlilv,
+						time_limit: this.qixianvalue
+					}
+					uni.showLoading({
+						mask: true,
+						title: "请稍后"
+					})
+					this.$u.post('/api/Calculator/group_loans', params).then(res => {
+						if (res.code == 1) {
+							uni.navigateTo({
+								url: "./counter-info?params=" + encodeURIComponent(JSON.stringify(params)) + "&type=3"
+							})
+						} else {
+							this.$u.toast(res.msg)
+						}
+					})
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.counter {
+		.tabs-title {
+			padding: 24rpx 240rpx 0 24rpx;
+			font-size: 24rpx;
+			font-family: PingFangSC-Medium, PingFang SC;
+			font-weight: 500;
+			color: #333333;
+			background-color: #fff;
+		}
+
+		.jisuan-row {
+			height: 82rpx;
+			border-bottom: 2rpx solid #F6F6F6;
+			padding: 0 24rpx;
+			background-color: #fff;
+
+			.row-left {
+				width: 294rpx;
+				font-size: 24rpx;
+				font-weight: 400;
+				color: #999999;
+			}
+
+			.row-right {
+				.input {
+					font-size: 24rpx;
+					flex: 1;
+					margin-right: 10rpx;
+				}
+
+				.text {
+					font-size: 24rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #CCCCCC;
+
+				}
+			}
+		}
+
+		.down-btn {
+			position: fixed;
+			z-index: 10;
+			width: 750rpx;
+			height: 166rpx;
+			background: #FFFFFF;
+			bottom: 0;
+			left: 0;
+			padding: 0 24rpx 54rpx 24rpx;
+
+			text:first-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #FFA120;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+
+			text:last-child {
+				width: 338rpx;
+				line-height: 84rpx;
+				background: #1F7EFF;
+				border-radius: 20rpx;
+				text-align: center;
+				font-size: 28rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #FFFFFF;
+			}
+		}
+
+		.dataCenter-header {
+			padding: 0 70rpx;
+			height: 120rpx;
+			background-color: #fff;
+			position: sticky;
+			top: 0;
+			left: 0;
+			z-index: 10;
+			border-bottom: 2rpx solid #F5F5F5;
+
+			.header-item {
+				.active {
+					font-size: 28rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #666666;
+				}
+
+				.active1 {
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #333333;
+				}
+
+				image {
+					width: 50rpx;
+					height: 20rpx;
+					// margin-top: 10rpx;
+				}
+			}
+		}
+	}
+</style>

+ 167 - 0
pagesA/index/dataCenter.vue

@@ -0,0 +1,167 @@
+<template>
+	<view class="newHouses">
+		<view class="newHouses-bg"></view>
+		<view class="renqi-box u-flex">
+			<u-icon name="heart-fill" color="#fff" size="26"></u-icon>
+			<text class="text">累计人气值 {{popularity}}</text>
+		</view>
+		<view class="swiper-box">
+			<u-swiper :list="swiperlisi" height="376" name="logo" @click="tobanner" border-radius="20"></u-swiper>
+		</view>
+		<view class="newHouses-list">
+			<view class="newHouses-title u-flex u-row-between">
+				<text>全部楼盘</text>
+				<image src="../../static/images/shaixuan.png" @click="opensearch" mode=""></image>
+			</view>
+			<view class="u-flex u-row-between u-flex-wrap">
+				<view v-for="(item,index) in list" :key="index">
+					<gf-goods :data="item"></gf-goods>
+				</view>
+			</view>
+		</view>
+		<gf-search ref="search" @shaixuan="shaixuan"></gf-search>
+	</view>
+</template>
+
+<script>
+	import {
+		mapState,
+		mapActions,
+		mapMutations,
+		mapGetters
+	} from 'vuex'
+	export default {
+		data() {
+			return {
+				title:'',
+				swiperlisi: [],
+				page: 1,
+				list: [],  
+				params: {}
+			}
+		},
+		onLoad(options) {
+			this.title = options.title
+			this.getlist()
+			this.getbanner()
+		},
+		onShow() {
+			if(this.title!=''){
+				uni.setNavigationBarTitle({
+					title: this.title //这是修改后的导航栏文字
+				})
+			}
+		},
+		computed: {
+			...mapState(["popularity"])
+		},
+		onReachBottom() {
+			if (this.list.length % 20 == 0) {
+				this.page++
+				this.getlist()
+			}
+		},
+		methods: {
+			shaixuan(e) {
+				this.params = e
+				this.page = 1
+				this.list = []
+				this.getlist()
+			},
+			tobanner(e) {
+				if (this.swiperlisi[e].url) {
+					uni.navigateTo({
+						url: this.swiperlisi[e].url
+					})
+				}
+			},
+			getbanner() {
+				this.$u.post('/api/Index/banner', {
+					location: 2
+				}).then(res => {
+					this.swiperlisi = res.data
+				})
+			},
+			getlist() {
+				console.log(this.params);
+				this.$u.post('/api/Index/property_list', {
+					page: this.page,
+					page_num: 20,
+					type: 2,
+					cate_name:this.title,
+					...this.params
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			},
+			opensearch() {
+				this.$refs.search.show = true
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.newHouses {
+		position: relative;
+		z-index: 1;
+
+		.newHouses-list {
+			width: 702rpx;
+			background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 38%, rgba(255, 255, 255, 0) 100%);
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx;
+
+			.newHouses-title {
+				padding: 20rpx 0;
+
+				text {
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #131415;
+				}
+
+				image {
+					width: 32rpx;
+					height: 32rpx;
+				}
+			}
+		}
+
+		.swiper-box {
+			padding: 0 24rpx;
+			margin-bottom: 20rpx;
+		}
+
+		.renqi-box {
+			width: 750rpx;
+			height: 56rpx;
+			background: rgba(256, 256, 256, 0.1);
+			padding: 0 24rpx;
+			margin-bottom: 24rpx;
+
+			.text {
+				font-size: 24rpx;
+				font-family: Helvetica;
+				color: #FFFFFF;
+				margin-left: 10rpx;
+			}
+		}
+
+		.newHouses-bg {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 750rpx;
+			height: 556rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			z-index: -1;
+		}
+	}
+</style>

+ 167 - 0
pagesA/index/newHouses.vue

@@ -0,0 +1,167 @@
+<template>
+	<view class="newHouses">
+		<view class="newHouses-bg"></view>
+		<view class="renqi-box u-flex">
+			<u-icon name="heart-fill" color="#fff" size="26"></u-icon>
+			<text class="text">累计人气值 {{popularity}}</text>
+		</view>
+		<view class="swiper-box">
+			<u-swiper :list="swiperlisi" height="376" name="logo" @click="tobanner" border-radius="20"></u-swiper>
+		</view>
+		<view class="newHouses-list">
+			<view class="newHouses-title u-flex u-row-between">
+				<text>全部楼盘</text>
+				<image src="../../static/images/shaixuan.png" @click="opensearch" mode=""></image>
+			</view>
+			<view class="u-flex u-row-between u-flex-wrap">
+				<view v-for="(item,index) in list" :key="index">
+					<gf-goods :data="item"></gf-goods>
+				</view>
+			</view>
+		</view>
+		<gf-search ref="search" @shaixuan="shaixuan"></gf-search>
+	</view>
+</template>
+
+<script>
+	import {
+		mapState,
+		mapActions,
+		mapMutations,
+		mapGetters
+	} from 'vuex'
+	export default {
+		data() {
+			return {
+				title:'',
+				swiperlisi: [],
+				page: 1,
+				list: [],  
+				params: {}
+			}
+		},
+		onLoad(options) {
+			this.title = options.title
+			this.getlist()
+			this.getbanner()
+		},
+		onShow() {
+			if(this.title!=''){
+				uni.setNavigationBarTitle({
+					title: this.title //这是修改后的导航栏文字
+				})
+			}
+		},
+		computed: {
+			...mapState(["popularity"])
+		},
+		onReachBottom() {
+			if (this.list.length % 20 == 0) {
+				this.page++
+				this.getlist()
+			}
+		},
+		methods: {
+			shaixuan(e) {
+				this.params = e
+				this.page = 1
+				this.list = []
+				this.getlist()
+			},
+			tobanner(e) {
+				if (this.swiperlisi[e].url) {
+					uni.navigateTo({
+						url: this.swiperlisi[e].url
+					})
+				}
+			},
+			getbanner() {
+				this.$u.post('/api/Index/banner', {
+					location: 2
+				}).then(res => {
+					this.swiperlisi = res.data
+				})
+			},
+			getlist() {
+				console.log(this.params);
+				this.$u.post('/api/Index/property_list', {
+					page: this.page,
+					page_num: 20,
+					type: 2,
+					cate_name:this.title,
+					...this.params
+				}).then(res => {
+					this.list = this.list.concat(res.data)
+				})
+			},
+			opensearch() {
+				this.$refs.search.show = true
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F6F6F6;
+	}
+
+	.newHouses {
+		position: relative;
+		z-index: 1;
+
+		.newHouses-list {
+			width: 702rpx;
+			background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 38%, rgba(255, 255, 255, 0) 100%);
+			border-radius: 20rpx;
+			margin: 20rpx auto;
+			padding: 0 20rpx;
+
+			.newHouses-title {
+				padding: 20rpx 0;
+
+				text {
+					font-size: 34rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #131415;
+				}
+
+				image {
+					width: 32rpx;
+					height: 32rpx;
+				}
+			}
+		}
+
+		.swiper-box {
+			padding: 0 24rpx;
+			margin-bottom: 20rpx;
+		}
+
+		.renqi-box {
+			width: 750rpx;
+			height: 56rpx;
+			background: rgba(256, 256, 256, 0.1);
+			padding: 0 24rpx;
+			margin-bottom: 24rpx;
+
+			.text {
+				font-size: 24rpx;
+				font-family: Helvetica;
+				color: #FFFFFF;
+				margin-left: 10rpx;
+			}
+		}
+
+		.newHouses-bg {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 750rpx;
+			height: 556rpx;
+			background: linear-gradient(180deg, #1E7DFF 0%, #F6F6F6 100%);
+			z-index: -1;
+		}
+	}
+</style>

+ 1295 - 0
platforms/h5/common/OrbitControls.js

@@ -0,0 +1,1295 @@
+import {
+	EventDispatcher,
+	MOUSE,
+	Quaternion,
+	Spherical,
+	TOUCH,
+	Vector2,
+	Vector3
+} from './three.js';
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+//    Orbit - left mouse / touch: one-finger move
+//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+const _changeEvent = { type: 'change' };
+const _startEvent = { type: 'start' };
+const _endEvent = { type: 'end' };
+
+class OrbitControls extends EventDispatcher {
+
+	constructor( object, domElement ) {
+
+		super();
+
+		this.object = object;
+		this.domElement = domElement;
+		this.domElement.style.touchAction = 'none'; // disable touch scroll
+
+		// Set to false to disable this control
+		this.enabled = true;
+
+		// "target" sets the location of focus, where the object orbits around
+		this.target = new Vector3();
+
+		// How far you can dolly in and out ( PerspectiveCamera only )
+		this.minDistance = 0;
+		this.maxDistance = Infinity;
+
+		// How far you can zoom in and out ( OrthographicCamera only )
+		this.minZoom = 0;
+		this.maxZoom = Infinity;
+
+		// How far you can orbit vertically, upper and lower limits.
+		// Range is 0 to Math.PI radians.
+		this.minPolarAngle = 0; // radians
+		this.maxPolarAngle = Math.PI; // radians
+
+		// How far you can orbit horizontally, upper and lower limits.
+		// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
+		this.minAzimuthAngle = - Infinity; // radians
+		this.maxAzimuthAngle = Infinity; // radians
+
+		// Set to true to enable damping (inertia)
+		// If damping is enabled, you must call controls.update() in your animation loop
+		this.enableDamping = false;
+		this.dampingFactor = 0.05;
+
+		// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+		// Set to false to disable zooming
+		this.enableZoom = true;
+		this.zoomSpeed = 1.0;
+
+		// Set to false to disable rotating
+		this.enableRotate = true;
+		this.rotateSpeed = 1.0;
+
+		// Set to false to disable panning
+		this.enablePan = true;
+		this.panSpeed = 1.0;
+		this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
+		this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
+
+		// Set to true to automatically rotate around the target
+		// If auto-rotate is enabled, you must call controls.update() in your animation loop
+		this.autoRotate = false;
+		this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
+
+		// The four arrow keys
+		this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
+
+		// Mouse buttons
+		this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
+
+		// Touch fingers
+		this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
+
+		// for reset
+		this.target0 = this.target.clone();
+		this.position0 = this.object.position.clone();
+		this.zoom0 = this.object.zoom;
+
+		// the target DOM element for key events
+		this._domElementKeyEvents = null;
+
+		//
+		// public methods
+		//
+
+		this.getPolarAngle = function () {
+
+			return spherical.phi;
+
+		};
+
+		this.getAzimuthalAngle = function () {
+
+			return spherical.theta;
+
+		};
+
+		this.getDistance = function () {
+
+			return this.object.position.distanceTo( this.target );
+
+		};
+
+		this.listenToKeyEvents = function ( domElement ) {
+
+			domElement.addEventListener( 'keydown', onKeyDown );
+			this._domElementKeyEvents = domElement;
+
+		};
+
+		this.stopListenToKeyEvents = function () {
+
+			this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
+			this._domElementKeyEvents = null;
+
+		};
+
+		this.saveState = function () {
+
+			scope.target0.copy( scope.target );
+			scope.position0.copy( scope.object.position );
+			scope.zoom0 = scope.object.zoom;
+
+		};
+
+		this.reset = function () {
+
+			scope.target.copy( scope.target0 );
+			scope.object.position.copy( scope.position0 );
+			scope.object.zoom = scope.zoom0;
+
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent( _changeEvent );
+
+			scope.update();
+
+			state = STATE.NONE;
+
+		};
+
+		// this method is exposed, but perhaps it would be better if we can make it private...
+		this.update = function () {
+
+			const offset = new Vector3();
+
+			// so camera.up is the orbit axis
+			const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
+			const quatInverse = quat.clone().invert();
+
+			const lastPosition = new Vector3();
+			const lastQuaternion = new Quaternion();
+
+			const twoPI = 2 * Math.PI;
+
+			return function update() {
+
+				const position = scope.object.position;
+
+				offset.copy( position ).sub( scope.target );
+
+				// rotate offset to "y-axis-is-up" space
+				offset.applyQuaternion( quat );
+
+				// angle from z-axis around y-axis
+				spherical.setFromVector3( offset );
+
+				if ( scope.autoRotate && state === STATE.NONE ) {
+
+					rotateLeft( getAutoRotationAngle() );
+
+				}
+
+				if ( scope.enableDamping ) {
+
+					spherical.theta += sphericalDelta.theta * scope.dampingFactor;
+					spherical.phi += sphericalDelta.phi * scope.dampingFactor;
+
+				} else {
+
+					spherical.theta += sphericalDelta.theta;
+					spherical.phi += sphericalDelta.phi;
+
+				}
+
+				// restrict theta to be between desired limits
+
+				let min = scope.minAzimuthAngle;
+				let max = scope.maxAzimuthAngle;
+
+				if ( isFinite( min ) && isFinite( max ) ) {
+
+					if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
+
+					if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
+
+					if ( min <= max ) {
+
+						spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
+
+					} else {
+
+						spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
+							Math.max( min, spherical.theta ) :
+							Math.min( max, spherical.theta );
+
+					}
+
+				}
+
+				// restrict phi to be between desired limits
+				spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+
+				spherical.makeSafe();
+
+
+				spherical.radius *= scale;
+
+				// restrict radius to be between desired limits
+				spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+
+				// move target to panned location
+
+				if ( scope.enableDamping === true ) {
+
+					scope.target.addScaledVector( panOffset, scope.dampingFactor );
+
+				} else {
+
+					scope.target.add( panOffset );
+
+				}
+
+				offset.setFromSpherical( spherical );
+
+				// rotate offset back to "camera-up-vector-is-up" space
+				offset.applyQuaternion( quatInverse );
+
+				position.copy( scope.target ).add( offset );
+
+				scope.object.lookAt( scope.target );
+
+				if ( scope.enableDamping === true ) {
+
+					sphericalDelta.theta *= ( 1 - scope.dampingFactor );
+					sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+
+					panOffset.multiplyScalar( 1 - scope.dampingFactor );
+
+				} else {
+
+					sphericalDelta.set( 0, 0, 0 );
+
+					panOffset.set( 0, 0, 0 );
+
+				}
+
+				scale = 1;
+
+				// update condition is:
+				// min(camera displacement, camera rotation in radians)^2 > EPS
+				// using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+				if ( zoomChanged ||
+					lastPosition.distanceToSquared( scope.object.position ) > EPS ||
+					8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+					scope.dispatchEvent( _changeEvent );
+
+					lastPosition.copy( scope.object.position );
+					lastQuaternion.copy( scope.object.quaternion );
+					zoomChanged = false;
+
+					return true;
+
+				}
+
+				return false;
+
+			};
+
+		}();
+
+		this.dispose = function () {
+
+			scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
+
+			scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
+			scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
+			scope.domElement.removeEventListener( 'wheel', onMouseWheel );
+
+			scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+			scope.domElement.removeEventListener( 'pointerup', onPointerUp );
+
+
+			if ( scope._domElementKeyEvents !== null ) {
+
+				scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
+				scope._domElementKeyEvents = null;
+
+			}
+
+			//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+		};
+
+		//
+		// internals
+		//
+
+		const scope = this;
+
+		const STATE = {
+			NONE: - 1,
+			ROTATE: 0,
+			DOLLY: 1,
+			PAN: 2,
+			TOUCH_ROTATE: 3,
+			TOUCH_PAN: 4,
+			TOUCH_DOLLY_PAN: 5,
+			TOUCH_DOLLY_ROTATE: 6
+		};
+
+		let state = STATE.NONE;
+
+		const EPS = 0.000001;
+
+		// current position in spherical coordinates
+		const spherical = new Spherical();
+		const sphericalDelta = new Spherical();
+
+		let scale = 1;
+		const panOffset = new Vector3();
+		let zoomChanged = false;
+
+		const rotateStart = new Vector2();
+		const rotateEnd = new Vector2();
+		const rotateDelta = new Vector2();
+
+		const panStart = new Vector2();
+		const panEnd = new Vector2();
+		const panDelta = new Vector2();
+
+		const dollyStart = new Vector2();
+		const dollyEnd = new Vector2();
+		const dollyDelta = new Vector2();
+
+		const pointers = [];
+		const pointerPositions = {};
+
+		function getAutoRotationAngle() {
+
+			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+		}
+
+		function getZoomScale() {
+
+			return Math.pow( 0.95, scope.zoomSpeed );
+
+		}
+
+		function rotateLeft( angle ) {
+
+			sphericalDelta.theta -= angle;
+
+		}
+
+		function rotateUp( angle ) {
+
+			sphericalDelta.phi -= angle;
+
+		}
+
+		const panLeft = function () {
+
+			const v = new Vector3();
+
+			return function panLeft( distance, objectMatrix ) {
+
+				v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+				v.multiplyScalar( - distance );
+
+				panOffset.add( v );
+
+			};
+
+		}();
+
+		const panUp = function () {
+
+			const v = new Vector3();
+
+			return function panUp( distance, objectMatrix ) {
+
+				if ( scope.screenSpacePanning === true ) {
+
+					v.setFromMatrixColumn( objectMatrix, 1 );
+
+				} else {
+
+					v.setFromMatrixColumn( objectMatrix, 0 );
+					v.crossVectors( scope.object.up, v );
+
+				}
+
+				v.multiplyScalar( distance );
+
+				panOffset.add( v );
+
+			};
+
+		}();
+
+		// deltaX and deltaY are in pixels; right and down are positive
+		const pan = function () {
+
+			const offset = new Vector3();
+
+			return function pan( deltaX, deltaY ) {
+
+				const element = scope.domElement;
+
+				if ( scope.object.isPerspectiveCamera ) {
+
+					// perspective
+					const position = scope.object.position;
+					offset.copy( position ).sub( scope.target );
+					let targetDistance = offset.length();
+
+					// half of the fov is center to top of screen
+					targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
+
+					// we use only clientHeight here so aspect ratio does not distort speed
+					panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+					panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+				} else if ( scope.object.isOrthographicCamera ) {
+
+					// orthographic
+					panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+					panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+				} else {
+
+					// camera neither orthographic nor perspective
+					console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+					scope.enablePan = false;
+
+				}
+
+			};
+
+		}();
+
+		function dollyOut( dollyScale ) {
+
+			if ( scope.object.isPerspectiveCamera ) {
+
+				scale /= dollyScale;
+
+			} else if ( scope.object.isOrthographicCamera ) {
+
+				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
+
+			} else {
+
+				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+				scope.enableZoom = false;
+
+			}
+
+		}
+
+		function dollyIn( dollyScale ) {
+
+			if ( scope.object.isPerspectiveCamera ) {
+
+				scale *= dollyScale;
+
+			} else if ( scope.object.isOrthographicCamera ) {
+
+				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
+
+			} else {
+
+				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+				scope.enableZoom = false;
+
+			}
+
+		}
+
+		//
+		// event callbacks - update the object state
+		//
+
+		function handleMouseDownRotate( event ) {
+
+			rotateStart.set( event.clientX, event.clientY );
+
+		}
+
+		function handleMouseDownDolly( event ) {
+
+			dollyStart.set( event.clientX, event.clientY );
+
+		}
+
+		function handleMouseDownPan( event ) {
+
+			panStart.set( event.clientX, event.clientY );
+
+		}
+
+		function handleMouseMoveRotate( event ) {
+
+			rotateEnd.set( event.clientX, event.clientY );
+
+			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+			const element = scope.domElement;
+
+			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+			rotateStart.copy( rotateEnd );
+
+			scope.update();
+
+		}
+
+		function handleMouseMoveDolly( event ) {
+
+			dollyEnd.set( event.clientX, event.clientY );
+
+			dollyDelta.subVectors( dollyEnd, dollyStart );
+
+			if ( dollyDelta.y > 0 ) {
+
+				dollyOut( getZoomScale() );
+
+			} else if ( dollyDelta.y < 0 ) {
+
+				dollyIn( getZoomScale() );
+
+			}
+
+			dollyStart.copy( dollyEnd );
+
+			scope.update();
+
+		}
+
+		function handleMouseMovePan( event ) {
+
+			panEnd.set( event.clientX, event.clientY );
+
+			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+			pan( panDelta.x, panDelta.y );
+
+			panStart.copy( panEnd );
+
+			scope.update();
+
+		}
+
+		function handleMouseWheel( event ) {
+
+			if ( event.deltaY < 0 ) {
+
+				dollyIn( getZoomScale() );
+
+			} else if ( event.deltaY > 0 ) {
+
+				dollyOut( getZoomScale() );
+
+			}
+
+			scope.update();
+
+		}
+
+		function handleKeyDown( event ) {
+
+			let needsUpdate = false;
+
+			switch ( event.code ) {
+
+				case scope.keys.UP:
+
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
+
+					} else {
+
+						pan( 0, scope.keyPanSpeed );
+
+					}
+
+					needsUpdate = true;
+					break;
+
+				case scope.keys.BOTTOM:
+
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
+
+					} else {
+
+						pan( 0, - scope.keyPanSpeed );
+
+					}
+
+					needsUpdate = true;
+					break;
+
+				case scope.keys.LEFT:
+
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
+
+					} else {
+
+						pan( scope.keyPanSpeed, 0 );
+
+					}
+
+					needsUpdate = true;
+					break;
+
+				case scope.keys.RIGHT:
+
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
+
+					} else {
+
+						pan( - scope.keyPanSpeed, 0 );
+
+					}
+
+					needsUpdate = true;
+					break;
+
+			}
+
+			if ( needsUpdate ) {
+
+				// prevent the browser from scrolling on cursor keys
+				event.preventDefault();
+
+				scope.update();
+
+			}
+
+
+		}
+
+		function handleTouchStartRotate() {
+
+			if ( pointers.length === 1 ) {
+
+				rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
+
+			} else {
+
+				const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+				const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+
+				rotateStart.set( x, y );
+
+			}
+
+		}
+
+		function handleTouchStartPan() {
+
+			if ( pointers.length === 1 ) {
+
+				panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
+
+			} else {
+
+				const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+				const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+
+				panStart.set( x, y );
+
+			}
+
+		}
+
+		function handleTouchStartDolly() {
+
+			const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
+			const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
+
+			const distance = Math.sqrt( dx * dx + dy * dy );
+
+			dollyStart.set( 0, distance );
+
+		}
+
+		function handleTouchStartDollyPan() {
+
+			if ( scope.enableZoom ) handleTouchStartDolly();
+
+			if ( scope.enablePan ) handleTouchStartPan();
+
+		}
+
+		function handleTouchStartDollyRotate() {
+
+			if ( scope.enableZoom ) handleTouchStartDolly();
+
+			if ( scope.enableRotate ) handleTouchStartRotate();
+
+		}
+
+		function handleTouchMoveRotate( event ) {
+
+			if ( pointers.length == 1 ) {
+
+				rotateEnd.set( event.pageX, event.pageY );
+
+			} else {
+
+				const position = getSecondPointerPosition( event );
+
+				const x = 0.5 * ( event.pageX + position.x );
+				const y = 0.5 * ( event.pageY + position.y );
+
+				rotateEnd.set( x, y );
+
+			}
+
+			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+			const element = scope.domElement;
+
+			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+			rotateStart.copy( rotateEnd );
+
+		}
+
+		function handleTouchMovePan( event ) {
+
+			if ( pointers.length === 1 ) {
+
+				panEnd.set( event.pageX, event.pageY );
+
+			} else {
+
+				const position = getSecondPointerPosition( event );
+
+				const x = 0.5 * ( event.pageX + position.x );
+				const y = 0.5 * ( event.pageY + position.y );
+
+				panEnd.set( x, y );
+
+			}
+
+			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+			pan( panDelta.x, panDelta.y );
+
+			panStart.copy( panEnd );
+
+		}
+
+		function handleTouchMoveDolly( event ) {
+
+			const position = getSecondPointerPosition( event );
+
+			const dx = event.pageX - position.x;
+			const dy = event.pageY - position.y;
+
+			const distance = Math.sqrt( dx * dx + dy * dy );
+
+			dollyEnd.set( 0, distance );
+
+			dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+
+			dollyOut( dollyDelta.y );
+
+			dollyStart.copy( dollyEnd );
+
+		}
+
+		function handleTouchMoveDollyPan( event ) {
+
+			if ( scope.enableZoom ) handleTouchMoveDolly( event );
+
+			if ( scope.enablePan ) handleTouchMovePan( event );
+
+		}
+
+		function handleTouchMoveDollyRotate( event ) {
+
+			if ( scope.enableZoom ) handleTouchMoveDolly( event );
+
+			if ( scope.enableRotate ) handleTouchMoveRotate( event );
+
+		}
+
+		//
+		// event handlers - FSM: listen for events and reset state
+		//
+
+		function onPointerDown( event ) {
+
+			if ( scope.enabled === false ) return;
+
+			if ( pointers.length === 0 ) {
+
+				scope.domElement.setPointerCapture( event.pointerId );
+
+				scope.domElement.addEventListener( 'pointermove', onPointerMove );
+				scope.domElement.addEventListener( 'pointerup', onPointerUp );
+
+			}
+
+			//
+
+			addPointer( event );
+
+			if ( event.pointerType === 'touch' ) {
+
+				onTouchStart( event );
+
+			} else {
+
+				onMouseDown( event );
+
+			}
+
+		}
+
+		function onPointerMove( event ) {
+
+			if ( scope.enabled === false ) return;
+
+			if ( event.pointerType === 'touch' ) {
+
+				onTouchMove( event );
+
+			} else {
+
+				onMouseMove( event );
+
+			}
+
+		}
+
+		function onPointerUp( event ) {
+
+		    removePointer( event );
+
+		    if ( pointers.length === 0 ) {
+
+		        scope.domElement.releasePointerCapture( event.pointerId );
+
+		        scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+		        scope.domElement.removeEventListener( 'pointerup', onPointerUp );
+
+		    }
+
+		    scope.dispatchEvent( _endEvent );
+
+		    state = STATE.NONE;
+
+		}
+
+		function onPointerCancel( event ) {
+
+			removePointer( event );
+
+		}
+
+		function onMouseDown( event ) {
+
+			let mouseAction;
+
+			switch ( event.button ) {
+
+				case 0:
+
+					mouseAction = scope.mouseButtons.LEFT;
+					break;
+
+				case 1:
+
+					mouseAction = scope.mouseButtons.MIDDLE;
+					break;
+
+				case 2:
+
+					mouseAction = scope.mouseButtons.RIGHT;
+					break;
+
+				default:
+
+					mouseAction = - 1;
+
+			}
+
+			switch ( mouseAction ) {
+
+				case MOUSE.DOLLY:
+
+					if ( scope.enableZoom === false ) return;
+
+					handleMouseDownDolly( event );
+
+					state = STATE.DOLLY;
+
+					break;
+
+				case MOUSE.ROTATE:
+
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						if ( scope.enablePan === false ) return;
+
+						handleMouseDownPan( event );
+
+						state = STATE.PAN;
+
+					} else {
+
+						if ( scope.enableRotate === false ) return;
+
+						handleMouseDownRotate( event );
+
+						state = STATE.ROTATE;
+
+					}
+
+					break;
+
+				case MOUSE.PAN:
+
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						if ( scope.enableRotate === false ) return;
+
+						handleMouseDownRotate( event );
+
+						state = STATE.ROTATE;
+
+					} else {
+
+						if ( scope.enablePan === false ) return;
+
+						handleMouseDownPan( event );
+
+						state = STATE.PAN;
+
+					}
+
+					break;
+
+				default:
+
+					state = STATE.NONE;
+
+			}
+
+			if ( state !== STATE.NONE ) {
+
+				scope.dispatchEvent( _startEvent );
+
+			}
+
+		}
+
+		function onMouseMove( event ) {
+
+			switch ( state ) {
+
+				case STATE.ROTATE:
+
+					if ( scope.enableRotate === false ) return;
+
+					handleMouseMoveRotate( event );
+
+					break;
+
+				case STATE.DOLLY:
+
+					if ( scope.enableZoom === false ) return;
+
+					handleMouseMoveDolly( event );
+
+					break;
+
+				case STATE.PAN:
+
+					if ( scope.enablePan === false ) return;
+
+					handleMouseMovePan( event );
+
+					break;
+
+			}
+
+		}
+
+		function onMouseWheel( event ) {
+
+			if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
+
+			event.preventDefault();
+
+			scope.dispatchEvent( _startEvent );
+
+			handleMouseWheel( event );
+
+			scope.dispatchEvent( _endEvent );
+
+		}
+
+		function onKeyDown( event ) {
+
+			if ( scope.enabled === false || scope.enablePan === false ) return;
+
+			handleKeyDown( event );
+
+		}
+
+		function onTouchStart( event ) {
+
+			trackPointer( event );
+
+			switch ( pointers.length ) {
+
+				case 1:
+
+					switch ( scope.touches.ONE ) {
+
+						case TOUCH.ROTATE:
+
+							if ( scope.enableRotate === false ) return;
+
+							handleTouchStartRotate();
+
+							state = STATE.TOUCH_ROTATE;
+
+							break;
+
+						case TOUCH.PAN:
+
+							if ( scope.enablePan === false ) return;
+
+							handleTouchStartPan();
+
+							state = STATE.TOUCH_PAN;
+
+							break;
+
+						default:
+
+							state = STATE.NONE;
+
+					}
+
+					break;
+
+				case 2:
+
+					switch ( scope.touches.TWO ) {
+
+						case TOUCH.DOLLY_PAN:
+
+							if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+							handleTouchStartDollyPan();
+
+							state = STATE.TOUCH_DOLLY_PAN;
+
+							break;
+
+						case TOUCH.DOLLY_ROTATE:
+
+							if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+
+							handleTouchStartDollyRotate();
+
+							state = STATE.TOUCH_DOLLY_ROTATE;
+
+							break;
+
+						default:
+
+							state = STATE.NONE;
+
+					}
+
+					break;
+
+				default:
+
+					state = STATE.NONE;
+
+			}
+
+			if ( state !== STATE.NONE ) {
+
+				scope.dispatchEvent( _startEvent );
+
+			}
+
+		}
+
+		function onTouchMove( event ) {
+
+			trackPointer( event );
+
+			switch ( state ) {
+
+				case STATE.TOUCH_ROTATE:
+
+					if ( scope.enableRotate === false ) return;
+
+					handleTouchMoveRotate( event );
+
+					scope.update();
+
+					break;
+
+				case STATE.TOUCH_PAN:
+
+					if ( scope.enablePan === false ) return;
+
+					handleTouchMovePan( event );
+
+					scope.update();
+
+					break;
+
+				case STATE.TOUCH_DOLLY_PAN:
+
+					if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+					handleTouchMoveDollyPan( event );
+
+					scope.update();
+
+					break;
+
+				case STATE.TOUCH_DOLLY_ROTATE:
+
+					if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+
+					handleTouchMoveDollyRotate( event );
+
+					scope.update();
+
+					break;
+
+				default:
+
+					state = STATE.NONE;
+
+			}
+
+		}
+
+		function onContextMenu( event ) {
+
+			if ( scope.enabled === false ) return;
+
+			event.preventDefault();
+
+		}
+
+		function addPointer( event ) {
+
+			pointers.push( event );
+
+		}
+
+		function removePointer( event ) {
+
+			delete pointerPositions[ event.pointerId ];
+
+			for ( let i = 0; i < pointers.length; i ++ ) {
+
+				if ( pointers[ i ].pointerId == event.pointerId ) {
+
+					pointers.splice( i, 1 );
+					return;
+
+				}
+
+			}
+
+		}
+
+		function trackPointer( event ) {
+
+			let position = pointerPositions[ event.pointerId ];
+
+			if ( position === undefined ) {
+
+				position = new Vector2();
+				pointerPositions[ event.pointerId ] = position;
+
+			}
+
+			position.set( event.pageX, event.pageY );
+
+		}
+
+		function getSecondPointerPosition( event ) {
+
+			const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ];
+
+			return pointerPositions[ pointer.pointerId ];
+
+		}
+
+		//
+
+		scope.domElement.addEventListener( 'contextmenu', onContextMenu );
+
+		scope.domElement.addEventListener( 'pointerdown', onPointerDown );
+		scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
+		scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
+
+		// force an update at start
+
+		this.update();
+
+	}
+
+}
+
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+// This is very similar to OrbitControls, another set of touch behavior
+//
+//    Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
+//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+//    Pan - left mouse, or arrow keys / touch: one-finger move
+
+class MapControls extends OrbitControls {
+
+	constructor( object, domElement ) {
+
+		super( object, domElement );
+
+		this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
+
+		this.mouseButtons.LEFT = MOUSE.PAN;
+		this.mouseButtons.RIGHT = MOUSE.ROTATE;
+
+		this.touches.ONE = TOUCH.PAN;
+		this.touches.TWO = TOUCH.DOLLY_ROTATE;
+
+	}
+
+}
+
+export { OrbitControls, MapControls };

+ 568 - 0
platforms/h5/common/obj/MTLLoader.js

@@ -0,0 +1,568 @@
+import {
+	Color,
+	DefaultLoadingManager,
+	FileLoader,
+	FrontSide,
+	Loader,
+	LoaderUtils,
+	MeshPhongMaterial,
+	RepeatWrapping,
+	TextureLoader,
+	Vector2,
+	sRGBEncoding
+} from '../three.js';
+
+/**
+ * Loads a Wavefront .mtl file specifying materials
+ */
+
+class MTLLoader extends Loader {
+
+	constructor(manager) {
+
+		super(manager);
+
+	}
+
+	/**
+	 * Loads and parses a MTL asset from a URL.
+	 *
+	 * @param {String} url - URL to the MTL file.
+	 * @param {Function} [onLoad] - Callback invoked with the loaded object.
+	 * @param {Function} [onProgress] - Callback for download progress.
+	 * @param {Function} [onError] - Callback for download errors.
+	 *
+	 * @see setPath setResourcePath
+	 *
+	 * @note In order for relative texture references to resolve correctly
+	 * you must call setResourcePath() explicitly prior to load.
+	 */
+	load(url, onLoad, onProgress, onError) {
+
+		const scope = this;
+
+		const path = (this.path === '') ? LoaderUtils.extractUrlBase(url) : this.path;
+
+		const loader = new FileLoader(this.manager);
+		loader.setPath(this.path);
+		loader.setRequestHeader(this.requestHeader);
+		loader.setWithCredentials(this.withCredentials);
+		loader.load(url, function(text) {
+
+			try {
+
+				onLoad(scope.parse(text, path));
+
+			} catch (e) {
+
+				if (onError) {
+
+					onError(e);
+
+				} else {
+
+					console.error(e);
+
+				}
+
+				scope.manager.itemError(url);
+
+			}
+
+		}, onProgress, onError);
+
+	}
+
+	setMaterialOptions(value) {
+
+		this.materialOptions = value;
+		return this;
+
+	}
+
+	/**
+	 * Parses a MTL file.
+	 *
+	 * @param {String} text - Content of MTL file
+	 * @return {MaterialCreator}
+	 *
+	 * @see setPath setResourcePath
+	 *
+	 * @note In order for relative texture references to resolve correctly
+	 * you must call setResourcePath() explicitly prior to parse.
+	 */
+	parse(text, path) {
+
+		const lines = text.split('\n');
+		let info = {};
+		const delimiter_pattern = /\s+/;
+		const materialsInfo = {};
+
+		for (let i = 0; i < lines.length; i++) {
+
+			let line = lines[i];
+			line = line.trim();
+
+			if (line.length === 0 || line.charAt(0) === '#') {
+
+				// Blank line or comment ignore
+				continue;
+
+			}
+
+			const pos = line.indexOf(' ');
+
+			let key = (pos >= 0) ? line.substring(0, pos) : line;
+			key = key.toLowerCase();
+
+			let value = (pos >= 0) ? line.substring(pos + 1) : '';
+			value = value.trim();
+
+			if (key === 'newmtl') {
+
+				// New material
+
+				info = { name: value };
+				materialsInfo[value] = info;
+
+			} else {
+
+				if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') {
+
+					const ss = value.split(delimiter_pattern, 3);
+					info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];
+
+				} else {
+
+					info[key] = value;
+
+				}
+
+			}
+
+		}
+
+		const materialCreator = new MaterialCreator(this.resourcePath || path, this.materialOptions);
+		materialCreator.setCrossOrigin(this.crossOrigin);
+		materialCreator.setManager(this.manager);
+		materialCreator.setMaterials(materialsInfo);
+		return materialCreator;
+
+	}
+
+}
+
+/**
+ * Create a new MTLLoader.MaterialCreator
+ * @param baseUrl - Url relative to which textures are loaded
+ * @param options - Set of options on how to construct the materials
+ *                  side: Which side to apply the material
+ *                        FrontSide (default), THREE.BackSide, THREE.DoubleSide
+ *                  wrap: What type of wrapping to apply for textures
+ *                        RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
+ *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
+ *                                Default: false, assumed to be already normalized
+ *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
+ *                                  Default: false
+ * @constructor
+ */
+
+class MaterialCreator {
+
+	constructor(baseUrl = '', options = {}) {
+
+		this.baseUrl = baseUrl;
+		this.options = options;
+		this.materialsInfo = {};
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+		this.crossOrigin = 'anonymous';
+
+		this.side = (this.options.side !== undefined) ? this.options.side : FrontSide;
+		this.wrap = (this.options.wrap !== undefined) ? this.options.wrap : RepeatWrapping;
+
+	}
+
+	setCrossOrigin(value) {
+
+		this.crossOrigin = value;
+		return this;
+
+	}
+
+	setManager(value) {
+
+		this.manager = value;
+
+	}
+
+	setMaterials(materialsInfo) {
+
+		this.materialsInfo = this.convert(materialsInfo);
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+	}
+
+	convert(materialsInfo) {
+
+		if (!this.options) return materialsInfo;
+
+		const converted = {};
+
+		for (const mn in materialsInfo) {
+
+			// Convert materials info into normalized form based on options
+
+			const mat = materialsInfo[mn];
+
+			const covmat = {};
+
+			converted[mn] = covmat;
+
+			for (const prop in mat) {
+
+				let save = true;
+				let value = mat[prop];
+				const lprop = prop.toLowerCase();
+
+				switch (lprop) {
+
+					case 'kd':
+					case 'ka':
+					case 'ks':
+
+						// Diffuse color (color under white light) using RGB values
+
+						if (this.options && this.options.normalizeRGB) {
+
+							value = [value[0] / 255, value[1] / 255, value[2] / 255];
+
+						}
+
+						if (this.options && this.options.ignoreZeroRGBs) {
+
+							if (value[0] === 0 && value[1] === 0 && value[2] === 0) {
+
+								// ignore
+
+								save = false;
+
+							}
+
+						}
+
+						break;
+
+					default:
+
+						break;
+
+				}
+
+				if (save) {
+
+					covmat[lprop] = value;
+
+				}
+
+			}
+
+		}
+
+		return converted;
+
+	}
+
+	preload() {
+
+		for (const mn in this.materialsInfo) {
+
+			this.create(mn);
+
+		}
+
+	}
+
+	getIndex(materialName) {
+
+		return this.nameLookup[materialName];
+
+	}
+
+	getAsArray() {
+
+		let index = 0;
+
+		for (const mn in this.materialsInfo) {
+
+			this.materialsArray[index] = this.create(mn);
+			this.nameLookup[mn] = index;
+			index++;
+
+		}
+
+		return this.materialsArray;
+
+	}
+
+	create(materialName) {
+
+		if (this.materials[materialName] === undefined) {
+
+			this.createMaterial_(materialName);
+
+		}
+
+		return this.materials[materialName];
+
+	}
+
+	createMaterial_(materialName) {
+
+		// Create material
+
+		const scope = this;
+		const mat = this.materialsInfo[materialName];
+		const params = {
+
+			name: materialName,
+			side: this.side
+
+		};
+
+		function resolveURL(baseUrl, url) {
+
+			if (typeof url !== 'string' || url === '')
+				return '';
+
+			// Absolute URL
+			if (/^https?:\/\//i.test(url)) return url;
+
+			return baseUrl + url;
+
+		}
+
+		function setMapForType(mapType, value) {
+
+			if (params[mapType]) return; // Keep the first encountered texture
+
+			const texParams = scope.getTextureParams(value, params);
+			const map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url));
+
+			map.repeat.copy(texParams.scale);
+			map.offset.copy(texParams.offset);
+
+			map.wrapS = scope.wrap;
+			map.wrapT = scope.wrap;
+
+			if (mapType === 'map' || mapType === 'emissiveMap') {
+
+				map.encoding = sRGBEncoding;
+
+			}
+
+			params[mapType] = map;
+
+		}
+
+		for (const prop in mat) {
+
+			const value = mat[prop];
+			let n;
+
+			if (value === '') continue;
+
+			switch (prop.toLowerCase()) {
+
+				// Ns is material specular exponent
+
+				case 'kd':
+
+					// Diffuse color (color under white light) using RGB values
+
+					params.color = new Color().fromArray(value).convertSRGBToLinear();
+
+					break;
+
+				case 'ks':
+
+					// Specular color (color when light is reflected from shiny surface) using RGB values
+					params.specular = new Color().fromArray(value).convertSRGBToLinear();
+
+					break;
+
+				case 'ke':
+
+					// Emissive using RGB values
+					params.emissive = new Color().fromArray(value).convertSRGBToLinear();
+
+					break;
+
+				case 'map_kd':
+
+					// Diffuse texture map
+
+					setMapForType('map', value);
+
+					break;
+
+				case 'map_ks':
+
+					// Specular map
+
+					setMapForType('specularMap', value);
+
+					break;
+
+				case 'map_ke':
+
+					// Emissive map
+
+					setMapForType('emissiveMap', value);
+
+					break;
+
+				case 'norm':
+
+					setMapForType('normalMap', value);
+
+					break;
+
+				case 'map_bump':
+				case 'bump':
+
+					// Bump texture map
+
+					setMapForType('bumpMap', value);
+
+					break;
+
+				case 'map_d':
+
+					// Alpha map
+
+					setMapForType('alphaMap', value);
+					params.transparent = true;
+
+					break;
+
+				case 'ns':
+
+					// The specular exponent (defines the focus of the specular highlight)
+					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+
+					params.shininess = parseFloat(value);
+
+					break;
+
+				case 'd':
+					n = parseFloat(value);
+
+					if (n < 1) {
+
+						params.opacity = n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				case 'tr':
+					n = parseFloat(value);
+
+					if (this.options && this.options.invertTrProperty) n = 1 - n;
+
+					if (n > 0) {
+
+						params.opacity = 1 - n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				default:
+					break;
+
+			}
+
+		}
+
+		this.materials[materialName] = new MeshPhongMaterial(params);
+		return this.materials[materialName];
+
+	}
+
+	getTextureParams(value, matParams) {
+
+		const texParams = {
+
+			scale: new Vector2(1, 1),
+			offset: new Vector2(0, 0)
+
+		};
+
+		const items = value.split(/\s+/);
+		let pos;
+
+		pos = items.indexOf('-bm');
+
+		if (pos >= 0) {
+
+			matParams.bumpScale = parseFloat(items[pos + 1]);
+			items.splice(pos, 2);
+
+		}
+
+		pos = items.indexOf('-s');
+
+		if (pos >= 0) {
+
+			texParams.scale.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
+			items.splice(pos, 4); // we expect 3 parameters here!
+
+		}
+
+		pos = items.indexOf('-o');
+
+		if (pos >= 0) {
+
+			texParams.offset.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
+			items.splice(pos, 4); // we expect 3 parameters here!
+
+		}
+
+		texParams.url = items.join(' ').trim();
+		return texParams;
+
+	}
+
+	loadTexture(url, mapping, onLoad, onProgress, onError) {
+
+		const manager = (this.manager !== undefined) ? this.manager : DefaultLoadingManager;
+		let loader = manager.getHandler(url);
+
+		if (loader === null) {
+
+			loader = new TextureLoader(manager);
+
+		}
+
+		if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin);
+
+		const texture = loader.load(url, onLoad, onProgress, onError);
+
+		if (mapping !== undefined) texture.mapping = mapping;
+
+		return texture;
+
+	}
+
+}
+
+export { MTLLoader };
+

+ 905 - 0
platforms/h5/common/obj/OBJLoader.js

@@ -0,0 +1,905 @@
+import {
+	BufferGeometry,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	LineBasicMaterial,
+	LineSegments,
+	Loader,
+	Material,
+	Mesh,
+	MeshPhongMaterial,
+	Points,
+	PointsMaterial,
+	Vector3,
+	Color
+} from '../three.js';
+
+// o object_name | g group_name
+const _object_pattern = /^[og]\s*(.+)?/;
+// mtllib file_reference
+const _material_library_pattern = /^mtllib /;
+// usemtl material_name
+const _material_use_pattern = /^usemtl /;
+// usemap map_name
+const _map_use_pattern = /^usemap /;
+const _face_vertex_data_separator_pattern = /\s+/;
+
+const _vA = new Vector3();
+const _vB = new Vector3();
+const _vC = new Vector3();
+
+const _ab = new Vector3();
+const _cb = new Vector3();
+
+const _color = new Color();
+
+function ParserState() {
+
+	const state = {
+		objects: [],
+		object: {},
+
+		vertices: [],
+		normals: [],
+		colors: [],
+		uvs: [],
+
+		materials: {},
+		materialLibraries: [],
+
+		startObject: function ( name, fromDeclaration ) {
+
+			// If the current object (initial from reset) is not from a g/o declaration in the parsed
+			// file. We need to use it for the first parsed g/o to keep things in sync.
+			if ( this.object && this.object.fromDeclaration === false ) {
+
+				this.object.name = name;
+				this.object.fromDeclaration = ( fromDeclaration !== false );
+				return;
+
+			}
+
+			const previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
+
+			if ( this.object && typeof this.object._finalize === 'function' ) {
+
+				this.object._finalize( true );
+
+			}
+
+			this.object = {
+				name: name || '',
+				fromDeclaration: ( fromDeclaration !== false ),
+
+				geometry: {
+					vertices: [],
+					normals: [],
+					colors: [],
+					uvs: [],
+					hasUVIndices: false
+				},
+				materials: [],
+				smooth: true,
+
+				startMaterial: function ( name, libraries ) {
+
+					const previous = this._finalize( false );
+
+					// New usemtl declaration overwrites an inherited material, except if faces were declared
+					// after the material, then it must be preserved for proper MultiMaterial continuation.
+					if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
+
+						this.materials.splice( previous.index, 1 );
+
+					}
+
+					const material = {
+						index: this.materials.length,
+						name: name || '',
+						mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
+						smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
+						groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
+						groupEnd: - 1,
+						groupCount: - 1,
+						inherited: false,
+
+						clone: function ( index ) {
+
+							const cloned = {
+								index: ( typeof index === 'number' ? index : this.index ),
+								name: this.name,
+								mtllib: this.mtllib,
+								smooth: this.smooth,
+								groupStart: 0,
+								groupEnd: - 1,
+								groupCount: - 1,
+								inherited: false
+							};
+							cloned.clone = this.clone.bind( cloned );
+							return cloned;
+
+						}
+					};
+
+					this.materials.push( material );
+
+					return material;
+
+				},
+
+				currentMaterial: function () {
+
+					if ( this.materials.length > 0 ) {
+
+						return this.materials[ this.materials.length - 1 ];
+
+					}
+
+					return undefined;
+
+				},
+
+				_finalize: function ( end ) {
+
+					const lastMultiMaterial = this.currentMaterial();
+					if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
+
+						lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
+						lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
+						lastMultiMaterial.inherited = false;
+
+					}
+
+					// Ignore objects tail materials if no face declarations followed them before a new o/g started.
+					if ( end && this.materials.length > 1 ) {
+
+						for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) {
+
+							if ( this.materials[ mi ].groupCount <= 0 ) {
+
+								this.materials.splice( mi, 1 );
+
+							}
+
+						}
+
+					}
+
+					// Guarantee at least one empty material, this makes the creation later more straight forward.
+					if ( end && this.materials.length === 0 ) {
+
+						this.materials.push( {
+							name: '',
+							smooth: this.smooth
+						} );
+
+					}
+
+					return lastMultiMaterial;
+
+				}
+			};
+
+			// Inherit previous objects material.
+			// Spec tells us that a declared material must be set to all objects until a new material is declared.
+			// If a usemtl declaration is encountered while this new object is being parsed, it will
+			// overwrite the inherited material. Exception being that there was already face declarations
+			// to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+
+			if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
+
+				const declared = previousMaterial.clone( 0 );
+				declared.inherited = true;
+				this.object.materials.push( declared );
+
+			}
+
+			this.objects.push( this.object );
+
+		},
+
+		finalize: function () {
+
+			if ( this.object && typeof this.object._finalize === 'function' ) {
+
+				this.object._finalize( true );
+
+			}
+
+		},
+
+		parseVertexIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+		},
+
+		parseNormalIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+		},
+
+		parseUVIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+		},
+
+		addVertex: function ( a, b, c ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addVertexPoint: function ( a ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+		},
+
+		addVertexLine: function ( a ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+		},
+
+		addNormal: function ( a, b, c ) {
+
+			const src = this.normals;
+			const dst = this.object.geometry.normals;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addFaceNormal: function ( a, b, c ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.normals;
+
+			_vA.fromArray( src, a );
+			_vB.fromArray( src, b );
+			_vC.fromArray( src, c );
+
+			_cb.subVectors( _vC, _vB );
+			_ab.subVectors( _vA, _vB );
+			_cb.cross( _ab );
+
+			_cb.normalize();
+
+			dst.push( _cb.x, _cb.y, _cb.z );
+			dst.push( _cb.x, _cb.y, _cb.z );
+			dst.push( _cb.x, _cb.y, _cb.z );
+
+		},
+
+		addColor: function ( a, b, c ) {
+
+			const src = this.colors;
+			const dst = this.object.geometry.colors;
+
+			if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addUV: function ( a, b, c ) {
+
+			const src = this.uvs;
+			const dst = this.object.geometry.uvs;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ] );
+
+		},
+
+		addDefaultUV: function () {
+
+			const dst = this.object.geometry.uvs;
+
+			dst.push( 0, 0 );
+			dst.push( 0, 0 );
+			dst.push( 0, 0 );
+
+		},
+
+		addUVLine: function ( a ) {
+
+			const src = this.uvs;
+			const dst = this.object.geometry.uvs;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+		},
+
+		addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+			const vLen = this.vertices.length;
+
+			let ia = this.parseVertexIndex( a, vLen );
+			let ib = this.parseVertexIndex( b, vLen );
+			let ic = this.parseVertexIndex( c, vLen );
+
+			this.addVertex( ia, ib, ic );
+			this.addColor( ia, ib, ic );
+
+			// normals
+
+			if ( na !== undefined && na !== '' ) {
+
+				const nLen = this.normals.length;
+
+				ia = this.parseNormalIndex( na, nLen );
+				ib = this.parseNormalIndex( nb, nLen );
+				ic = this.parseNormalIndex( nc, nLen );
+
+				this.addNormal( ia, ib, ic );
+
+			} else {
+
+				this.addFaceNormal( ia, ib, ic );
+
+			}
+
+			// uvs
+
+			if ( ua !== undefined && ua !== '' ) {
+
+				const uvLen = this.uvs.length;
+
+				ia = this.parseUVIndex( ua, uvLen );
+				ib = this.parseUVIndex( ub, uvLen );
+				ic = this.parseUVIndex( uc, uvLen );
+
+				this.addUV( ia, ib, ic );
+
+				this.object.geometry.hasUVIndices = true;
+
+			} else {
+
+				// add placeholder values (for inconsistent face definitions)
+
+				this.addDefaultUV();
+
+			}
+
+		},
+
+		addPointGeometry: function ( vertices ) {
+
+			this.object.geometry.type = 'Points';
+
+			const vLen = this.vertices.length;
+
+			for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+				const index = this.parseVertexIndex( vertices[ vi ], vLen );
+
+				this.addVertexPoint( index );
+				this.addColor( index );
+
+			}
+
+		},
+
+		addLineGeometry: function ( vertices, uvs ) {
+
+			this.object.geometry.type = 'Line';
+
+			const vLen = this.vertices.length;
+			const uvLen = this.uvs.length;
+
+			for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+				this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+			}
+
+			for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+				this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+			}
+
+		}
+
+	};
+
+	state.startObject( '', false );
+
+	return state;
+
+}
+
+//
+
+class OBJLoader extends Loader {
+
+	constructor( manager ) {
+
+		super( manager );
+
+		this.materials = null;
+
+	}
+
+	load( url, onLoad, onProgress, onError ) {
+
+		const scope = this;
+
+		const loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.setRequestHeader( this.requestHeader );
+		loader.setWithCredentials( this.withCredentials );
+		loader.load( url, function ( text ) {
+
+			try {
+
+				onLoad( scope.parse( text ) );
+
+			} catch ( e ) {
+
+				if ( onError ) {
+
+					onError( e );
+
+				} else {
+
+					console.error( e );
+
+				}
+
+				scope.manager.itemError( url );
+
+			}
+
+		}, onProgress, onError );
+
+	}
+
+	setMaterials( materials ) {
+
+		this.materials = materials;
+
+		return this;
+
+	}
+
+	parse( text ) {
+
+		const state = new ParserState();
+
+		if ( text.indexOf( '\r\n' ) !== - 1 ) {
+
+			// This is faster than String.split with regex that splits on both
+			text = text.replace( /\r\n/g, '\n' );
+
+		}
+
+		if ( text.indexOf( '\\\n' ) !== - 1 ) {
+
+			// join lines separated by a line continuation character (\)
+			text = text.replace( /\\\n/g, '' );
+
+		}
+
+		const lines = text.split( '\n' );
+		let result = [];
+
+		for ( let i = 0, l = lines.length; i < l; i ++ ) {
+
+			const line = lines[ i ].trimStart();
+
+			if ( line.length === 0 ) continue;
+
+			const lineFirstChar = line.charAt( 0 );
+
+			// @todo invoke passed in handler if any
+			if ( lineFirstChar === '#' ) continue;
+
+			if ( lineFirstChar === 'v' ) {
+
+				const data = line.split( _face_vertex_data_separator_pattern );
+
+				switch ( data[ 0 ] ) {
+
+					case 'v':
+						state.vertices.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] ),
+							parseFloat( data[ 3 ] )
+						);
+						if ( data.length >= 7 ) {
+
+							_color.setRGB(
+								parseFloat( data[ 4 ] ),
+								parseFloat( data[ 5 ] ),
+								parseFloat( data[ 6 ] )
+							).convertSRGBToLinear();
+
+							state.colors.push( _color.r, _color.g, _color.b );
+
+						} else {
+
+							// if no colors are defined, add placeholders so color and vertex indices match
+
+							state.colors.push( undefined, undefined, undefined );
+
+						}
+
+						break;
+					case 'vn':
+						state.normals.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] ),
+							parseFloat( data[ 3 ] )
+						);
+						break;
+					case 'vt':
+						state.uvs.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] )
+						);
+						break;
+
+				}
+
+			} else if ( lineFirstChar === 'f' ) {
+
+				const lineData = line.slice( 1 ).trim();
+				const vertexData = lineData.split( _face_vertex_data_separator_pattern );
+				const faceVertices = [];
+
+				// Parse the face vertex data into an easy to work with format
+
+				for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+					const vertex = vertexData[ j ];
+
+					if ( vertex.length > 0 ) {
+
+						const vertexParts = vertex.split( '/' );
+						faceVertices.push( vertexParts );
+
+					}
+
+				}
+
+				// Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+				const v1 = faceVertices[ 0 ];
+
+				for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+					const v2 = faceVertices[ j ];
+					const v3 = faceVertices[ j + 1 ];
+
+					state.addFace(
+						v1[ 0 ], v2[ 0 ], v3[ 0 ],
+						v1[ 1 ], v2[ 1 ], v3[ 1 ],
+						v1[ 2 ], v2[ 2 ], v3[ 2 ]
+					);
+
+				}
+
+			} else if ( lineFirstChar === 'l' ) {
+
+				const lineParts = line.substring( 1 ).trim().split( ' ' );
+				let lineVertices = [];
+				const lineUVs = [];
+
+				if ( line.indexOf( '/' ) === - 1 ) {
+
+					lineVertices = lineParts;
+
+				} else {
+
+					for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+						const parts = lineParts[ li ].split( '/' );
+
+						if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] );
+						if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] );
+
+					}
+
+				}
+
+				state.addLineGeometry( lineVertices, lineUVs );
+
+			} else if ( lineFirstChar === 'p' ) {
+
+				const lineData = line.slice( 1 ).trim();
+				const pointData = lineData.split( ' ' );
+
+				state.addPointGeometry( pointData );
+
+			} else if ( ( result = _object_pattern.exec( line ) ) !== null ) {
+
+				// o object_name
+				// or
+				// g group_name
+
+				// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
+				// let name = result[ 0 ].slice( 1 ).trim();
+				const name = ( ' ' + result[ 0 ].slice( 1 ).trim() ).slice( 1 );
+
+				state.startObject( name );
+
+			} else if ( _material_use_pattern.test( line ) ) {
+
+				// material
+
+				state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
+
+			} else if ( _material_library_pattern.test( line ) ) {
+
+				// mtl file
+
+				state.materialLibraries.push( line.substring( 7 ).trim() );
+
+			} else if ( _map_use_pattern.test( line ) ) {
+
+				// the line is parsed but ignored since the loader assumes textures are defined MTL files
+				// (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
+
+				console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' );
+
+			} else if ( lineFirstChar === 's' ) {
+
+				result = line.split( ' ' );
+
+				// smooth shading
+
+				// @todo Handle files that have varying smooth values for a set of faces inside one geometry,
+				// but does not define a usemtl for each face set.
+				// This should be detected and a dummy material created (later MultiMaterial and geometry groups).
+				// This requires some care to not create extra material on each smooth value for "normal" obj files.
+				// where explicit usemtl defines geometry groups.
+				// Example asset: examples/models/obj/cerberus/Cerberus.obj
+
+				/*
+					 * http://paulbourke.net/dataformats/obj/
+					 *
+					 * From chapter "Grouping" Syntax explanation "s group_number":
+					 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
+					 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
+					 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
+					 * than 0."
+					 */
+				if ( result.length > 1 ) {
+
+					const value = result[ 1 ].trim().toLowerCase();
+					state.object.smooth = ( value !== '0' && value !== 'off' );
+
+				} else {
+
+					// ZBrush can produce "s" lines #11707
+					state.object.smooth = true;
+
+				}
+
+				const material = state.object.currentMaterial();
+				if ( material ) material.smooth = state.object.smooth;
+
+			} else {
+
+				// Handle null terminated files without exception
+				if ( line === '\0' ) continue;
+
+				console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+			}
+
+		}
+
+		state.finalize();
+
+		const container = new Group();
+		container.materialLibraries = [].concat( state.materialLibraries );
+
+		const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 );
+
+		if ( hasPrimitives === true ) {
+
+			for ( let i = 0, l = state.objects.length; i < l; i ++ ) {
+
+				const object = state.objects[ i ];
+				const geometry = object.geometry;
+				const materials = object.materials;
+				const isLine = ( geometry.type === 'Line' );
+				const isPoints = ( geometry.type === 'Points' );
+				let hasVertexColors = false;
+
+				// Skip o/g line declarations that did not follow with any faces
+				if ( geometry.vertices.length === 0 ) continue;
+
+				const buffergeometry = new BufferGeometry();
+
+				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) );
+
+				if ( geometry.normals.length > 0 ) {
+
+					buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) );
+
+				}
+
+				if ( geometry.colors.length > 0 ) {
+
+					hasVertexColors = true;
+					buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) );
+
+				}
+
+				if ( geometry.hasUVIndices === true ) {
+
+					buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) );
+
+				}
+
+				// Create materials
+
+				const createdMaterials = [];
+
+				for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+					const sourceMaterial = materials[ mi ];
+					const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
+					let material = state.materials[ materialHash ];
+
+					if ( this.materials !== null ) {
+
+						material = this.materials.create( sourceMaterial.name );
+
+						// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
+						if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) {
+
+							const materialLine = new LineBasicMaterial();
+							Material.prototype.copy.call( materialLine, material );
+							materialLine.color.copy( material.color );
+							material = materialLine;
+
+						} else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) {
+
+							const materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } );
+							Material.prototype.copy.call( materialPoints, material );
+							materialPoints.color.copy( material.color );
+							materialPoints.map = material.map;
+							material = materialPoints;
+
+						}
+
+					}
+
+					if ( material === undefined ) {
+
+						if ( isLine ) {
+
+							material = new LineBasicMaterial();
+
+						} else if ( isPoints ) {
+
+							material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+						} else {
+
+							material = new MeshPhongMaterial();
+
+						}
+
+						material.name = sourceMaterial.name;
+						material.flatShading = sourceMaterial.smooth ? false : true;
+						material.vertexColors = hasVertexColors;
+
+						state.materials[ materialHash ] = material;
+
+					}
+
+					createdMaterials.push( material );
+
+				}
+
+				// Create mesh
+
+				let mesh;
+
+				if ( createdMaterials.length > 1 ) {
+
+					for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+						const sourceMaterial = materials[ mi ];
+						buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
+
+					}
+
+					if ( isLine ) {
+
+						mesh = new LineSegments( buffergeometry, createdMaterials );
+
+					} else if ( isPoints ) {
+
+						mesh = new Points( buffergeometry, createdMaterials );
+
+					} else {
+
+						mesh = new Mesh( buffergeometry, createdMaterials );
+
+					}
+
+				} else {
+
+					if ( isLine ) {
+
+						mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] );
+
+					} else if ( isPoints ) {
+
+						mesh = new Points( buffergeometry, createdMaterials[ 0 ] );
+
+					} else {
+
+						mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] );
+
+					}
+
+				}
+
+				mesh.name = object.name;
+
+				container.add( mesh );
+
+			}
+
+		} else {
+
+			// if there is only the default parser state object with no geometry data, interpret data as point cloud
+
+			if ( state.vertices.length > 0 ) {
+
+				const material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+				const buffergeometry = new BufferGeometry();
+
+				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) );
+
+				if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) {
+
+					buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) );
+					material.vertexColors = true;
+
+				}
+
+				const points = new Points( buffergeometry, material );
+				container.add( points );
+
+			}
+
+		}
+
+		return container;
+
+	}
+
+}
+
+export { OBJLoader };

File diff suppressed because it is too large
+ 13013 - 0
platforms/h5/common/three.js


BIN
static/images/baobei-img.png


BIN
static/images/baobei.png


BIN
static/images/baobei1.png


BIN
static/images/code.png


BIN
static/images/dataCenter1-1.png


BIN
static/images/dijia-bg.png


BIN
static/images/head.png


BIN
static/images/hot.png


BIN
static/images/index.png


BIN
static/images/index1.png


BIN
static/images/jindu.png


BIN
static/images/jindu1.png


BIN
static/images/mine-tabs1.png


BIN
static/images/mine-tabs2.png


BIN
static/images/mine-tabs3.png


BIN
static/images/mine.png


BIN
static/images/mine1.png


BIN
static/images/pk.png


BIN
static/images/popup1-1.png


BIN
static/images/qianbao.png


BIN
static/images/shaixuan.png


BIN
static/images/tabs1.png


BIN
static/images/tabs2.png


BIN
static/images/tabs3.png


BIN
static/images/tabs4.png


BIN
static/images/video-play.png


BIN
static/images/video.png


BIN
static/images/video1.png


BIN
static/images/xiangji.png


BIN
static/images/xiegang.png


+ 34 - 0
store/index.js

@@ -0,0 +1,34 @@
+// 页面路径:store/index.js 
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex); //vue的插件机制
+
+//Vuex.Store 构造器选项
+const store = new Vuex.Store({
+	state: { //存放状态
+		config: {},
+		city: '',
+		popularity: 0,
+		citydata:{}
+	},
+	getters: {
+		config: state => state.config
+	},
+	mutations: {
+		setconfig(state, data) {
+			// 变更状态
+			state.config = data
+		},
+		setcity(state, data) {
+			// 变更状态
+			state.city = data.district
+			state.citydata = data
+		},
+		setpopularity(state, data) {
+			// 变更状态
+			state.popularity = data
+		}
+	}
+})
+export default store

+ 10 - 0
uni.promisify.adaptor.js

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

+ 76 - 0
uni.scss

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

+ 21 - 0
uview-ui/LICENSE

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

+ 106 - 0
uview-ui/README.md

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large
+ 24 - 0
uview-ui/components/u-avatar/u-avatar.vue


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

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

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

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

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