Browse Source

first commit

jinx 4 years ago
commit
5c9d7c7ef2
100 changed files with 8909 additions and 0 deletions
  1. 6 0
      .gitignore
  2. 37 0
      apis/agent.js
  3. 16 0
      apis/city.js
  4. 79 0
      apis/fetcher/request.js
  5. 74 0
      apis/house.js
  6. 25 0
      apis/im.js
  7. 52 0
      apis/user.js
  8. 54 0
      app.js
  9. 0 0
      apps/4Dkankan/README.md
  10. 37 0
      apps/4Dkankan/apis/agent.js
  11. 16 0
      apps/4Dkankan/apis/city.js
  12. 79 0
      apps/4Dkankan/apis/fetcher/request.js
  13. 74 0
      apps/4Dkankan/apis/house.js
  14. 25 0
      apps/4Dkankan/apis/im.js
  15. 0 0
      apps/4Dkankan/apis/index.js
  16. 52 0
      apps/4Dkankan/apis/user.js
  17. 174 0
      apps/4Dkankan/app.js
  18. 64 0
      apps/4Dkankan/app.json
  19. 323 0
      apps/4Dkankan/app.wxss
  20. 1227 0
      apps/4Dkankan/colorui/icon.wxss
  21. 3053 0
      apps/4Dkankan/colorui/main.wxss
  22. 355 0
      apps/4Dkankan/components/chat-input/chat-input.js
  23. 3 0
      apps/4Dkankan/components/chat-input/chat-input.json
  24. 28 0
      apps/4Dkankan/components/chat-input/chat-input.wxml
  25. 58 0
      apps/4Dkankan/components/chat-input/chat-input.wxss
  26. 9 0
      apps/4Dkankan/components/chat-input/extra.wxml
  27. 33 0
      apps/4Dkankan/components/chat-input/extra.wxss
  28. 15 0
      apps/4Dkankan/components/chat-input/voice.wxml
  29. 40 0
      apps/4Dkankan/components/chat-input/voice.wxss
  30. 57 0
      apps/4Dkankan/components/contact-user/cantact-user.js
  31. 4 0
      apps/4Dkankan/components/contact-user/cantact-user.json
  32. 12 0
      apps/4Dkankan/components/contact-user/cantact-user.wxml
  33. 57 0
      apps/4Dkankan/components/contact-user/cantact-user.wxss
  34. 30 0
      apps/4Dkankan/components/content-card/index.js
  35. 4 0
      apps/4Dkankan/components/content-card/index.json
  36. 6 0
      apps/4Dkankan/components/content-card/index.wxml
  37. 43 0
      apps/4Dkankan/components/content-card/index.wxss
  38. 37 0
      apps/4Dkankan/components/content-title/index.js
  39. 4 0
      apps/4Dkankan/components/content-title/index.json
  40. 9 0
      apps/4Dkankan/components/content-title/index.wxml
  41. 64 0
      apps/4Dkankan/components/content-title/index.wxss
  42. 38 0
      apps/4Dkankan/components/detail-components/detail-banner/detail-banner.js
  43. 4 0
      apps/4Dkankan/components/detail-components/detail-banner/detail-banner.json
  44. 18 0
      apps/4Dkankan/components/detail-components/detail-banner/detail-banner.wxml
  45. 70 0
      apps/4Dkankan/components/detail-components/detail-banner/detail-banner.wxss
  46. 34 0
      apps/4Dkankan/components/detail-components/detail-intro/detail-intro.js
  47. 4 0
      apps/4Dkankan/components/detail-components/detail-intro/detail-intro.json
  48. 7 0
      apps/4Dkankan/components/detail-components/detail-intro/detail-intro.wxml
  49. 22 0
      apps/4Dkankan/components/detail-components/detail-intro/detail-intro.wxss
  50. 322 0
      apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.js
  51. 7 0
      apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.json
  52. 97 0
      apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.wxml
  53. 210 0
      apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.wxss
  54. 103 0
      apps/4Dkankan/components/header-nav/header-nav.js
  55. 4 0
      apps/4Dkankan/components/header-nav/header-nav.json
  56. 20 0
      apps/4Dkankan/components/header-nav/header-nav.wxml
  57. 67 0
      apps/4Dkankan/components/header-nav/header-nav.wxss
  58. 30 0
      apps/4Dkankan/components/house-item/house-item.js
  59. 4 0
      apps/4Dkankan/components/house-item/house-item.json
  60. 24 0
      apps/4Dkankan/components/house-item/house-item.wxml
  61. 88 0
      apps/4Dkankan/components/house-item/house-item.wxss
  62. 43 0
      apps/4Dkankan/components/login-pannel/login-pannel.js
  63. 4 0
      apps/4Dkankan/components/login-pannel/login-pannel.json
  64. 10 0
      apps/4Dkankan/components/login-pannel/login-pannel.wxml
  65. 58 0
      apps/4Dkankan/components/login-pannel/login-pannel.wxss
  66. 23 0
      apps/4Dkankan/components/modal/modal.js
  67. 4 0
      apps/4Dkankan/components/modal/modal.json
  68. 6 0
      apps/4Dkankan/components/modal/modal.wxml
  69. 24 0
      apps/4Dkankan/components/modal/modal.wxss
  70. 23 0
      apps/4Dkankan/components/no-more-bar/no-more-bar.js
  71. 4 0
      apps/4Dkankan/components/no-more-bar/no-more-bar.json
  72. 3 0
      apps/4Dkankan/components/no-more-bar/no-more-bar.wxml
  73. 9 0
      apps/4Dkankan/components/no-more-bar/no-more-bar.wxss
  74. 93 0
      apps/4Dkankan/components/searchbar/searchbar.js
  75. 4 0
      apps/4Dkankan/components/searchbar/searchbar.json
  76. 6 0
      apps/4Dkankan/components/searchbar/searchbar.wxml
  77. 7 0
      apps/4Dkankan/components/searchbar/searchbar.wxss
  78. 141 0
      apps/4Dkankan/components/select-form/select-form.js
  79. 4 0
      apps/4Dkankan/components/select-form/select-form.json
  80. 63 0
      apps/4Dkankan/components/select-form/select-form.wxml
  81. 52 0
      apps/4Dkankan/components/select-form/select-form.wxss
  82. 64 0
      apps/4Dkankan/components/swiper/swiper.js
  83. 4 0
      apps/4Dkankan/components/swiper/swiper.json
  84. 10 0
      apps/4Dkankan/components/swiper/swiper.wxml
  85. 16 0
      apps/4Dkankan/components/swiper/swiper.wxss
  86. 107 0
      apps/4Dkankan/components/tab-bar/tab-bar.js
  87. 8 0
      apps/4Dkankan/components/tab-bar/tab-bar.json
  88. 18 0
      apps/4Dkankan/components/tab-bar/tab-bar.wxml
  89. 9 0
      apps/4Dkankan/components/tab-bar/tab-bar.wxss
  90. 29 0
      apps/4Dkankan/components/tabs-house-list/tabs-house-list.js
  91. 9 0
      apps/4Dkankan/components/tabs-house-list/tabs-house-list.json
  92. 11 0
      apps/4Dkankan/components/tabs-house-list/tabs-house-list.wxml
  93. 18 0
      apps/4Dkankan/components/tabs-house-list/tabs-house-list.wxss
  94. 85 0
      apps/4Dkankan/components/trtc-room/common/constants.js
  95. 346 0
      apps/4Dkankan/components/trtc-room/controller/user-controller.js
  96. 14 0
      apps/4Dkankan/components/trtc-room/libs/mta_analysis.js
  97. 1 0
      apps/4Dkankan/components/trtc-room/libs/tim-wx.js
  98. 33 0
      apps/4Dkankan/components/trtc-room/model/pusher.js
  99. 38 0
      apps/4Dkankan/components/trtc-room/model/stream.js
  100. 0 0
      apps/4Dkankan/components/trtc-room/model/user.js

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+package-lock.json
+./node_modules
+.idea
+project.config.json
+.tmp
+node_modules

+ 37 - 0
apis/agent.js

@@ -0,0 +1,37 @@
+import request from './fetcher/request'
+// 经纪人相关的API
+export default {
+  
+  fetchAgentDetail (agent_user_id) {
+    return request.get('agency/detail', {
+      agency_user_id: agent_user_id
+    })
+  },
+  // 客户详情
+  fetchCustomerDetail ({ agency_user_id, user_id }) {
+    return request.get('agency/customer/detail', { agency_user_id, user_id })
+  },
+  // 获取客源列表
+  fetchCustomers (data) {
+    return request.get('agency/customer/list', data)
+  },
+
+   // 根据手机号获取客源
+   fetchUserByPhone (data) {
+    return request.get('user/getByPhone', data)
+  },
+
+  // 增加客源
+  addCustomer (data) {
+    return request.post('agency/add/customer', data)
+  },
+  // 搜索客源列表
+  searchCustomers (data) {
+    return request.get('agency/customer/query', data)
+  },
+
+  // 修改客源详情
+  updateCustomer (data) {
+    return request.post('agency/update/customer', data)
+  }
+}

+ 16 - 0
apis/city.js

@@ -0,0 +1,16 @@
+import request from './fetcher/request'
+
+export default {
+  getCityList () {
+    return request.get('region/allCity')
+  },
+  getCityListByProv (province) {
+    return request.get('region/cityList',{province})
+  },
+  getProvinceList () {
+    return request.get('region/provinceList')
+  },
+  getSubList (parent_id) {
+    return request.get('region/list',{parent_id})
+  },
+}

+ 79 - 0
apis/fetcher/request.js

@@ -0,0 +1,79 @@
+import ERROR_CODE from '../../config/error_code'
+import { API_BASE_URL } from './../../config/config'
+import { loginByUserInfo } from '../../utils/login'
+const BASE_URL = `${API_BASE_URL}/app/`
+// 需要登录的错误码
+const needLoginErrorCode = [3004, 3005, 3006, 3013]
+
+Promise.prototype.finally = function (callback) {
+  var Promise = this.constructor;
+  return this.then(
+      function (value) {
+          Promise.resolve(callback()).then(
+              function () {
+                  return value;
+              }
+          );
+      },
+      function (reason) {
+          Promise.resolve(callback()).then(
+              function () {
+                  throw reason;
+              }
+          );
+      }
+  );
+}
+
+function request (url, options) {
+  return new Promise((resolve, reject) => {
+    wx.request(Object.assign({
+      url: url.indexOf('://') === -1 ? BASE_URL + url : url,
+      method: options.method,
+      data: options.data,
+      // header: {
+      //   token: app.globalData.token
+      // },
+      success (res) {
+        console.log(res, url.indexOf('://') === -1 ? BASE_URL + url : url, '接口返回')
+        if (res.data.code == 0 || res.data.code == 200) {
+          resolve(res.data)
+        } else if (needLoginErrorCode.indexOf(Number(res.data.code)) !== -1) {
+          wx.navigateTo({
+            url: `/pages/login/login`
+          })
+        } else {
+          
+          reject(res)
+        }
+      },
+      fail (err) {
+        reject(err)
+      }
+    }, options))
+    setTimeout(() => reject('time out'), 5000)
+  })
+}
+
+function get (url, data, options = {}) {
+  options.method = 'GET'
+  options.data = data
+  return request(url, options)
+}
+
+function post (url, data = {}, options = {}) {
+  options.method = 'POST'
+  // token和user_ID放在url上, 后续后端再做优化
+  const app = getApp();
+  url += `?token=${app.globalData.token || ''}&user_id=${app.globalData.userinfo.user_id}`
+  options.data = Object.assign({
+    user_id: app.globalData.userinfo.user_id
+  }, data)
+  return request(url, options)
+}
+
+export default {
+  request,
+  get,
+  post
+}

+ 74 - 0
apis/house.js

@@ -0,0 +1,74 @@
+import request from './fetcher/request'
+
+// 房屋相关
+export default {
+  // 通过小区id获取房源
+  houseListByEstateId (data) {
+    return request.get('house/estate/houses', data)
+  },
+  // 获取房源列表
+  houseList (data) {
+    return request.get('house/list', data)
+  },
+  // house/agencyHouse
+  fetchAgencyHouseList (data) {
+    return request.get('house/agencyHouse', data)
+  },
+  // 房源详情
+  houseDetail (house_id) {
+    return request.get('house/detail', {
+      house_id
+    })
+  },
+  // 是否关注
+  isHouseFocused ({ house_id, user_id}) {
+    return request.get('user/focus', {
+      house_id,
+      user_id
+    })
+  },
+  // 关注房源
+  focusHouse ({house_id, is_valid}) {
+    return request.post('user/focus/house/add', { house_id, is_valid })
+  },
+  // 取消关注
+  cancleFocusHouse ({house_id}) {
+    return request.get('user/focus/house/delete', { house_id, user_id: getApp().globalData.userinfo.user_id, token: getApp().globalData.token})
+  },
+  // 获取房源的经纪人列表
+  getAgentByHouseId (house_id) {
+    return request.get('agency/house/list', {
+      house_id
+    })
+  },
+  // 添加浏览记录
+  addReadHistory ({ house_id }) {
+    return request.post('user/read/house/add', { house_id })
+  },
+  // 增加分享记录
+  addSharedHistory ({house_id, sale_type}) {
+    return request.post('user/house/share/add', {house_id, sale_type})
+  },
+  // 根据场景获取经纪人
+  getAgencyByScene (scene) {
+    return request.get('house/getAgencyId', {scene_code: scene})
+  },
+  
+  getRecommendHouse ({ page_num=1, page_size=10}) {
+    return request.get('house/recommend', { page_num, page_size })
+  },
+
+  getHouseByScene (scene) {
+    return request.get('house/getByScene', {scene_code: scene})
+  },
+
+  /* 向openApi发送数据
+  ** @params user_id
+  ** @params house_id
+  ** @params room_id
+  */
+ postDataToOpen (data) {
+   data.user_id = getApp().globalData.userinfo.user_id || ''
+   return request.get('house/sendData', data)
+ } 
+}

+ 25 - 0
apis/im.js

@@ -0,0 +1,25 @@
+import request from "./fetcher/request"
+import { API_BASE_URL } from './../config/config'
+
+
+export default {
+  register ({name}) {
+    return request.post(`${API_BASE_URL}/im/register`, {name})
+  },
+  getContacts () {
+    const app = getApp()
+    return request.get(`${API_BASE_URL}/im/getContacts/${app.globalData.userinfo.user_id}`, {user_id: app.globalData.userinfo.user_id})
+  },
+  upload () {
+    const formData = new FormData()
+    formData.append("file", $('#file')[0].files[0])
+    return request.post(`${API_BASE_URL}/im/upload`, formData)
+  },
+  addFriend (friend_id) {
+    return request.get(`${API_BASE_URL}/im/addFriend/${friend_id}/${getApp().globalData.userinfo.user_id}`)
+  },
+
+  getMsgHistory (friend_id, offset=0) {
+    return request.get(`${API_BASE_URL}/im/getMsgHistory/${friend_id}/${getApp().globalData.userinfo.user_id}/${offset}`)
+  }
+}

+ 52 - 0
apis/user.js

@@ -0,0 +1,52 @@
+import request from './fetcher/request'
+
+export default {
+  // 用户详情
+  fetchUserInfo (user_id) {
+    return request.get('user/detail', { user_id })
+  },
+  // 登录
+  /*
+  ** wx_code String
+  ** phone_num String
+  ** iv String
+  */
+  loginByPhoneCode (data) {
+    let str = '?'
+    Object.keys(data).forEach(item => {
+      str += `${item}=${data[item]}&`
+    })
+    
+    return request.post(`user/login${str}`, data)
+  },
+  loginByUserInfo (data) {
+    return request.post(`user/login`, data)
+  },
+  loginByPhoneCodeAgency (data) {
+    let str = '?'
+    Object.keys(data).forEach(item => {
+      str += `${item}=${data[item]}&`
+    })
+    
+    return request.post(`agency/login${str}`, data)
+  },
+  // 分享历史
+  fetchSharedHistory (data) {
+    return request.get('user/house/share/list', data)
+  },
+  // 浏览历史
+  fetchReadHistory (data) {
+    return request.post('user/house/read/history', data)
+  },
+  // 关注列表
+  fetchFocusHouses (data) {
+    data = Object.assign({
+      page_num: 1,
+      page_size: 50
+    }, data)
+    return request.get('user/focus/house', data)
+  },
+  delegation (data) {
+    return request.post('user/delegation', data)
+  }
+}

+ 54 - 0
app.js

@@ -0,0 +1,54 @@
+let fs = require('fs')
+let path = require('path')
+
+
+// 获取命令行参数
+let parm = process.argv.splice(2)
+// 第一个参数是路径
+let rootPath = parm[0]
+// 后面的所有参数都是文件后缀
+let types = parm.splice(1)
+// 需要过滤的文件夹
+let filter = ['./node_modules']
+// 统计结果
+let num = 0
+
+
+// 获取行数
+async function line(path) {
+    let rep = await fs.readFileSync(path)
+    rep = rep.toString()
+    let lines = rep.split('\n')
+    console.log(path + ' ' + lines.length)
+    num += lines.length
+}
+
+
+// 递归所有文件夹统计
+async function start(pt) {
+    let files = fs.readdirSync(pt)
+    files
+        .map(file => {
+            return `${pt}/${file}`
+        })
+        .forEach(file => {
+            let stat = fs.statSync(file)
+            if (stat.isDirectory()) {
+                if (filter.indexOf(pt) != -1) {
+                    return
+                }
+                start(file)
+                return
+            }
+            let ext = path.extname(file)
+            if (types.indexOf(ext) != -1) {
+                line(file)
+            }
+        })
+}
+
+
+;(async () => {
+    await start(rootPath)
+    console.log(`总代码行数:${num}`)
+})()

+ 0 - 0
apps/4Dkankan/README.md


+ 37 - 0
apps/4Dkankan/apis/agent.js

@@ -0,0 +1,37 @@
+import request from './fetcher/request'
+// 经纪人相关的API
+export default {
+  
+  fetchAgentDetail (agent_user_id) {
+    return request.get('agency/detail', {
+      agency_user_id: agent_user_id
+    })
+  },
+  // 客户详情
+  fetchCustomerDetail ({ agency_user_id, user_id }) {
+    return request.get('agency/customer/detail', { agency_user_id, user_id })
+  },
+  // 获取客源列表
+  fetchCustomers (data) {
+    return request.get('agency/customer/list', data)
+  },
+
+   // 根据手机号获取客源
+   fetchUserByPhone (data) {
+    return request.get('user/getByPhone', data)
+  },
+
+  // 增加客源
+  addCustomer (data) {
+    return request.post('agency/add/customer', data)
+  },
+  // 搜索客源列表
+  searchCustomers (data) {
+    return request.get('agency/customer/query', data)
+  },
+
+  // 修改客源详情
+  updateCustomer (data) {
+    return request.post('agency/update/customer', data)
+  }
+}

+ 16 - 0
apps/4Dkankan/apis/city.js

@@ -0,0 +1,16 @@
+import request from './fetcher/request'
+
+export default {
+  getCityList () {
+    return request.get('region/allCity')
+  },
+  getCityListByProv (province) {
+    return request.get('region/cityList',{province})
+  },
+  getProvinceList () {
+    return request.get('region/provinceList')
+  },
+  getSubList (parent_id) {
+    return request.get('region/list',{parent_id})
+  },
+}

+ 79 - 0
apps/4Dkankan/apis/fetcher/request.js

@@ -0,0 +1,79 @@
+import ERROR_CODE from '../../config/error_code'
+import { API_BASE_URL } from './../../config/config'
+import { loginByUserInfo } from '../../utils/login'
+const BASE_URL = `${API_BASE_URL}/app/`
+// 需要登录的错误码
+const needLoginErrorCode = [3004, 3005, 3006, 3013]
+
+Promise.prototype.finally = function (callback) {
+  var Promise = this.constructor;
+  return this.then(
+      function (value) {
+          Promise.resolve(callback()).then(
+              function () {
+                  return value;
+              }
+          );
+      },
+      function (reason) {
+          Promise.resolve(callback()).then(
+              function () {
+                  throw reason;
+              }
+          );
+      }
+  );
+}
+
+function request (url, options) {
+  return new Promise((resolve, reject) => {
+    wx.request(Object.assign({
+      url: url.indexOf('://') === -1 ? BASE_URL + url : url,
+      method: options.method,
+      data: options.data,
+      // header: {
+      //   token: app.globalData.token
+      // },
+      success (res) {
+        console.log(res, url.indexOf('://') === -1 ? BASE_URL + url : url, '接口返回')
+        if (res.data.code == 0 || res.data.code == 200) {
+          resolve(res.data)
+        } else if (needLoginErrorCode.indexOf(Number(res.data.code)) !== -1) {
+          wx.navigateTo({
+            url: `/pages/login/login`
+          })
+        } else {
+          
+          reject(res)
+        }
+      },
+      fail (err) {
+        reject(err)
+      }
+    }, options))
+    setTimeout(() => reject('time out'), 5000)
+  })
+}
+
+function get (url, data, options = {}) {
+  options.method = 'GET'
+  options.data = data
+  return request(url, options)
+}
+
+function post (url, data = {}, options = {}) {
+  options.method = 'POST'
+  // token和user_ID放在url上, 后续后端再做优化
+  const app = getApp();
+  url += `?token=${app.globalData.token || ''}&user_id=${app.globalData.userinfo.user_id}`
+  options.data = Object.assign({
+    user_id: app.globalData.userinfo.user_id
+  }, data)
+  return request(url, options)
+}
+
+export default {
+  request,
+  get,
+  post
+}

+ 74 - 0
apps/4Dkankan/apis/house.js

@@ -0,0 +1,74 @@
+import request from './fetcher/request'
+
+// 房屋相关
+export default {
+  // 通过小区id获取房源
+  houseListByEstateId (data) {
+    return request.get('house/estate/houses', data)
+  },
+  // 获取房源列表
+  houseList (data) {
+    return request.get('house/list', data)
+  },
+  // house/agencyHouse
+  fetchAgencyHouseList (data) {
+    return request.get('house/agencyHouse', data)
+  },
+  // 房源详情
+  houseDetail (house_id) {
+    return request.get('house/detail', {
+      house_id
+    })
+  },
+  // 是否关注
+  isHouseFocused ({ house_id, user_id}) {
+    return request.get('user/focus', {
+      house_id,
+      user_id
+    })
+  },
+  // 关注房源
+  focusHouse ({house_id, is_valid}) {
+    return request.post('user/focus/house/add', { house_id, is_valid })
+  },
+  // 取消关注
+  cancleFocusHouse ({house_id}) {
+    return request.get('user/focus/house/delete', { house_id, user_id: getApp().globalData.userinfo.user_id, token: getApp().globalData.token})
+  },
+  // 获取房源的经纪人列表
+  getAgentByHouseId (house_id) {
+    return request.get('agency/house/list', {
+      house_id
+    })
+  },
+  // 添加浏览记录
+  addReadHistory ({ house_id }) {
+    return request.post('user/read/house/add', { house_id })
+  },
+  // 增加分享记录
+  addSharedHistory ({house_id, sale_type}) {
+    return request.post('user/house/share/add', {house_id, sale_type})
+  },
+  // 根据场景获取经纪人
+  getAgencyByScene (scene) {
+    return request.get('house/getAgencyId', {scene_code: scene})
+  },
+  
+  getRecommendHouse ({ page_num=1, page_size=10}) {
+    return request.get('house/recommend', { page_num, page_size })
+  },
+
+  getHouseByScene (scene) {
+    return request.get('house/getByScene', {scene_code: scene})
+  },
+
+  /* 向openApi发送数据
+  ** @params user_id
+  ** @params house_id
+  ** @params room_id
+  */
+ postDataToOpen (data) {
+   data.user_id = getApp().globalData.userinfo.user_id || ''
+   return request.get('house/sendData', data)
+ } 
+}

+ 25 - 0
apps/4Dkankan/apis/im.js

@@ -0,0 +1,25 @@
+import request from "./fetcher/request"
+import { API_BASE_URL } from './../config/config'
+
+
+export default {
+  register ({name}) {
+    return request.post(`${API_BASE_URL}/im/register`, {name})
+  },
+  getContacts () {
+    const app = getApp()
+    return request.get(`${API_BASE_URL}/im/getContacts/${app.globalData.userinfo.user_id}`, {user_id: app.globalData.userinfo.user_id})
+  },
+  upload () {
+    const formData = new FormData()
+    formData.append("file", $('#file')[0].files[0])
+    return request.post(`${API_BASE_URL}/im/upload`, formData)
+  },
+  addFriend (friend_id) {
+    return request.get(`${API_BASE_URL}/im/addFriend/${friend_id}/${getApp().globalData.userinfo.user_id}`)
+  },
+
+  getMsgHistory (friend_id, offset=0) {
+    return request.get(`${API_BASE_URL}/im/getMsgHistory/${friend_id}/${getApp().globalData.userinfo.user_id}/${offset}`)
+  }
+}

+ 0 - 0
apps/4Dkankan/apis/index.js


+ 52 - 0
apps/4Dkankan/apis/user.js

@@ -0,0 +1,52 @@
+import request from './fetcher/request'
+
+export default {
+  // 用户详情
+  fetchUserInfo (user_id) {
+    return request.get('user/detail', { user_id })
+  },
+  // 登录
+  /*
+  ** wx_code String
+  ** phone_num String
+  ** iv String
+  */
+  loginByPhoneCode (data) {
+    let str = '?'
+    Object.keys(data).forEach(item => {
+      str += `${item}=${data[item]}&`
+    })
+    
+    return request.post(`user/login${str}`, data)
+  },
+  loginByUserInfo (data) {
+    return request.post(`user/login`, data)
+  },
+  loginByPhoneCodeAgency (data) {
+    let str = '?'
+    Object.keys(data).forEach(item => {
+      str += `${item}=${data[item]}&`
+    })
+    
+    return request.post(`agency/login${str}`, data)
+  },
+  // 分享历史
+  fetchSharedHistory (data) {
+    return request.get('user/house/share/list', data)
+  },
+  // 浏览历史
+  fetchReadHistory (data) {
+    return request.post('user/house/read/history', data)
+  },
+  // 关注列表
+  fetchFocusHouses (data) {
+    data = Object.assign({
+      page_num: 1,
+      page_size: 50
+    }, data)
+    return request.get('user/focus/house', data)
+  },
+  delegation (data) {
+    return request.post('user/delegation', data)
+  }
+}

+ 174 - 0
apps/4Dkankan/app.js

@@ -0,0 +1,174 @@
+//app.js
+import {
+  loadToken,
+  loadUserInfo
+} from './utils/storage'
+
+import ImApi from './apis/im'
+import AppIMDelegate from "./delegate/app-im-delegate";
+import { fotmatDate } from './utils/date'
+
+var QQMapWX = require('/utils/qqmap-wx-jssdk.min.js')
+var timer = null
+App({
+  onLaunch: function (options) {
+    //检查是否存在新版本
+    // 检查更新并提示
+    if (wx.getUpdateManager) { // wx.getUpdateManager 从 1.9.90 开始支持
+      const updateManager = wx.getUpdateManager()
+      updateManager.onCheckForUpdate(function (res) {
+        // 请求完新版本信息的回调
+        console.log('updateInfo:', res)
+      })
+      updateManager.onUpdateReady(function () {
+        updateManager.applyUpdate()
+      })
+    }
+    this.appIMDelegate = new AppIMDelegate(this);
+    this.appIMDelegate.onLaunch(options);
+    this.globalData.qqmapsdk = new QQMapWX({
+      key: 'RG7BZ-G2KRV-XWPP2-U5GWL-WWQPF-RIBUW'
+    });
+    if (this.globalData.token) {
+      wx.nextTick((callback => {
+        this.getContact()
+      }))
+    }
+    wx.getSystemInfo({
+      success (res) {
+        console.log(res, '系统信息')
+      }
+    })
+    
+  },
+  /**
+   * 当小程序启动,或从后台进入前台显示,会触发 onShow
+   */
+  onShow: function (options) {
+    setTimeout(() => {
+      this.appIMDelegate.onShow()
+    }, 1000)
+  },
+  SetProvinceCity(province, city, district) {
+    this.globalData.province = province;
+    this.globalData.city = city;
+    this.globalData.district = district;
+    this.globalData.raw_city = false;
+  },
+  wxshowloading(title) {
+    wx.showLoading({
+      title: title,
+      mask: true
+    });
+  },
+  /**
+   * 当小程序从前台进入后台,会触发 onHide
+   */
+  onHide: function () {
+    this.appIMDelegate.onHide();
+  },
+  /**
+   * 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
+   */
+  onError: function (msg) {
+    console.log("onError")
+  },
+  ShowToast(title) {
+    // Toast提示封装 app.showToast('')
+    wx.showToast({
+      title: title,
+      icon: 'none',
+      duration: 2000,
+    })
+  },
+
+  ShowModel(title, content) {
+    // Model 封装 app.ShowModel('')
+    wx.showModal({
+      title: title,
+      content: content,
+      showCancel: false,
+    });
+  },
+  InterError(res) {
+    this.ShowModel('网络错误', '请稍后再试~');
+    wx.hideLoading()
+  },
+  makePhoneCall(phone) {
+    wx.makePhoneCall({
+      phoneNumber: phone
+    })
+  },
+  getIMHandler() {
+    return this.appIMDelegate.getIMHandlerDelegate();
+  },
+  async getNewMessage (msg) {
+    const { conversations } = this.globalData
+    let item = conversations.find(item => item.id === msg.fromId)
+    if (item) {
+        item.latestMsgContent = msg.content
+        item.latestMsgTime = msg.sendTime
+        item.unReadNum ? item.unReadNum++ : item.unReadNum = 1
+        this.globalData.conversations = this.dealConversations(conversations)
+    } else {
+        await this.getContact()
+    }
+},
+getContact(isNewMsg) {
+  return ImApi.getContacts().then(res => {
+    const friends = res.data.friends
+    const {
+      conversations
+    } = this.globalData
+    this.globalData.unViewMsg = 0
+    this.globalData.conversations = this.dealConversations(friends.map(item => {
+      const con = conversations.find(con => con.id === item.id)
+      if (con) {
+        item = Object.assign(con, item)
+      } else if (isNewMsg){
+        item.unReadNum = 1
+      }
+      this.globalData.unViewMsg += item.unReadNum
+      return item
+    }))
+  })
+},
+  dealConversations(conversations) {
+    return conversations.map(item => {
+      let content = item.latestMsgContent
+      try {
+        let parseContent = JSON.parse(content)
+        if (parseContent.house_name) {
+          content = `【云带看】${JSON.parse(content).house_name}`
+        } else if (parseContent.duration) {
+          content = '【语音】'
+        } else if (parseContent.content) {
+          content = '【图片】'
+        }
+
+      } catch (err) {}
+      item.latestMsgContent = content
+      const now = new Date()
+      if (now.getMonth() === new Date(item.latestMsgTime).getMonth() && now.getDate() === new Date(item.latestMsgTime).getDate()) {
+        item.timeStr = fotmatDate(item.latestMsgTime, 'hh:mm')
+      } else {
+        item.timeStr = fotmatDate(item.latestMsgTime, 'MM/dd')
+      }
+      
+      return item
+    }).sort((a, b) => new Date(b.latestMsgTime) - new Date(a.latestMsgTime))
+  },
+  globalData: {
+    token: loadToken(),
+    raw_city: true,
+    city: '珠海', // 默认进入首页的地址
+    province: '广东省',
+    district: '',
+    userinfo: loadUserInfo() || {},
+    io: '',
+    messageList: [],
+    unViewMsg: 0,
+    conversations: [],
+    type: 'user'
+  }
+});

+ 64 - 0
apps/4Dkankan/app.json

@@ -0,0 +1,64 @@
+{
+  "pages": [
+    "pages/index/index",
+    "pages/detail/detail",
+    "pages/contact/contact",
+    "pages/my/my",
+    "pages/city/city",
+    "pages/web/web",
+    "pages/chat-list/chat-list",
+    "pages/chat/chat",
+    "pages/chat-input/chat-input",
+    "pages/map/map",
+    "pages/agent-detail/agent-detail",
+    "pages/user-info/user-info",
+    "pages/about/about",
+    "pages/login/login",
+    "pages/share-history/share-history",
+    "pages/follow-list/follow-list",
+    "pages/detail-intro/detail-intro",
+    "pages/view-history/view-history",
+    "pages/shared/shared"
+  ],
+  "usingComponents": {
+    "search-bar": "components/searchbar/searchbar",
+    "swiper-banner": "components/swiper/swiper",
+    "login-pannel": "components/login-pannel/login-pannel"
+  },
+  "window": {
+    "navigationBarTitleText": "授权验证",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationBarTextStyle": "black"
+  },
+  "tabBar": {
+    "custom": true,
+    "color": "#79868F",
+    "selectedColor": "#1FE4DC",
+    "list": [
+      {
+        "selectedIconPath": "image/4Dage/tab/home-active.png",
+        "iconPath": "image/4Dage/tab/home.png",
+        "pagePath": "pages/index/index",
+        "text": "首页"
+      },
+      {
+        "selectedIconPath": "image/4Dage/tab/message-active.png",
+        "iconPath": "image/4Dage/tab/message.png",
+        "pagePath": "pages/chat-list/chat-list",
+        "text": "消息"
+      },
+      {
+        "selectedIconPath": "image/4Dage/tab/my-active.png",
+        "iconPath": "image/4Dage/tab/my.png",
+        "pagePath": "pages/my/my",
+        "text": "我的"
+      }
+    ]
+  },
+  "permission": {
+    "scope.userLocation": {
+      "desc": "为了更好的服务,请授权位置信息"
+    }
+  },
+  "sitemapLocation": "sitemap.json"
+}

File diff suppressed because it is too large
+ 323 - 0
apps/4Dkankan/app.wxss


File diff suppressed because it is too large
+ 1227 - 0
apps/4Dkankan/colorui/icon.wxss


File diff suppressed because it is too large
+ 3053 - 0
apps/4Dkankan/colorui/main.wxss


+ 355 - 0
apps/4Dkankan/components/chat-input/chat-input.js

@@ -0,0 +1,355 @@
+const MIN_VOICE_TIME = 1, MAX_VOICE_TIME = 60, START_TIME_DOWN = 54, status = {
+    START: 1,
+    SUCCESS: 2,
+    CANCEL: 3,
+    SHORT: 4,
+    FAIL: 5,
+    UNAUTH: 6
+}, EVENT = {
+    EXTRA_CLICK: 'extraClickEvent',
+    EXTRA_ITEM_CLICK: 'extraItemClickEvent',
+    VOICE_RECORD: 'voiceRecordEvent',
+    SEND_MESSAGE: 'sendMessageEvent'
+};
+Component({
+    properties: {
+        minVoiceTime: {
+            type: Number,
+            value: MIN_VOICE_TIME
+        },
+        maxVoiceTime: {
+            type: Number,
+            value: MAX_VOICE_TIME
+        },
+        startTimeDown: {
+            type: Number,
+            value: START_TIME_DOWN,
+        },
+        tabBarHeight: {
+            type: Number,
+            value: 0
+        },
+        format: {
+            type: String,
+            value: 'mp3'
+        },
+        extraArray: {
+            type: Array,
+            value: []
+        }
+    },
+    data: {
+        windowHeight: 0,
+        windowWidth: 0,
+        cancelLineYPosition: 0,
+        _startTimeDown: START_TIME_DOWN,
+        timer: -1,
+        singleVoiceTimeCount: 0,
+        textMessage: '',
+        voiceObj: {moveToCancel: false},
+        extraObj: {
+            chatInputShowExtra: false,
+            chatInputExtraArr: []
+        },
+        inputStatus: 'text',
+        inputValueEventTemp: ''
+    },
+    observers: {
+        'extraArray'(value) {
+            this.setData({
+                'extraObj.chatInputExtraArr': value || []
+            })
+        },
+        'startTimeDown'(startTimeDown) {
+            const data = this.data;
+            data._startTimeDown = startTimeDown && startTimeDown < data.maxVoiceTime && startTimeDown > 0 ? startTimeDown : START_TIME_DOWN;
+        }
+    },
+    methods: {
+        getRecordStatus() {
+            return {...status};
+        },
+        closeExtraView() {
+            this.setData({
+                'extraObj.chatInputShowExtra': false
+            });
+        },
+        _chatInput$extra$click$event() {
+            const isShow = !this.data.extraObj.chatInputShowExtra;
+            this.setData({
+                'extraObj.chatInputShowExtra': isShow
+            }, () => {
+                this.triggerEvent(EVENT.EXTRA_CLICK, {isShow}, {});
+            });
+        },
+        _change$input$way$event() {
+            this.setData({
+                'inputStatus': this.data.inputStatus === 'text' ? 'voice' : 'text',
+                'extraObj.chatInputShowExtra': false
+            });
+        },
+        _triggerVoiceRecordEvent({status, dataset}) {
+            this.triggerEvent(EVENT.VOICE_RECORD, {recordStatus: status, ...dataset}, {});
+        },
+        _long$click$voice$btn(e) {
+            if ('send$voice$btn' === e.currentTarget.id) {//长按时需要打开录音功能,开始录音
+                this._checkRecordAuth(() => {
+                    const {maxVoiceTime, singleVoiceTimeCount} = this.data;
+                    this.setData({//调出取消弹窗
+                        'voiceObj.showCancelSendVoicePart': true,
+                        'voiceObj.timeDownNum': maxVoiceTime - singleVoiceTimeCount,
+                        'voiceObj.status': 'start',
+                        'voiceObj.startStatus': 1,
+                        'voiceObj.moveToCancel': false
+                    }, () => {
+                        this._triggerVoiceRecordEvent({status: status.START});
+                    });
+                    this.recorderManager.start({duration: 60000, format: this.data.format});
+                }, (res) => {
+                    //录音失败
+                    console.error('录音拒绝授权');
+                    clearInterval(timer);
+                    this._endRecord();
+                    this.setData({
+                        'voiceObj.status': 'end',
+                        'voiceObj.showCancelSendVoicePart': false
+                    });
+                    this._triggerVoiceRecordEvent({status: status.UNAUTH});
+
+                    wx.showModal({
+                        title: '您未授权语音功能',
+                        content: '暂时不能使用语音',
+                        confirmText: '去设置',
+                        success: res => {
+                            if (res.confirm) {
+                                wx.openSetting({
+                                    success: res => {
+                                        if (res.authSetting['scope.record']) {
+                                            this.setData({
+                                                'extraObj.chatInputShowExtra': false
+                                            });
+                                        }
+                                    }
+                                });
+                            } else {
+                                this.setData({
+                                    'inputStatus': 'text',
+                                    'extraObj.chatInputShowExtra': false
+                                });
+                            }
+                        }
+                    });
+                });
+            }
+        },
+        _dealVoiceLongClickEventWithHighVersion() {
+            this.recorderManager.onStart(() => {
+                this.data.singleVoiceTimeCount = 0;
+                const {_startTimeDown, maxVoiceTime} = this.data;
+                //设置定时器计时60秒
+                this.data.timer = setInterval(() => {
+                    const voiceTimeCount = ++this.data.singleVoiceTimeCount;
+                    if (voiceTimeCount >= _startTimeDown && voiceTimeCount < maxVoiceTime) {
+                        this.setData({
+                            'voiceObj.timeDownNum': maxVoiceTime - voiceTimeCount,
+                            'voiceObj.status': 'timeDown'
+                        })
+                    } else if (voiceTimeCount >= maxVoiceTime) {
+                        this.setData({
+                            'voiceObj.status': 'timeout'
+                        });
+                        this._delayDismissCancelView();
+                        clearInterval(this.data.timer);
+                        this._endRecord();
+                    }
+                }, 1000);
+            })
+        },
+        _send$voice$move$event(e) {
+            if ('send$voice$btn' === e.currentTarget.id) {
+                const {windowHeight, voiceObj, tabBarHeight, cancelLineYPosition} = this.data,
+                    y = windowHeight + tabBarHeight - e.touches[0].clientY;
+                if (y > cancelLineYPosition) {
+                    if (!voiceObj.moveToCancel) {
+                        this.setData({
+                            'voiceObj.moveToCancel': true
+                        });
+                    }
+                } else {
+                    if (voiceObj.moveToCancel) {//如果移出了该区域
+                        this.setData({
+                            'voiceObj.moveToCancel': false
+                        })
+                    }
+                }
+
+            }
+        },
+        _send$voice$move$end$event(e) {
+            if ('send$voice$btn' === e.currentTarget.id) {
+                const {singleVoiceTimeCount, minVoiceTime, timer} = this.data;
+                if (singleVoiceTimeCount < minVoiceTime) {//语音时间太短
+                    this.setData({
+                        'voiceObj.status': 'short'
+                    });
+                    this._delayDismissCancelView();
+                } else {//语音时间正常
+                    this.setData({
+                        'voiceObj.showCancelSendVoicePart': false,
+                        'voiceObj.status': 'end'
+                    });
+                }
+                clearInterval(timer);
+                this._endRecord();
+            }
+        },
+        _initVoiceData() {
+            const {windowWidth, windowHeight} = this.data, width = windowWidth / 2.6;
+            this.setData({
+                'inputStatus': 'text',
+                'windowHeight': windowHeight,
+                'windowWidth': windowWidth,
+                'voiceObj.status': 'end',
+                'voiceObj.startStatus': 0,
+                'voiceObj.voicePartWidth': width,
+                'voiceObj.moveToCancel': false,
+                'voiceObj.voicePartPositionToBottom': (windowHeight - width / 2.4) / 2,
+                'voiceObj.voicePartPositionToLeft': (windowWidth - width) / 2
+            });
+        },
+        _delayDismissCancelView() {
+            setTimeout(() => {
+                if (this.data.voiceObj.status !== 'start') {
+                    this.setData({
+                        'voiceObj.showCancelSendVoicePart': false,
+                        'voiceObj.status': 'end'
+                    });
+                }
+            }, 1000);
+        },
+
+        _endRecord() {
+            this.setData({
+                'voiceObj.startStatus': 0
+            }, () => {
+                this.recorderManager.stop();
+            });
+        },
+        _chatInput$bind$focus$event() {
+            this.setData({
+                'inputType': 'text'
+            })
+        },
+        _chatInput$send$text$message(e) {
+            this.setData({
+                textMessage: ''
+            }, () => {
+                this.triggerEvent(EVENT.SEND_MESSAGE, {value: e.detail.value});
+                this.data.inputValueEventTemp = '';
+            });
+        },
+        _chatInput$bind$blur$event() {
+            setTimeout(() => {
+                let obj = {};
+                if (!this.data.inputValueEventTemp) {
+                    this.data.inputValueEventTemp = '';
+                    obj['inputType'] = 'none';
+                }
+                obj['extraObj.chatInputShowExtra'] = false;
+                this.setData(obj);
+            });
+        },
+        _chatInput$send$text$message02() {
+            this.setData({
+                textMessage: '',
+                'inputType': 'none'
+            }, () => {
+                if (!!this.data.inputValueEventTemp) {
+                    this.triggerEvent(EVENT.SEND_MESSAGE, {value: this.data.inputValueEventTemp});
+                    this.data.inputValueEventTemp = '';
+                }
+            });
+        },
+        _chatInput$getValue$event(e) {
+            const {detail: {value: textMessage}} = e;
+            this.data.inputValueEventTemp = textMessage;
+            this.setData({
+                textMessage
+            })
+        },
+        _chatInput$extra$item$click$event(e) {
+            const {currentTarget: {dataset}} = e;
+            this.triggerEvent(EVENT.EXTRA_ITEM_CLICK, {...dataset}, {});
+        },
+
+        _setVoiceListener() {
+            this.recorderManager.onStop((res) => {
+                console.log(res, this.data.voiceObj.status);
+                if (this.data.voiceObj.status === 'short') {//录音时间太短或者移动到了取消录音区域, 则取消录音
+                    this._triggerVoiceRecordEvent({status: status.SHORT});
+                    return;
+                } else if (this.data.voiceObj.moveToCancel) {
+                    this._triggerVoiceRecordEvent({status: status.CANCEL});
+                    return;
+                }
+                console.log('录音成功');
+                this._triggerVoiceRecordEvent({status: status.SUCCESS, dataset: res});
+            });
+            this.recorderManager.onError((res) => {
+                this._triggerVoiceRecordEvent({status: status.FAIL, dataset: res});
+            });
+        },
+
+        _checkRecordAuth(cbOk, cbError) {
+            wx.getSetting({
+                success: (res) => {
+                    if (!res.authSetting['scope.record']) {
+                        wx.authorize({
+                            scope: 'scope.record',
+                            success: (res) => {
+                                // 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
+                                console.log('同意', res);
+                            }, fail: res => {
+                                console.log('拒绝', res);
+                                cbError && cbError();
+                            }
+                        })
+                    } else {
+                        cbOk && cbOk();
+                    }
+                }
+            })
+        }
+    },
+    lifetimes: {
+        created() {
+            this.recorderManager = wx.getRecorderManager();
+            const {windowHeight, windowWidth} = wx.getSystemInfoSync();
+            if (!windowHeight || !windowWidth) {
+                console.error('没有获取到手机的屏幕尺寸:windowWidth', windowWidth, 'windowHeight', windowHeight);
+                return;
+            }
+            this.data.windowHeight = windowHeight;
+            this.data.windowWidth = windowWidth;
+            this.data.cancelLineYPosition = windowHeight * 0.12;
+            this._dealVoiceLongClickEventWithHighVersion();
+            this._setVoiceListener();
+        },
+        attached() {
+            this._initVoiceData();
+        },
+        detached() {
+            clearInterval(this.data.timer);
+        }
+    }
+});
+
+// module.exports = {
+//     clickExtraListener: clickExtraItemListener,
+//     closeExtraView: closeExtraView,
+//     recordVoiceListener: sendVoiceListener,
+//     setVoiceRecordStatusListener: setVoiceRecordStatusListener,
+//     setTextMessageListener: setTextMessageListener,
+//     setExtraButtonClickListener: setExtraButtonClickListener,
+//     VRStatus: status
+// };

+ 3 - 0
apps/4Dkankan/components/chat-input/chat-input.json

@@ -0,0 +1,3 @@
+{
+  "component": true
+}

+ 28 - 0
apps/4Dkankan/components/chat-input/chat-input.wxml

@@ -0,0 +1,28 @@
+<import src="voice.wxml" />
+<import src="extra.wxml" />
+<view class="input-flex-column" catchtap="">
+    <view class="input-text-voice-super">
+        <image src="../../image/4Dage/chat/voice.png"
+               class="extra-btn-style" bindtap="_change$input$way$event"/>
+        <block wx:if="{{inputStatus==='voice'}}">
+            <template is="voice" data="{{voiceObj:voiceObj}}"/>
+        </block>
+        <input wx:elif="{{inputStatus==='text'}}"
+               class="chat-input-style" style="margin-left:{{showVoicePart?0:16}}rpx;"
+               placeholder="对Ta发送消息"
+               maxlength="500" confirm-type="send" value="{{textMessage}}" bindconfirm="_chatInput$send$text$message" bindfocus="_chatInput$bind$focus$event" bindblur="_chatInput$bind$blur$event" bindinput="_chatInput$getValue$event"/>
+        <view hover-class="press-style-opacity">
+            <view wx:if="{{inputType==='text'}}" class="chat-input-send-button-style" catchtap="_chatInput$send$text$message02">发送</view>
+            <image wx:else class="extra-btn-style"
+                   src="../../image/4Dage/chat/extra.png" catchtap="_chatInput$extra$click$event" />
+        </view>
+
+
+    </view>
+    <block wx:if="{{extraObj.chatInputShowExtra}}">
+        <view class="list-divide-line" />
+        <template is="chat-extra-function-part"
+                  data="{{chat$input$extra$arr:extraObj.chatInputExtraArr}}" />
+    </block>
+</view>
+

+ 58 - 0
apps/4Dkankan/components/chat-input/chat-input.wxss

@@ -0,0 +1,58 @@
+@import "voice.wxss";
+@import "extra.wxss";
+input{
+    padding-top: 10rpx;
+    padding-bottom:10rpx;
+    width: 100%;
+}
+
+.extra-btn-style{
+    width: 50rpx;
+    height: 50rpx;
+    /* padding:25rpx 15rpx; */
+    display: flex;
+    flex-shrink:0;
+}
+.input-text-voice-super{
+    display: flex;
+    flex-direction: row;
+    background-color: white;
+    width: 100%;
+    align-items: center;
+    height:120rpx;
+    padding: 0 10rpx;
+
+}
+.input-flex-column{
+    width:100%;
+    display: flex;
+    flex-direction:column;
+    position: fixed;
+    left: 0;
+    bottom: 0;
+}
+
+.list-divide-line {
+    width: 100%;
+    height: 1rpx;
+    background-color: #dddddd;
+}
+
+.chat-input-style{
+    border-radius:10rpx;
+    border:2rpx solid #e5e5e5;
+    height: 82rpx;
+    padding: 0 20rpx;
+    margin: 0 8px;
+}
+
+.chat-input-send-button-style{
+    width: 100rpx;
+    padding: 15rpx 0;
+    font-size: 30rpx;
+    text-align: center;
+    margin: 0 10rpx;
+    border-radius: 10rpx;
+    background-color: mediumseagreen;
+    color: white;
+}

+ 9 - 0
apps/4Dkankan/components/chat-input/extra.wxml

@@ -0,0 +1,9 @@
+<template name="chat-extra-function-part">
+    <view  class="extra-super" >
+
+        <view wx:for="{{chat$input$extra$arr}}" wx:key="{{index}}" class="flex-column" style="width: 25%" bindtap="_chatInput$extra$item$click$event" data-index="{{index}}">
+            <image class="extra-image-size" src="./../../image/chat/extra/{{item.picName}}.png" />
+            <text class="extra-text-size">{{item.description}}</text>
+        </view>
+    </view>
+</template>

+ 33 - 0
apps/4Dkankan/components/chat-input/extra.wxss

@@ -0,0 +1,33 @@
+.press-style{
+    background-color: rgba(0,0,0,0.1);
+}
+
+.press-style-opacity{
+    opacity: 0.5;
+}
+
+.extra-super{
+    display: flex;
+    padding-top: 25rpx;
+    height: 234rpx;
+    width: 100%;
+    background-color: white
+}
+.extra-image-size{
+    width: 114rpx;
+    height: 114rpx
+}
+.extra-text-size{
+    color: #666666;
+    font-size: 24rpx;
+    margin-top: 10rpx
+}
+
+.flex-column {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+.flex-row{
+    display: flex;
+}

File diff suppressed because it is too large
+ 15 - 0
apps/4Dkankan/components/chat-input/voice.wxml


+ 40 - 0
apps/4Dkankan/components/chat-input/voice.wxss

@@ -0,0 +1,40 @@
+
+.btn-voice-press {
+    background-color: gainsboro;
+}
+
+button{
+    font-size: 32rpx;
+    width:100%;
+    line-height: 74rpx;
+    margin-top: 15rpx;
+    margin-bottom: 15rpx;
+    margin: 0rpx 8px;
+}
+.voice-status-style{
+    position: absolute;
+    left: 0;
+    bottom: 22rpx;
+    width: 80%;
+    text-align: center;
+    font-size: 24rpx;
+    color: white;
+    padding-top: 5rpx;
+    padding-bottom: 5rpx;
+    margin-left: 10%;
+    border-radius: 10rpx;
+}
+
+.voice-record-git-status-style{
+    position: absolute;
+    left: 0;
+    bottom: 75rpx;
+    width: 100%;
+    display: flex;
+    justify-content: center;
+}
+
+.voice-record-git-size-style{
+    width: 58rpx;
+    height: 10rpx;
+}

+ 57 - 0
apps/4Dkankan/components/contact-user/cantact-user.js

@@ -0,0 +1,57 @@
+import ImSend from './../../utils/imSend'
+import { loginByUserInfo } from '../../utils/login'
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    agent_user: Object,
+    house_id: String
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+  pageLifetimes: {
+    show () {
+      this.setData({
+        loginStatus: getApp().globalData.token ? true : false
+      })
+    }
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    makePhone () {
+      wx.makePhoneCall({
+        phoneNumber: this.properties.agent_user.phone,
+      })
+    },
+    toAgentDetail () {
+      wx.navigateTo({
+        url: `/pages/agent-detail/agent-detail?agency_id=${this.properties.agent_user.agency_user_id}`,
+      })
+    },
+    senMsg () {
+      ImSend.sendVrMsg(getApp().globalData.last_house, '', this.properties.agent_user.agency_user_id, false).then(res => {
+        wx.navigateTo({
+          url: `/pages/chat/chat?toId=${this.properties.agent_user.agency_user_id}&toName=${this.properties.agent_user.name}`,
+        })
+      })
+    },
+    bindgetuserinfo (e) {
+      const { action } = e.currentTarget.dataset
+      loginByUserInfo().then(res => {
+        this.setData({
+          loginStatus: getApp().globalData.token ? true : false
+        })
+        this[action] && this[action]()
+      })
+    }
+  }
+})

+ 4 - 0
apps/4Dkankan/components/contact-user/cantact-user.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 12 - 0
apps/4Dkankan/components/contact-user/cantact-user.wxml

@@ -0,0 +1,12 @@
+<view class="contact-w">
+  <view class="avatar-w" bindtap="toAgentDetail" style="background-image: url({{agent_user.avatar || 'https://4dkk2.4dage.com/v3/img/apps/vrhouse/avatar_default.jpg?_=246'}})">
+    <image class="identify" src="{{agent_user.tag_image}}"></image>
+  </view>
+  <view class="user-info">
+    <view class="username">{{agent_user.name ? agent_user.name : ''}}</view>
+    <view class="user-type">{{agent_user.store ? agent_user.store : ''}}</view>
+  </view>
+  <button class="msg-btn btn" data-action="senMsg" open-type="getUserInfo" bindgetuserinfo="bindgetuserinfo" wx:if="{{ !loginStatus }}">在线问</button>
+  <button class="msg-btn btn" bindtap="senMsg" wx:else>在线问</button>
+  <button class="phone-btn btn" bindtap="makePhone">打电话</button>
+</view>

+ 57 - 0
apps/4Dkankan/components/contact-user/cantact-user.wxss

@@ -0,0 +1,57 @@
+.contact-w {
+  display: flex;
+  /* align-items: center; */
+  padding: 27rpx 48rpx 0;
+  box-shadow:0px -2px 8px rgba(0,0,0,0.16);
+  width: 100%;
+  height: 144rpx;
+  background: #fff;
+  z-index: 11111;
+}
+.user-info {
+  flex: 1;
+  font-size: 22rpx;
+  color: #79868F;
+}
+.username {
+  font-size: 30rpx;
+  color: #111111;
+  margin-top: 2rpx;
+}
+.user-type {
+}
+.avatar {
+  width: 76rpx;
+  height: 76rpx;
+  margin: 0 auto;
+  border-radius: 50%;
+}
+
+.btn {
+  color: #fff;
+  width: 168rpx;
+  height: 90rpx;
+  line-height: 90rpx;
+  text-align: center;
+  font-size: 30rpx;
+  background: #1FE4DC;
+}
+.msg-btn {
+  background: #00B4ED;
+  margin-right: 16rpx;
+}
+.avatar-w {
+  position: relative;
+  margin-right: 20rpx;
+  width: 80rpx;
+  height: 80rpx;
+  background-size: 100% 100%;
+  border-radius: 50%;
+}
+.identify {
+  width: 80rpx;
+  height: 80rpx;
+  position: absolute;
+  top: 0;
+  left: 0;
+}

+ 30 - 0
apps/4Dkankan/components/content-card/index.js

@@ -0,0 +1,30 @@
+// components/cotent-title/index.js
+Component({
+  externalClasses: ['l-content'],
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    name: {
+      type: String
+    },
+    cardPadding: {
+      type: Boolean,
+      value: true
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+
+  }
+})

+ 4 - 0
apps/4Dkankan/components/content-card/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 6 - 0
apps/4Dkankan/components/content-card/index.wxml

@@ -0,0 +1,6 @@
+<view class="content-card">
+    <view class="content-name">{{name}}</view>
+  <view class="card-bottom l-content ">
+    <slot />
+  </view>
+</view>

+ 43 - 0
apps/4Dkankan/components/content-card/index.wxss

@@ -0,0 +1,43 @@
+.content-card {
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0px -4rpx 16rpx 0px rgba(148, 163, 178, 0.05);
+  border-radius: 20rpx 20rpx 0px 0px;
+}
+
+.card-dot {
+  width: 18rpx;
+  height: 18rpx;
+  border-radius: 50%;
+  border: 6rpx solid #3963BC;
+  margin-left: 26rpx;
+}
+.card-top {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  width: 750rpx;
+  height: 100rpx;
+}
+
+.content-name {
+  font-size: 28rpx;
+  font-family: PingFangSC-Regular;
+  font-weight: 400;
+  color: rgba(69, 82, 107, 1);
+  line-height: 20px;
+  margin-left: 8px;
+}
+
+.card-bottom {
+  width: 750rpx;
+  box-sizing: border-box;
+}
+
+.card-padding {
+  padding: 0rpx 20rpx 40rpx 20rpx;
+}
+
+.padd{
+  padding-bottom: 40rpx;
+}

+ 37 - 0
apps/4Dkankan/components/content-title/index.js

@@ -0,0 +1,37 @@
+// components/cotent-title/index.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    name: {
+      type: String
+    },
+    describe: {
+      type: String
+    },
+    doc: {
+      type: Boolean,
+      value: true
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onDoc() {
+      wx.navigateToMiniProgram({
+        appId: 'wxb05fa7b69aa7e5b7',
+        path: '/pages/md/index?title=' + this.properties.name + '&desc=' + this.properties.describe
+      })
+    }
+  }
+})

+ 4 - 0
apps/4Dkankan/components/content-title/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 9 - 0
apps/4Dkankan/components/content-title/index.wxml

@@ -0,0 +1,9 @@
+<view class="content-title">
+  <view class="content-name">{{name}}</view>
+  <view class="content-line"></view>
+  <view class="content-describe">{{describe}}</view>
+  <slot />
+<!--  <view wx:if="{{doc}}" class="doc-container" bindtap="onDoc">-->
+<!--    <image src='/images/doc.png' class='doc-img'></image>-->
+<!--  </view>-->
+</view>

+ 64 - 0
apps/4Dkankan/components/content-title/index.wxss

@@ -0,0 +1,64 @@
+page{
+  background-color: white;
+}
+
+.content-title {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background: #f7f7f7;
+  box-sizing: border-box; 
+  padding-bottom: 160rpx;
+}
+ 
+.content-name {
+  font-size: 36rpx;
+  font-family: PingFangSC-Regular;
+  font-weight: 400;
+  color: rgba(69, 82, 107, 1);
+  line-height: 50rpx;
+  margin-top: 22rpx;
+}
+
+.content-line {
+  width: 28rpx;
+  height: 6rpx;
+  background: rgba(69, 82, 107, 1);
+  border-radius: 2px;
+}
+
+.content-describe {
+  font-size: 24rpx;
+  font-family: PingFangSC-Regular;
+  font-weight: 400;
+  color: rgba(140, 152, 174, 1);
+  line-height: 24rpx;
+  margin-top: 6rpx;
+  margin-bottom: 22rpx
+}
+
+.doc-container{
+  position: fixed;
+  right: 40rpx;
+  bottom: 60rpx;
+  z-index: 2;
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  box-shadow:0px 3px 8px 0px rgba(155,193,185,0.5);
+  display: flex;
+  justify-content: center;
+  background-color: #fff;
+  align-items: center;
+}
+
+.doc-img{
+  width: 50rpx;
+  height:50rpx;
+}
+
+.doc-txt{
+  font-size: 28rpx;
+  color: #3963BC;
+  margin-top: 15rpx;
+}

+ 38 - 0
apps/4Dkankan/components/detail-components/detail-banner/detail-banner.js

@@ -0,0 +1,38 @@
+// components/house-item/house-item.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    imglist: Array,
+    vrLink: String
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    currentIndex: 0,
+    preIndex: 1
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    swiperchange (e) {
+      this.setData({
+        preIndex: e.detail.current + 1
+      })
+    },
+    HandleImgClick (e) {
+      
+    },
+    onlineWatch () {
+      this.triggerEvent('onlineWatch')
+    },
+    toVrHouse () {
+      this.triggerEvent('toVrHouse')
+    }
+  }
+})

+ 4 - 0
apps/4Dkankan/components/detail-components/detail-banner/detail-banner.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 18 - 0
apps/4Dkankan/components/detail-components/detail-banner/detail-banner.wxml

@@ -0,0 +1,18 @@
+<view class="banner">
+    <swiper  class="screen-swiper" bindchange="swiperchange" current="{{currentIndex}}"  circular="true"  duration="500">
+        <swiper-item 
+        wx:for="{{imglist}}" 
+        wx:key="index" bindtap="HandleImgClick"
+        data-urls="{{imglist}}" 
+        data-currenturl="{{item}}"
+        >
+          <image src="{{item}}" mode="aspectFill" lazy-load="true"></image>
+          <image src="/image/4Dage/vr-icon-big.svg" class="can-vr" wx-if="{{vrLink && index === 0}}" bind:tap="toVrHouse" />
+        </swiper-item>
+    </swiper>
+    <view class="vr-click-tip" wx:if="{{vrLink}}" bindtap="onlineWatch">语音云带看</view>
+    <view class="swiper-page ">
+        <view class="mask"></view>
+        <view class="swiper-page-content">{{ preIndex }} / {{imglist.length}}</view>
+    </view>
+</view>

File diff suppressed because it is too large
+ 70 - 0
apps/4Dkankan/components/detail-components/detail-banner/detail-banner.wxss


+ 34 - 0
apps/4Dkankan/components/detail-components/detail-intro/detail-intro.js

@@ -0,0 +1,34 @@
+
+import HouseApi from '../../../apis/house'
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    house_id: String
+  },
+  data: {
+    intros: []
+  },
+  ready () {
+    this.getHouseDetail(this.properties.house_id)
+  },
+  methods: {
+    getHouseDetail(house_id){
+      wx.showLoading({
+        title: '加载数据中'
+      })
+      HouseApi.houseDetail(house_id).then(res => {
+        let house = res.data
+        if (house.remarks) {
+          this.setData({
+            intros: JSON.parse(house.remarks)
+          })
+        }
+        wx.hideLoading()
+      }).catch(err => {
+        wx.hideLoading()
+      })
+    },
+  }
+})

+ 4 - 0
apps/4Dkankan/components/detail-components/detail-intro/detail-intro.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 7 - 0
apps/4Dkankan/components/detail-components/detail-intro/detail-intro.wxml

@@ -0,0 +1,7 @@
+<view class="detail-intro">
+  <view class="intro-title">房源介绍</view>
+  <view class="intro-content" wx:for="{{intros}}" wx:key="index">
+    <view class="content-label">{{ item.title }}</view>
+    <view>{{ item.text }}</view>
+  </view>
+</view>

+ 22 - 0
apps/4Dkankan/components/detail-components/detail-intro/detail-intro.wxss

@@ -0,0 +1,22 @@
+.detail-intro {
+  padding: 0 44rpx;
+  color: #111;
+  font-size: 30rpx;
+  box-sizing: border-box;
+}
+
+.intro-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  padding: 68rpx 0 34rpx;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+.intro-content {
+  line-height: 42rpx;
+}
+.content-label {
+  padding: 34rpx 0 28rpx;
+  color: #A3A4A5;
+  line-height: 42rpx;
+}

+ 322 - 0
apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.js

@@ -0,0 +1,322 @@
+
+import HouseApi from '../../../apis/house'
+import { fotmatDate } from '../../../utils/date'
+import loginFn from '../../../utils/login'
+import MsgManager from '../../../pages/chat/msg-manager'
+import { randomString } from './../../../utils/tools'
+import { saveViewHistory } from '../../../utils/storage'
+import { loginByUserInfo } from '../../../utils/login'
+const app = getApp();
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    house_id: String,
+    agent_user: Object,
+    isAgency: Boolean
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    loginStatus: app.globalData.token ? true : false,
+    userinfo: app.globalData.userinfo,
+    isFocused: false,
+    recommendList: [],
+    user_code: '',
+    markers: [],
+    house: getApp().globalData.lastHouse,
+    detailItems: [
+      {
+        label: '单价',
+        value: '',
+        name: 'unit_price'
+      },
+      {
+        label: '挂牌',
+        value: '',
+        name: 'apply_time',
+        format: fotmatDate
+      },
+      {
+        label: '朝向',
+        value: '',
+        name: 'orientation'
+      },
+      {
+        label: '楼层',
+        value: '',
+        name: 'floor_num'
+      },
+      {
+        label: '楼型',
+        value: '',
+        name: 'building_type'
+      },
+      {
+        label: '装修',
+        value: '',
+        name: 'decoration'
+      },
+      {
+        label: '年代',
+        value: '',
+        name: 'building_age'
+      },
+      {
+        label: '用途',
+        value: '',
+        name: 'house_usage'
+      },
+    ]
+  },
+  pageLifetimes: {
+    show () {
+      this.setData({
+        loginStatus: app.globalData.token ? true : false,
+        userinfo: app.globalData.userinfo,
+        app_type: app.globalData.type
+      })
+    }
+  },
+  ready () {
+    this.setData({
+      loginStatus: app.globalData.token ? true : false,
+      userinfo: app.globalData.userinfo,
+    })
+    this.getHouseDetail(this.properties.house_id)
+    this.fetchRecommendHouseList()
+    this.msgManager = new MsgManager(this)
+    if (!this.data.loginStatus) {
+      wx.login({
+        success: (res) => {
+          this.setData({
+            user_code: res.code
+          })
+        }
+      })
+    } else {
+      this.getHouseFocusStatus()
+    }
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    toMap () {
+      const house = this.data.house
+      wx.navigateTo({
+        url: `/pages/map/map?longitude=${house.longitude}&latitude=${house.latitude}&housename=${house.district || ''} ${house.estate_name || ''}${this.properties.isAgency ? '' : `&agency_id=${this.properties.agent_user.agency_user_id}&agency_name=${this.properties.agent_user.name}&phone=${this.properties.agent_user.phone}`}`
+      })
+    },
+    sendSchoolMsg () {
+      if (!getApp().globalData.token) {
+        wx.navigateTo({
+          url: '/pages/login/login',
+        })
+        return
+      }
+      Promise.all([this.sendTextMsg('您好,请问这个房子周边有哪些学校?')]).then(res => {
+        wx.navigateTo({
+          url: `/pages/chat/chat?toId=${this.properties.agent_user.agency_user_id}&toName=${this.properties.agent_user.name}`
+        })
+      })
+    },
+    getHouseDetail(house_id){
+      wx.showLoading({
+        title: '加载数据中'
+      })
+      HouseApi.houseDetail(house_id).then(res => {
+        let { detailItems } = this.data
+        let house = res.data
+        detailItems.forEach(item => {
+          if (house[item.name]) {
+            // 若有format函数,则执行处理成想要格式化的值
+            if (item.format && typeof item.format === 'function') {
+              house[item.name] = item.format(house[item.name])
+            }
+            item.value = house[item.name]
+          } else {
+            item.value = '暂无数据'
+          }
+        })
+        house.tags = JSON.parse(house.tags || "[]")
+        house.remarks = JSON.parse(house.remarks || "[]")[0].text
+        res.data.detail_images = JSON.parse(res.data.detail_images)
+        res.data.detail_images = res.data.detail_images.map(item => `${item}?x-oss-process=image/resize,w_750,limit_0/quality,q_75`)
+        let markers = [
+          {
+            longitude: house.longitude,
+            latitude: house.latitude,
+            housename: house.title,
+            iconPath: "/image/4Dage/location-cycle.png",
+            zIndex: -1,
+            callout: {
+              content: `${house.district || ''} ${house.estate_name || ''}`,
+              display: 'ALWAYS',
+              padding: 15,
+              anchorY: 10
+            }
+          }
+        ]
+        this.setData({
+          house: res.data,
+          detailItems: detailItems,
+          markers
+        })
+        saveViewHistory(res.data)
+        app.globalData.last_house = house
+        wx.hideLoading()
+      }).catch(err => {
+        console.log(err, '获取详情出错')
+        wx.hideLoading()
+      })
+    },
+    getHouseFocusStatus () {
+      const { loginStatus } = this.data
+      if (!loginStatus) {
+        return
+      }
+      return HouseApi.isHouseFocused({
+        house_id: this.properties.house_id,
+        user_id: this.data.userinfo.user_id
+      }).then((res) => {
+        this.setData({
+          isFocused: res.data === -1 ? false : true
+        })
+        return res.data
+      })
+    },
+    bindgetphonenumber (e) {
+      loginFn(e, this.data.user_code, this.properties.isAgency ? true : false).then(res => {
+        this.setData({
+          loginStatus: app.globalData.token ? true : false,
+          userinfo: app.globalData.userinfo,
+        })
+        this.getHouseFocusStatus().then(res => {
+          if (!res) {
+            this.changeFocusStatus()
+          }
+        })
+      })
+    },
+    changeFocusStatus () {
+      const is_valid = this.data.isFocused ? 0 : 1
+      HouseApi.focusHouse({
+        is_valid,
+        house_id: this.properties.house_id
+      }).then(res => {
+        this.setData({
+          isFocused: is_valid
+        })
+      })
+    },
+    cancleFocusStatus () {
+      return HouseApi.cancleFocusHouse({house_id: this.properties.house_id}).then(() => {
+        this.setData({
+          isFocused: 0
+        })
+      })
+    },
+    fetchRecommendHouseList () {
+      const fetchData = {
+        city: app.globalData.city,
+        saleState: 1,
+        page_size: 8
+      }
+      HouseApi.houseList(fetchData).then(res => {
+        this.setData({
+          recommendList: res.data.list
+        })
+      })
+    },
+    toMoreIntro () {
+      wx.navigateTo({
+        url: `/pages/detail-intro/detail-intro?house_id=${this.properties.house_id}`
+      })
+    },
+    addReadHistory () {
+      HouseApi.addReadHistory({ house_id: this.properties.house_id })
+    },
+    // 发送带看
+    onlineWatch () {
+      if (!this.data.loginStatus) {
+        wx.navigateTo({
+          url: '/pages/login/login'
+        })
+        return
+      }
+      if (this.properties.isAgency) {
+        wx.navigateTo({
+          url: `/pages/message-list/message-list?house_id=${this.properties.house_id}`
+        })
+        return
+      }
+      const room_id = randomString(18)
+      this.toVrHouse(room_id, true)
+    },
+    // 发送房屋卡片
+    sendVrMsg (room_id, is_vr_invite) {
+      const { house } = this.data
+      let content = {
+        house_name: house.title,
+        image_url: house.detail_images[0],
+        house_type: house.house_type,
+        house_area: house.area,
+        orientation: house.orientation,
+        price: `${(house.price/10000).toFixed(0)}万`,
+        vr_link: is_vr_invite ? `${house.vrLink}${encodeURIComponent(`&room_id=${room_id}`)}` : '',
+        house_id: house.house_id
+      }
+      return this.sendMsg({content, msgType: 'vr'})
+    },
+    // 发送文字
+    sendTextMsg (content) {
+      return this.sendMsg({content, msgType: 'text'})
+    },
+    sendMsg ({content, msgType}) {
+      let defaultContent = {
+        fromId: app.globalData.userinfo.user_id,
+        fromName: app.globalData.userinfo.phone,
+        toId: this.properties.agent_user.agency_user_id,
+        toName: 'xu',
+        type: 'TYPE_ONE',
+        msgType,
+        content: content
+      }
+      return getApp().getIMHandler().newFriendSendMsg({content: defaultContent})
+    },
+    toVrHouse (room_id, isAuto) {
+      if (typeof room_id !== 'string') room_id = ''
+      
+      this.postDataToOpen(room_id).then(res => {
+        const vr_link = res.data.vrLink
+        wx.navigateTo({
+          url: `/pages/web/web?room_id=${res.data.roomId}&vr_link=${encodeURIComponent(vr_link)}&house_id=${this.properties.house_id}${isAuto ? `&is_auto=${isAuto}` : ''}`
+        })
+      })
+      
+    },
+    postDataToOpen (room_id) {
+      return HouseApi.postDataToOpen({house_id: this.properties.house_id, room_id, type: 'customer'})
+    },
+    toAgentDetail () {
+      wx.navigateTo({
+        url: `/pages/agent-detail/agent-detail?agency_id=${this.properties.agent_user.agency_user_id}`,
+      })
+    },
+    bindgetuserinfo (e) {
+      const { action } = e.currentTarget.dataset
+      loginByUserInfo().then(res => {
+        this.setData({
+          loginStatus: app.globalData.token ? true : false,
+          userinfo: app.globalData.userinfo,
+        })
+        this.getHouseFocusStatus()
+        this[action] && this[action]()
+      })
+    }
+  }
+})

+ 7 - 0
apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "detail-banner": "/components/detail-components/detail-banner/detail-banner",
+    "house-item": "/components/house-item/house-item"
+  }
+}

+ 97 - 0
apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.wxml

@@ -0,0 +1,97 @@
+<detail-banner imglist="{{house.detail_images}}" vrLink="{{house.vrLink}}" bind:onlineWatch="onlineWatch" bind:toVrHouse="toVrHouse" />
+<scroll-view class="contanerbox" scroll-y="true" wx:if="{{house.house_id}}">
+    <view class="house-info padding-container">
+        <view class="tab-list">
+            <view class="tab-item" wx:for="{{house.tags}}" wx:key="index">{{ item }}</view>
+        </view>
+        <view class="follow-status-w">
+          <button class="follow-status" open-type="getPhoneNumber" bindgetphonenumber="bindgetphonenumber" wx:if="{{ !loginStatus && app_type !== 'user' }}">
+            <image class="follow-icon" src="/image/4Dage/detail/icon-follow.svg" />
+            关注
+          </button>
+          <button class="follow-status" data-action="changeFocusStatus" open-type="getUserInfo" bindgetuserinfo="bindgetuserinfo" wx:elif="{{ !loginStatus && app_type === 'user' }}">
+            <image class="follow-icon" src="/image/4Dage/detail/icon-follow.svg" />
+            关注
+          </button>
+          <view class="follow-status" wx:elif="{{ !isFocused }}" bindtap="changeFocusStatus">
+            <image class="follow-icon" src="/image/4Dage/detail/icon-follow.svg" />
+            关注
+          </view>
+          <view class="follow-status" wx:elif="{{ isFocused }}" bindtap="cancleFocusStatus">
+            <image class="follow-icon" src="/image/4Dage/detail/icon-follow-active.svg" />
+            已关注
+          </view>
+        </view>
+        <view class="house-name ">{{ house.title }}</view>
+        <view class="contact-user">房源负责人:{{ agent_user.name }}<view class="icon-idcard" bind:tap="toAgentDetail"></view></view>
+        <view class="house-parameter">
+            <view class="parameter-item">
+                <view class="price">{{ house.price / 10000 }}万</view>
+                <view class="parameter-name">售价</view>
+            </view>
+            <view class="parameter-item">
+                <view class="price">{{ house.house_type || '暂无数据' }}</view>
+                <view class="parameter-name">房型</view>
+            </view>
+            <view class="parameter-item">
+                <view class="price">{{ house.area ? house.area + 'm²' : '暂无数据' }}</view>
+                <view class="parameter-name">建筑面积</view>
+            </view>
+        </view>
+        <view class="house-detail">
+            <view class="detail-item" wx:for="{{detailItems}}" wx:key="name">
+                <text class="label">{{ item.label }}:</text>
+                <text class="value">{{ item.value }}</text>
+            </view>
+        </view>
+        <view class="vr-tip" wx:if="{{!isAgency && house.vrLink}}">
+            <view class="vr-tip-logo"></view>
+            <view class="vr-tip-right">
+                <view class="vr-tip-title">语音云带看</view>
+                <view class="vr-tip-intro">在线语音带看,足不出户看实况</view>
+                <view class="vr-tip-btn" bind:tap="onlineWatch">线上带看</view>
+            </view>
+        </view>
+    </view>
+    <view class="location-info padding-container">
+        <view class="location-text-info">
+            <view class="detail-title">位置及周边配套</view>
+            <view class="location-item">
+                <text class="location-label">位置:</text>
+                <text class="location-value">{{ house.city + house.estate_name }}</text>
+            </view>
+            <view class="location-item"  wx:if="{{!isAgency}}">
+                <text class="location-label">学校信息:</text>
+                <text class="location-link" bindtap="sendSchoolMsg">咨询经纪人中小学情况</text>
+            </view>
+        </view>
+    </view>
+    <view class="map-w">
+      <map
+      id="map"
+      class="detail-map"
+      longitude="{{markers[0].longitude}}" 
+      latitude="{{markers[0].latitude}}"
+      enable-scroll="{{ false }}"
+      enable-zoom="{{ false }}"
+      markers="{{markers}}"
+      show-location>
+      </map>
+      <view class="cover" bindtap="toMap"></view>
+    </view>
+    
+    <view class=" padding-container" bind:tap="toMoreIntro">
+        <view class="location-text-info border-btn">
+            <view class="detail-title">房源介绍
+                <view class="arrow-right fr"></view>
+            </view>
+            <view class="detail-text">{{ house.remarks }}</view>
+        </view>
+    </view>
+    <view class="padding-container">
+        <view class="detail-title m-t-32">推荐房源({{recommendList.length}})</view>
+        <view class="house-list">
+            <house-item wx:for="{{recommendList}}" wx:key="id" house="{{item}}" />
+        </view>
+    </view>
+</scroll-view>

File diff suppressed because it is too large
+ 210 - 0
apps/4Dkankan/components/detail-components/detail-scroll/detail-scroll.wxss


+ 103 - 0
apps/4Dkankan/components/header-nav/header-nav.js

@@ -0,0 +1,103 @@
+Component({
+  properties: {
+    background: {
+      type: String,
+      value: 'rgba(255, 255, 255, 1)'
+    },
+    color: {
+      type: String,
+      value: 'rgba(0, 0, 0, 1)'
+    },
+    titleText: {
+      type: String,
+      value: '导航栏'
+    },
+    titleImg: {
+      type: String,
+      value: ''
+    },
+    backIcon: {
+      type: String,
+      value: '/image/4Dage/icon-back.svg'
+    },
+    homeIcon: {
+      type: String,
+      value: '/image/4Dage/icon-home.svg'
+    },
+    fontSize: {
+      type: Number,
+      value: 16
+    },
+    iconHeight: {
+      type: Number,
+      value: 19
+    },
+    iconWidth: {
+      type: Number,
+      value: 58
+    }
+  },
+  attached: function () {
+    var that = this;
+    that.setNavSize();
+    that.setStyle();
+  },
+  data: {
+  },
+  methods: {
+    // 通过获取系统信息计算导航栏高度 
+    setNavSize: function () {
+      var that = this
+        , sysinfo = wx.getSystemInfoSync()
+        , statusHeight = sysinfo.statusBarHeight
+        , isiOS = sysinfo.system.indexOf('iOS') > -1
+        , navHeight;
+      if (!isiOS) {
+        navHeight = 48;
+      } else {
+        navHeight = 44;
+      }
+      that.setData({
+        status: statusHeight,
+        navHeight: navHeight
+      })
+    },
+    setStyle: function () {
+      var that = this
+        , containerStyle
+        , textStyle
+        , iconStyle;
+      containerStyle = [
+        'background:' + that.data.background
+      ].join(';');
+      textStyle = [
+        'color:' + that.data.color,
+        'font-size:' + that.data.fontSize + 'px'
+      ].join(';');
+      iconStyle = [
+        'width: ' + that.data.iconWidth + 'px',
+        'height: ' + that.data.iconHeight + 'px'
+      ].join(';');
+      that.setData({
+        containerStyle: containerStyle,
+        textStyle: textStyle,
+        iconStyle: iconStyle
+      })
+    },
+    // 返回事件 
+    back: function () {
+      wx.navigateBack({
+        delta: 1
+      })
+      getApp().autoSubcrebe && getApp().autoSubcrebe()
+      this.triggerEvent('back', { back: 1 })
+    },
+    home: function () {
+      getApp().autoSubcrebe && getApp().autoSubcrebe()
+      wx.switchTab({
+        url: '/pages/index/index'
+      })
+      this.triggerEvent('home', {});
+    }
+  }
+})

+ 4 - 0
apps/4Dkankan/components/header-nav/header-nav.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 20 - 0
apps/4Dkankan/components/header-nav/header-nav.wxml

@@ -0,0 +1,20 @@
+<view class='nav' style='height: {{status + navHeight}}px'>
+  <view class='status' style='height: {{status}}px;{{containerStyle}}'></view>
+  <view class='navbar' style='height:{{navHeight}}px;{{containerStyle}}'>
+    <view class="navbar-left">
+      <view class='back-icon' wx:if="{{backIcon}}" bindtap='back'>
+        <image src='{{backIcon}}'></image>
+      </view>
+      <view class='home-icon' wx:if="{{homeIcon}}" bindtap='home'>
+        <image src='{{homeIcon}}'></image>
+      </view>
+    </view>
+    <view class='nav-icon' wx:if="{{titleImg}}">
+      <image src='{{titleImg}}' style='{{iconStyle}}'></image>
+    </view>
+    <view class='nav-title' wx:if="{{titleText && !titleImg}}">
+      <text style='{{textStyle}}'>{{titleText}}</text>
+    </view>
+  </view>
+</view>
+<view class="auto-padding" style='height: {{status + navHeight}}px'></view>

+ 67 - 0
apps/4Dkankan/components/header-nav/header-nav.wxss

@@ -0,0 +1,67 @@
+.nav {
+  position: fixed;
+  width: 100%;
+  background: #fff;
+  z-index: 10000000;
+}
+.navbar {
+  position: relative;
+}
+.navbar-left {
+  width: 80px;
+  height: 27px;
+  border: 1rpx solid #EAEAEA;
+  border-radius: 14px;
+  position: absolute;
+  left: 14rpx;
+  top: 50%;
+  transform: translateY(-50%);
+}
+.back-icon,
+.home-icon {
+  width: 40px;
+  height: 100%;
+  position: absolute;
+  transform: translateY(-50%);
+  top: 50%;
+  display: flex;
+}
+.back-icon {
+  left: 0;
+}
+.back-icon::after {
+  content: '';
+  display: block;
+  position: absolute;
+  height: 16px;
+  width: 1rpx;
+  background: #e5e5e5;
+  right: 0;
+  top: 5.5px;
+}
+.home-icon {
+  left: 40px;
+}
+.back-icon image {
+  width: 9px;
+  height: 15px;
+  margin: auto;
+}
+.home-icon image {
+  width: 15px;
+  height: 15px;
+  margin: auto;
+}
+.nav-title,
+.nav-icon {
+  position: absolute;
+  transform: translate(-50%, -50%);
+  left: 50%;
+  top: 50%;
+  font-size: 0;
+  font-weight: bold;
+}
+
+.auto-padding {
+  width: 100%;
+}

+ 30 - 0
apps/4Dkankan/components/house-item/house-item.js

@@ -0,0 +1,30 @@
+// components/house-item/house-item.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    house: Object
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    toDetail () {
+      getApp().autoSubcrebe && getApp().autoSubcrebe()
+      getApp().globalData.lastHouse = this.properties.house
+      console.log(this.properties.house)
+      wx.navigateTo({
+        url: `/pages/detail/detail?house_id=${this.properties.house.house_id}`
+      })
+    }
+  }
+})

+ 4 - 0
apps/4Dkankan/components/house-item/house-item.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 24 - 0
apps/4Dkankan/components/house-item/house-item.wxml

@@ -0,0 +1,24 @@
+<view class="house-item" bindtap="toDetail">
+  <view class="margin-right-sm house-image-w  active">
+    <view class=" card-house-image card-img-primary  imgurl" style="background-image: url('{{house.cover_image}}');"></view>
+    <view class="can-vr" wx:if="{{house.vrLink}}"></view>
+  </view>
+  <view class="card-content">
+    <view class="house-title ">
+      {{house.title}}
+    </view>
+    <view class="content">
+      <view class="houseinfo">
+        <view class="text-black address" >{{ house.district }}/{{ house.address }}</view>
+      </view>
+      <view class="tap-list" >
+        <view class="tap-item" wx:if="{{house.vrLink}}">云看房</view>
+        <view class="tap-item" wx:if="{{house.vrLink}}">随时可看</view>
+      </view>
+    <view class="price-w">
+        <text class="price text-bold">{{ house.price/10000 }}万</text>
+        <text class="price-tip">{{ house.unit_price }}元/平</text>
+      </view>
+    </view>
+  </view>
+</view>

File diff suppressed because it is too large
+ 88 - 0
apps/4Dkankan/components/house-item/house-item.wxss


+ 43 - 0
apps/4Dkankan/components/login-pannel/login-pannel.js

@@ -0,0 +1,43 @@
+import { loginByUserInfo } from './../../utils/login'
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    visible: Boolean
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    show: false
+  },
+  ready () {
+    this.showPannel()
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    bindgetuserinfo () {
+      loginByUserInfo().then(res => {
+        this.triggerEvent('loginSuccess')
+      })
+    },
+    showPannel () {
+      this.animation = wx.createAnimation({
+        duration: 200,
+        timingFunction: 'ease'
+      })
+      this.animation.translateY(8).step()
+      this.setData({
+        animationData: this.animation.export(),
+        show: true
+      })
+    },
+    hide () {
+      this.triggerEvent('hide')
+    }
+  }
+})

+ 4 - 0
apps/4Dkankan/components/login-pannel/login-pannel.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 10 - 0
apps/4Dkankan/components/login-pannel/login-pannel.wxml

@@ -0,0 +1,10 @@
+<cover-view class="login-pannel-w" >
+  <cover-view class="mask" bind:tap="hide"></cover-view>
+  <cover-view class="login-pannel" animation="{{animationData}}">
+    <cover-view>授权登录</cover-view>
+    <cover-image class="login-logo" src="/image/4Dage/login-logo.png" mode="heightFix"></cover-image>
+    <cover-view class="logo-bottom-tips">四维时代网络科技有限公司申请获取以下权限</cover-view>
+    <cover-view class="tips">·获得您的公开信息(昵称、头像等)</cover-view>
+    <button class="login-btn" open-type="getUserInfo" bindgetuserinfo="bindgetuserinfo">微信授权登录</button>
+  </cover-view>
+</cover-view>

+ 58 - 0
apps/4Dkankan/components/login-pannel/login-pannel.wxss

@@ -0,0 +1,58 @@
+.login-pannel-w {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 9999;
+}
+.mask {
+  width: 100vw;
+  height: 100vh;
+  background: rgba(0,0,0,0.3);
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 9999;
+}
+.login-pannel {
+  font-size: 30rpx;
+  color: #111;
+  border-radius: 16rpx 16rpx 0rpx 0rpx;
+  background: #fff;
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  width: 100vw;
+
+  padding: 52rpx 36rpx 100rpx;
+  z-index: 10000;
+  box-sizing: border-box;
+  transform: translateY(315px);
+}
+.login-logo {
+  width: 181.45rpx;
+  height: 120rpx;
+  margin: 0 auto;
+}
+.logo-bottom-tips {
+  padding: 28rpx 0 36rpx;
+  border-bottom: 1px solid rgba(0,0,0,0.1);
+  margin-bottom: 30rpx;
+  font-size: 26rpx;
+  text-align: center;
+}
+.tips {
+  font-size: 26rpx;
+  color: #79868F;
+  margin-bottom: 86rpx;
+  text-align: center;
+}
+.login-btn {
+  border-color: #1FE4DC;
+  background: #1FE4DC;
+  color: #fff;
+  font-size: 30rpx;
+  line-height: 80rpx;
+  border-radius: 8rpx;
+}

+ 23 - 0
apps/4Dkankan/components/modal/modal.js

@@ -0,0 +1,23 @@
+// components/house-item/house-item.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    house: Object
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+
+  }
+})

+ 4 - 0
apps/4Dkankan/components/modal/modal.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 6 - 0
apps/4Dkankan/components/modal/modal.wxml

@@ -0,0 +1,6 @@
+<view class="modal">
+  <view class="mask"></view>
+  <view class="content">
+    <slot></slot>
+  </view>
+</view>

+ 24 - 0
apps/4Dkankan/components/modal/modal.wxss

@@ -0,0 +1,24 @@
+.modal {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: #000;
+  opacity: 0.3;
+}
+
+.content {
+  position: absolute;
+}

+ 23 - 0
apps/4Dkankan/components/no-more-bar/no-more-bar.js

@@ -0,0 +1,23 @@
+// components/house-item/house-item.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    house: Object
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+
+  }
+})

+ 4 - 0
apps/4Dkankan/components/no-more-bar/no-more-bar.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 3 - 0
apps/4Dkankan/components/no-more-bar/no-more-bar.wxml

@@ -0,0 +1,3 @@
+<view class="no-more-bar">
+  没有更多了
+</view>

+ 9 - 0
apps/4Dkankan/components/no-more-bar/no-more-bar.wxss

@@ -0,0 +1,9 @@
+.no-more-bar {
+  width: 100%;
+  height: 84rpx;
+  line-height: 84rpx;
+  background: #F2F2F2;
+  font-size: 26rpx;
+  color: #A3A4A5;
+  text-align: center;
+}

+ 93 - 0
apps/4Dkankan/components/searchbar/searchbar.js

@@ -0,0 +1,93 @@
+// components/searchbar.js
+const app = getApp();
+Component({
+  options: {
+    addGlobalClass: true,
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    paramAtoB: {
+      type: String,//类型
+      value: 'default value'//默认值
+    },
+    currentvalue: {
+      type: String,//类型
+      value: '',//默认值
+      observer: function(newVal, oldVal) {
+        // 属性被改变时执行的函数(可选)
+        this.setData({
+          inputVal:newVal
+        })
+      }
+    },
+    disabled: {
+      type: Boolean,
+      value: false
+    },
+    placeholder: {
+      type: String,
+      value: ''
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    inputShowed: false,
+    timer: null,
+    inputVal: ""
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    backfill: function (e) {
+      var id = e.currentTarget.id;
+      for (var i = 0; i < this.data.suggestion.length;i++){
+        if(i == id){
+          this.setData({
+            backfill: this.data.suggestion[i].title
+          });
+        }
+      }
+    },
+    //触发关键词输入提示事件
+    getsuggest: function(e) {
+      var _this = this;
+      var old_timer = this.data.timer;
+      if (old_timer) {
+        clearTimeout(old_timer)
+      }
+      this.setData({
+        timer:setTimeout(() => {
+          if(e.detail.value){
+            app.globalData.qqmapsdk.getSuggestion({
+              //获取输入框值并设置keyword参数
+              keyword: e.detail.value, //用户输入的关键词,可设置固定值,如keyword:'KFC'
+              region:app.globalData.city, //设置城市名,限制关键词所示的地域范围,非必填参数
+              success: function(res) {//搜索成功后的回调
+                var sug = [];
+                for (var i = 0; i < res.data.length; i++) {
+                  sug.push(
+                      res.data[i].title,
+                  )
+                }
+                _this.triggerEvent('SearchList',sug);
+              },
+            });
+          }else{ _this.triggerEvent('SearchList',[]);}
+          // 调用关键词提示接口
+        }, 700),
+        inputVal:e.detail.value,
+      })
+    },
+    toSearch:function(e){
+      var value = this.data.inputVal;
+      this.triggerEvent('ChildInputValue',value);
+      },
+   }
+})

+ 4 - 0
apps/4Dkankan/components/searchbar/searchbar.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 6 - 0
apps/4Dkankan/components/searchbar/searchbar.wxml

@@ -0,0 +1,6 @@
+  <view class="cu-bar search bg-white">
+    <view class="search-form round">
+      <text class="cuIcon-search"></text>
+      <input type="text" disabled="{{disabled}}" value="{{inputVal}}"  bindinput='getsuggest' bindconfirm="toSearch" placeholder="{{placeholder}}" confirm-type="search"></input>
+    </view>
+  </view>

+ 7 - 0
apps/4Dkankan/components/searchbar/searchbar.wxss

@@ -0,0 +1,7 @@
+.box {
+  padding: 16rpx 40rpx 0;
+}
+
+.cu-bar {
+  margin-top: 20rpx;
+}

+ 141 - 0
apps/4Dkankan/components/select-form/select-form.js

@@ -0,0 +1,141 @@
+// components/select-form/select-form.js
+const app = getApp();
+Component({
+
+  /**
+   * 页面的初始数据
+   */
+  properties:{
+    selectType:{
+      type: String,
+      value: 'regions',
+    },
+    formselects:{
+      type:JSON,
+      value:{},
+      observer: function (newVal, oldVal, changedPath) {
+        // 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串
+        // 通常 newVal 就是新设置的数据, oldVal 是旧数据
+        this.setData({
+          formselects:newVal
+        })
+      },
+    },
+    clearfilters: {
+      type:Boolean,
+      value:false,
+      observer: function (newVal, oldVal, changedPath) {
+          this.setData({
+            filters:{},
+          })
+      },
+    },
+    lastConditions:{
+      type:Object,
+      value: {},
+    }
+  },
+  data: {
+    show_regions:true,
+    ActiveColor:true,
+    price:0,
+    start: 0,
+    end: 1000,
+    filters:{}
+  },
+  methods:{
+    RigionsClick(e){
+      this.setData({
+        show_regions:true,
+        ActiveColor:true
+      })
+    },
+    SubwayClick(e){
+      this.setData({
+        show_regions:false,
+        ActiveColor:false
+      })
+    },
+    RigionsselectClick(e){
+      console.log(e)
+      var dataset = e.currentTarget.dataset;
+      var value = dataset['value'];
+      var key = dataset['key'];
+      var filters = this.data.filters;
+      filters[key] = value;
+      this.setData({
+        filters:filters
+      });
+      this.triggerEvent('SelectEvent',{'key':key,
+      'value':value});
+    },
+    slider2change(e){
+      var money = e.detail.value * 20;
+      var start = (money - 1000>0)?money-1000:0;
+      var end = (money + 1000 >= 7000) ? "不限" : money + 1000;
+      this.setData({
+        price:  money,
+        start:start ,
+        end: end
+      });
+      this.setData({
+          ['filters.price']:money
+      });
+      this.triggerEvent('SelectEvent', {'key':'price','value':[start,end]});
+    },
+  },
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  }
+});

+ 4 - 0
apps/4Dkankan/components/select-form/select-form.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 63 - 0
apps/4Dkankan/components/select-form/select-form.wxml

@@ -0,0 +1,63 @@
+<!--components/select-form/select-form.wxml-->
+<view class="origin" wx-if="{{selectType=='regions'}}">
+    <view class="left-select">
+        <view class="lect-item {{ActiveColor?'active-color':''}}" bindtap='RigionsClick'>区域</view>
+    </view>
+    <view class="left-right">
+        <scroll-view scroll-y style="height: 400rpx;">
+            <view bindtap='RigionsselectClick' data-key="region" data-value="{{item}}" wx-if="{{show_regions}}"
+                  class="lect-item" wx:for="{{formselects.regions}}" wx:key wx:for-item="item">
+                {{item}}
+            </view>
+            <view  style="background: {{color}};opacity: 0.8;color: white"
+                   wx-if="{{!show_regions}}"
+                  class="lect-item" wx:for="{{formselects.subway}}" wx:key wx:for-item="color">
+                <view style="display: flex;flex-direction: row;justify-content: center"
+                      data-key="subway" data-value="{{index}}"
+                      bindtap='RigionsselectClick'
+                >
+                    <image src="/image/icon/subway.png" style="width: 40rpx;height: 40rpx"
+                    class="cell-center"></image>
+                    <view style="margin-left: 20rpx">
+                        {{index}}{{item}}
+                    </view>
+                </view>
+
+            </view>
+        </scroll-view>
+    </view>
+</view>
+<view class="house-type" wx-if="{{selectType=='house-type'}}">
+    <scroll-view scroll-y style="height: 400rpx;">
+        <view class="lect-item" bindtap='RigionsselectClick' data-key="house_type" data-value="{{item[0]}}"
+              wx:for="{{formselects.house_type}}" wx:key="item" wx:for-item="item">
+            {{item[1]}}
+        </view>
+    </scroll-view>
+</view>
+<view class="apartment" wx-if="{{selectType=='money'}}">
+    <view class="solid bg-white text-xxl padding">
+        <view class="section section_gap">
+            <view class='price-info'>
+                <text class="cuIcon-moneybag lg text-pink icon-price"></text>
+                <view class="text-pink margin-left-xs">{{price}}</view>
+            </view>
+            <view class="body-view">
+                <slider bindchange="slider2change" step="10" max="300"/>
+            </view>
+            <view class='price-auto'>{{start}} - {{end}}</view>
+        </view>
+    </view>
+</view>
+<view class="apartment" wx-if="{{selectType=='apartment'}}">
+    <scroll-view scroll-y style="height: 400rpx;">
+        <view class="lect-item" bindtap='RigionsselectClick' data-key="apartment" data-value="{{item[0]}}"
+              wx:for="{{formselects.apartment}}" wx:key="item" wx:for-item="item">
+            {{item[1]}}
+        </view>
+    </scroll-view>
+</view>
+
+
+
+

+ 52 - 0
apps/4Dkankan/components/select-form/select-form.wxss

@@ -0,0 +1,52 @@
+/* components/select-form/select-form.wxss */
+.selectbox{
+    background: url('/image/img-draw/search.png') no-repeat center center;filter:alpha(opacity=70);
+    background-size: cover;
+    background:rgba(0,0,0,0.5)
+}
+.origin{
+    display: flex;
+    flex-direction: row;
+}
+.cell-center{
+    align-self: center;
+    display: table-cell;
+    vertical-align: middle;
+}
+.left-select,.left-right{
+    width: 50%;
+    display: flex;
+    flex-direction: column;
+}
+.left-right{
+    background: #F2F2F2;
+}
+.lect-item{
+    border-bottom: 1px solid #EEE0E5;
+    height: 80rpx;
+    line-height: 80rpx;
+    text-align: center;
+}
+.active-color{
+    color: #7CCD7C;
+}
+
+.price-info,.price-auto{
+    display: flex;
+    color: #e54d42;
+    justify-content: center;
+}
+
+.price-info{
+    font-size: 40rpx;
+}
+.price-auto{
+    color: grey;
+    font-size: 15px;
+}
+.icon-price{
+    font-size: 20px;
+}
+
+@import "../../colorui/main.wxss";
+@import "../../colorui/icon.wxss";

+ 64 - 0
apps/4Dkankan/components/swiper/swiper.js

@@ -0,0 +1,64 @@
+// components/swiper/swiper.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  options: {
+    addGlobalClass: true,
+  },
+  externalClasses: ['col-class'],
+  properties: {
+    "swiper_style":{
+      type:String,
+      value: 'card-swiper',
+    },
+    "imgs_list": {
+      type: Object,
+      value: {}
+    }
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    cardCur: 0,
+    DotStyle: 0,
+    duration: 0,
+    interval: 0,
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    HandleClick(res){
+      var navigate = res.currentTarget.dataset.navigate;
+      if(!navigate){
+        return
+      }
+      wx.navigateTo({
+        url: '/pages/web/web?navigate='+navigate
+      })
+    },
+    intervalChange(e) {
+      this.setData({
+        interval: e.detail.value
+      })
+    },
+    durationChange(e) {
+      this.setData({
+        duration: e.detail.value
+      })
+    },
+      DotStyle(e) {
+      this.setData({
+        DotStyle: e.detail.value
+      })
+    },
+    cardSwiper(e) {
+      this.setData({
+        cardCur: e.detail.current
+      })
+    },
+  }
+})

+ 4 - 0
apps/4Dkankan/components/swiper/swiper.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 10 - 0
apps/4Dkankan/components/swiper/swiper.wxml

@@ -0,0 +1,10 @@
+
+  <swiper wx-if="{{swiper_style == 'card-swiper'}}" class="{{swiper_style}} square-dot" indicator-dots="true" circular="true" autoplay="true" interval="5000" duration="500" bindchange="cardSwiper" indicator-color="#8799a3" ndicator-active-color="#0081ff">
+    <swiper-item wx:for="{{imgs_list}}" wx:key class="{{cardCur==index?'cur':''}}">
+      <view class="swiper-item" bindtap="HandleClick" data-navigate="{{item.navigate}}">
+        <image src="{{item.url}}" mode="aspectFill" lazy-load></image>
+<!--        <video src="{{item.url}}" autoplay loop muted show-play-btn="{{false}}" controls="{{false}}" objectFit="cover" wx:if="{{item.type=='video'}}"></video>-->
+        {{imgs_list}}
+      </view>
+    </swiper-item>
+  </swiper>

+ 16 - 0
apps/4Dkankan/components/swiper/swiper.wxss

@@ -0,0 +1,16 @@
+.tower-swiper .tower-item {
+  transform: scale(calc(0.5 + var(--index) / 10));
+  margin-left: calc(var(--left) * 100rpx - 150rpx);
+  z-index: var(--index);
+}
+.card-swiper {
+  height:250rpx !important;
+}
+
+.screen-swiper{
+  position: relative;
+  top: 0;
+  left: 0;
+  right: 0;
+}
+

+ 107 - 0
apps/4Dkankan/components/tab-bar/tab-bar.js

@@ -0,0 +1,107 @@
+import eventEmitter from '../../utils/eventEmitter'
+import { isPhoneX } from './../../utils/tools'
+let app = getApp()
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    inactiveColor: {
+      type: String,
+      value: '#79868F'
+    },
+    activeColor: {
+      type: String,
+      value: '#1FE4DC'
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    active: 0,
+    tabItems: [
+      {
+        title: '首页',
+        url: '/pages/index/index',
+        icon: {
+          normal: '/image/4Dage/tab/home.svg',
+          active: '/image/4Dage/tab/home-active.svg'
+        }
+      },
+      {
+        title: '消息',
+        url: '/pages/chat-list/chat-list',
+        info: app.globalData.unViewMsg,
+        icon: {
+          normal: '/image/4Dage/tab/message.svg',
+          active: '/image/4Dage/tab/message-active.svg'
+        }
+      },
+      {
+        title: '我的',
+        url: '/pages/my/my',
+        icon: {
+          normal: '/image/4Dage/tab/my.svg',
+          active: '/image/4Dage/tab/my-active.svg'
+        }
+      },
+    ],
+    isPhoneX: false
+  },
+
+  attached: function() {
+  },
+  pageLifetimes: {
+    show () {
+      const currentRoute = getCurrentPages()[0].route
+      const { tabItems } = this.data
+      tabItems[1].info = app.globalData.unViewMsg
+      tabItems.forEach((item, index) => {
+        if (item.url.indexOf(currentRoute) !== -1) {
+          this.setData({
+            active: index,
+            tabItems: tabItems,
+          })
+        }
+      })
+      isPhoneX().then(res => {
+        this.setData({
+          isPhoneX: res
+        })
+      })
+      this.init()
+    },
+    hide () {
+      getApp().getIMHandler().removeOnReceiveMessageListener({listener: this.fn})
+    }
+  },
+  
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onChange(e) {
+      const index = e.detail, { tabItems } = this.data
+      wx.switchTab({
+        url: tabItems[index].url,
+      })
+    },
+    init () {
+      this.fn = (msg) => {
+        
+        const { tabItems } = this.data
+        app.globalData.unViewMsg = app.globalData.unViewMsg ? app.globalData.unViewMsg+1 : 1
+        tabItems[1].info = app.globalData.unViewMsg
+        this.setData({
+          tabItems
+        })
+        app.getNewMessage(msg)
+      }
+      getApp().getIMHandler().setOnReceiveMessageListener({
+        listener: this.fn
+    });
+    }
+  }
+})

+ 8 - 0
apps/4Dkankan/components/tab-bar/tab-bar.json

@@ -0,0 +1,8 @@
+{
+  "component": true,
+  "styleIsolation": "shared",
+  "usingComponents": {
+    "van-tabbar": "/components/vant-ui/tabbar/index",
+    "van-tabbar-item": "/components/vant-ui/tabbar-item/index"
+  }
+}

+ 18 - 0
apps/4Dkankan/components/tab-bar/tab-bar.wxml

@@ -0,0 +1,18 @@
+<van-tabbar class="{{isPhoneX ? 'is-iphoneX' : ''}}" safe-area-inset-bottom="{{true}}" active="{{ active }}" bind:change="onChange" inactive-color="{{inactiveColor}}" active-color="{{activeColor}}">
+  <van-tabbar-item info="{{item.info ? item.info : ''}}" wx:for="{{tabItems}}" wx:key="{{item.title}}" >
+    <image
+      slot="icon"
+      src="{{ item.icon.normal }}"
+      mode="aspectFit"
+      style="width: 42rpx; height: 42rpx;"
+    />
+    <image
+      slot="icon-active"
+      src="{{ item.icon.active }}"
+      mode="aspectFit"
+      style="width: 42rpx; height: 42rpx;"
+    />
+    {{ item.title }}
+    <!-- <view style="height: 60px"></view> -->
+  </van-tabbar-item>
+</van-tabbar>

+ 9 - 0
apps/4Dkankan/components/tab-bar/tab-bar.wxss

@@ -0,0 +1,9 @@
+.is-iphoneX .van-tabbar--safe {
+  padding-bottom: 50rpx;
+  height: 150rpx;
+}
+
+.van-tabbar {
+  min-height: 100rpx;
+	height: calc(100rpx + env(safe-area-inset-bottom) / 2);
+}

+ 29 - 0
apps/4Dkankan/components/tabs-house-list/tabs-house-list.js

@@ -0,0 +1,29 @@
+// components/house-item/house-item.js
+Component({
+  options: {
+    styleIsolation: 'shared'
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    tabs: Array
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    active: 0
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onChange (e) {
+      const index = e.detail.index
+      this.triggerEvent('change', this.properties.tabs[index])
+    }
+  }
+})

+ 9 - 0
apps/4Dkankan/components/tabs-house-list/tabs-house-list.json

@@ -0,0 +1,9 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-tab": "/components/vant-ui/tab/index",
+    "van-tabs": "/components/vant-ui/tabs/index",
+    "house-item": "/components/house-item/house-item",
+    "no-more-bar": "/components/no-more-bar/no-more-bar"
+  }
+}

+ 11 - 0
apps/4Dkankan/components/tabs-house-list/tabs-house-list.wxml

@@ -0,0 +1,11 @@
+<van-tabs active="{{ active }}" line-width="20" color="#1FE4DC" bind:change="onChange">
+  <van-tab title="{{ tabs.name }}" wx:for="{{tabs}}" wx:for-item="tabs" wx:key="index">
+    <view >
+      <view class="no-data" wx:if="{{tabs.house_list.length === 0}}">暂无数据</view>
+      <view class="house-list" wx:else>
+        <house-item wx:for="{{ tabs.house_list }}" house="{{ item }}" wx:key="id" />
+      </view>
+      <no-more-bar wx:if="{{tabs.house_list.length !== 0}}" />
+    </view>
+  </van-tab>
+</van-tabs>

+ 18 - 0
apps/4Dkankan/components/tabs-house-list/tabs-house-list.wxss

@@ -0,0 +1,18 @@
+.van-tabs__line {
+  bottom: 9px;
+}
+.van-tab--active {
+  color: #1FE4DC;
+}
+.van-tab {
+  font-size: 30rpx;
+}
+
+.no-data {
+  color: #A3A4A5;
+  text-align: center;
+  padding-top: 50px;
+}
+.house-list {
+  padding: 0 46rpx;
+}

+ 85 - 0
apps/4Dkankan/components/trtc-room/common/constants.js

@@ -0,0 +1,85 @@
+export const EVENT = {
+  LOCAL_JOIN: 'LOCAL_JOIN', // 本地进房成功
+  LOCAL_LEAVE: 'LOCAL_LEAVE', // 本地退房
+  REMOTE_USER_JOIN: 'REMOTE_USER_JOIN', // 远端用户进房
+  REMOTE_USER_LEAVE: 'REMOTE_USER_LEAVE', // 远端用户退房
+  REMOTE_VIDEO_ADD: 'REMOTE_VIDEO_ADD', // 远端视频流添加事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_VIDEO_REMOVE: 'REMOTE_VIDEO_REMOVE', // 远端视频流移出事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_AUDIO_ADD: 'REMOTE_AUDIO_ADD', // 远端音频流添加事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_AUDIO_REMOVE: 'REMOTE_AUDIO_REMOVE', // 远端音频流移除事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_STATE_UPDATE: 'REMOTE_STATE_UPDATE', // 远端用户播放状态变更
+  LOCAL_NET_STATE_UPDATE: 'LOCAL_NET_STATE_UPDATE', // 本地推流网络状态变更
+  REMOTE_NET_STATE_UPDATE: 'REMOTE_NET_STATE_UPDATE', // 远端用户网络状态变更
+  LOCAL_AUDIO_VOLUME_UPDATE: 'LOCAL_AUDIO_VOLUME_UPDATE', // 本地音量变更
+  REMOTE_AUDIO_VOLUME_UPDATE: 'REMOTE_AUDIO_VOLUME_UPDATE', // 远端用户音量变更
+  VIDEO_FULLSCREEN_UPDATE: 'VIDEO_FULLSCREEN_UPDATE', // 调用 player requestFullScreen 或者 exitFullScreen 后触发
+  BGM_PLAY_START: 'BGM_PLAY_START', // 调用 LivePusherContext.playBGM(Object object)
+  BGM_PLAY_FAIL: 'BGM_PLAY_FAIL', //
+  BGM_PLAY_PROGRESS: 'BGM_PLAY_PROGRESS', // bgm 播放时间戳变更
+  BGM_PLAY_COMPLETE: 'BGM_PLAY_COMPLETE', // bgm 播放结束 或者 调用 LivePusherContext.stopBGM() ?
+  ERROR: 'ERROR', // pusher 出现错误
+  IM_READY: 'IM_READY', // IM SDK 可用
+  IM_MESSAGE_RECEIVED: 'IM_MESSAGE_RECEIVED', // 收到IM 消息
+  IM_NOT_READY: 'IM_NOT_READY', // IM SDK 不可用
+  IM_KICKED_OUT: 'IM_KICKED_OUT', // IM SDK 下线
+  IM_ERROR: 'IM_ERROR', // IM SDK 下线
+}
+
+export const DEFAULT_COMPONENT_CONFIG = {
+  sdkAppID: '',
+  userID: '',
+  userSig: '',
+  template: '',
+  debugMode: false, // 是否开启调试模式
+  enableIM: false, // 是否开启 IM
+}
+
+export const DEFAULT_PUSHER_CONFIG = {
+  url: '',
+  mode: 'RTC', // RTC:实时通话(trtc sdk) live:直播模式(liteav sdk)
+  autopush: false, // 自动推送
+  enableCamera: false, // 是否开启摄像头
+  enableMic: false, // 是否开启麦克风
+  enableAgc: false, // 是否开启音频自动增益
+  enableAns: false, // 是否开启音频噪声抑制
+  enableEarMonitor: false, // 是否开启耳返(目前只在iOS平台有效)
+  enableAutoFocus: true, // 是否自动对焦
+  enableZoom: false, // 是否支持调整焦距
+  minBitrate: 600, // 最小码率
+  maxBitrate: 900, // 最大码率
+  videoWidth: 360, // 视频宽(若设置了视频宽高就会忽略aspect)
+  videoHeight: 640, // 视频高(若设置了视频宽高就会忽略aspect)
+  beautyLevel: 0, // 美颜,取值范围 0-9 ,0 表示关闭
+  whitenessLevel: 0, // 美白,取值范围 0-9 ,0 表示关闭
+  videoOrientation: 'vertical', // vertical horizontal
+  videoAspect: '9:16', // 宽高比,可选值有 3:4,9:16
+  frontCamera: 'front', // 前置或后置摄像头,可选值:front,back
+  enableRemoteMirror: false, // 设置推流画面是否镜像,产生的效果会表现在 live-player
+  localMirror: 'auto', // auto:前置摄像头镜像,后置摄像头不镜像(系统相机的表现)enable:前置摄像头和后置摄像头都镜像 disable: 前置摄像头和后置摄像头都不镜像
+  enableBackgroundMute: false, // 进入后台时是否静音
+  audioQuality: 'high', // 高音质(48KHz)或低音质(16KHz),可选值:high,low
+  audioVolumeType: 'voicecall', // 声音类型 可选值: media: 媒体音量,voicecall: 通话音量
+  audioReverbType: 0, // 音频混响类型 0: 关闭 1: KTV 2: 小房间 3:大会堂 4:低沉 5:洪亮 6:金属声 7:磁性
+  // waitingImage: 'https://web-player-1252463788.cos.ap-shanghai.myqcloud.com/demo/1px.png', // 当微信切到后台时的垫片图片 trtc暂不支持
+  waitingImage: 'https://mc.qcloudimg.com/static/img/daeed8616ac5df256c0591c22a65c4d3/pause_publish.jpg', // 当微信切到后台时的垫片图片 trtc暂不支持
+  waitingImageHash: '',
+  beautyStyle: 'smooth', // 美颜类型,取值有:smooth: 光滑 、nature: 自然
+  filter: '', // standard: 标准 pink: 粉嫩 nostalgia: 怀旧 blues: 蓝调 romantic: 浪漫  cool: 清凉 fresher: 清新 solor: 日系 aestheticism: 唯美 whitening:美白 cerisered: 樱红
+}
+
+export const DEFAULT_PLAYER_CONFIG = {
+  src: '',
+  mode: 'RTC',
+  autoplay: true, // 7.0.9 必须设置为true,否则 Android 有概率调用play()失败
+  muteAudio: true, // 默认不拉取音频,需要手动订阅,如果要快速播放,需要设置false
+  muteVideo: true, // 默认不拉取视频,需要手动订阅,如果要快速播放,需要设置false
+  orientation: 'vertical', // 画面方向 vertical horizontal
+  objectFit: 'fillCrop', // 填充模式,可选值有 contain,fillCrop
+  enableBackgroundMute: false, // 进入后台时是否静音(已废弃,默认退台静音)
+  minCache: 1, // 最小缓冲区,单位s(RTC 模式推荐 0.2s)
+  maxCache: 2, // 最大缓冲区,单位s(RTC 模式推荐 0.8s)
+  soundMode: 'speaker', // 声音输出方式 ear speaker
+  enableRecvMessage: 'false', // 是否接收SEI消息
+  autoPauseIfNavigate: true, // 当跳转到其它小程序页面时,是否自动暂停本页面的实时音视频播放
+  autoPauseIfOpenNative: true, // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
+}

+ 346 - 0
apps/4Dkankan/components/trtc-room/controller/user-controller.js

@@ -0,0 +1,346 @@
+import Event from '../utils/event.js'
+import User from '../model/user.js'
+import Stream from '../model/stream.js'
+import { EVENT } from '../common/constants.js'
+
+const TAG_NAME = 'UserController'
+/**
+ * 通讯成员管理
+ */
+class UserController {
+  constructor(componentContext) {
+    // userMap 用于存储完整的数据结构
+    this.userMap = new Map()
+    // userList 用于存储简化的用户数据 Object,包括 {userID hasMainAudio hasMainVideo hasAuxAudio hasAuxVideo}
+    this.userList = []
+    // streamList 存储steam 对象列表,用于 trtc-room 渲染 player
+    this.streamList = []
+    this._emitter = new Event()
+    this.componentContext = componentContext
+    this.isNewVersion = componentContext.isNewVersion
+  }
+  userEventHandler(event) {
+    const code = event.detail.code
+    let data
+    if (event.detail.message && typeof event.detail.message === 'string') {
+      try {
+        data = JSON.parse(event.detail.message)
+      } catch (exception) {
+        console.warn(TAG_NAME, 'userEventHandler 数据格式错误', exception)
+        return false
+      }
+    } else {
+      console.warn(TAG_NAME, 'userEventHandler 数据格式错误')
+      return false
+    }
+    switch (code) {
+      case 1020:
+        // console.log(TAG_NAME, '远端用户全量列表更新:', code)
+        if (!this.isNewVersion) {
+          // TODO 旧版SDK处理逻辑,返回全量的用户列表,需要对userList 进行前后对比,筛选出新增用户,暂不实现
+        }
+        break
+      case 1031:
+        // console.log(TAG_NAME, '远端用户进房通知:', code)
+        // 1031 有新用户
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11"
+        //          }
+        //      ]
+        // }
+        this.addUser(data)
+        break
+      case 1032:
+        // console.log(TAG_NAME, '远端用户退房通知:', code)
+        // 1032 有用户退出
+        this.removeUser(data)
+        break
+      case 1033:
+        // console.log(TAG_NAME, '远端用户视频状态位变化通知:', code)
+        // 1033 用户视频状态变化,新增stream或者更新stream 状态
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11",
+        //              "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
+        //              "streamtype":"main",
+        //              "hasvideo":true
+        //          }
+        //      ]
+        // }
+        this.updateUserVideo(data)
+        break
+      case 1034:
+        // console.log(TAG_NAME, '远端用户音频状态位变化通知:', code)
+        // 1034 用户音频状态变化
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11",
+        //              "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
+        //              "hasaudio":false
+        //          }
+        //      ]
+        // }
+        this.updateUserAudio(data)
+        break
+    }
+  }
+  /**
+   * 处理用户进房事件
+   * @param {Object} data pusher 下发的数据
+   */
+  addUser(data) {
+    // console.log(TAG_NAME, 'addUser', data)
+    const incomingUserList = data.userlist
+    const userMap = this.userMap
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        // 已经在 map 中的用户
+        let user = this.getUser(userID)
+        if (!user) {
+          // 新增用户
+          user = new User({ userID: userID })
+          this.userList.push({
+            userID: userID,
+          })
+        }
+        userMap.set(userID, user)
+        this._emitter.emit(EVENT.REMOTE_USER_JOIN, { userID: userID, userList: this.userList })
+        // console.log(TAG_NAME, 'addUser', item, userMap.get(userID), this.userMap)
+      })
+    }
+  }
+  /**
+   * 处理用户退房事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  removeUser(data) {
+    // console.log(TAG_NAME, 'removeUser', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        let user = this.getUser(userID)
+        // 偶现SDK触发退房事件前没有触发进房事件
+        if (!user || !user.streams) {
+          return
+        }
+        // 从userList 里删除指定的用户和 stream
+        this._removeUserAndStream(userID)
+        // 重置
+        user.streams['main'] && user.streams['main'].reset()
+        user.streams['aux'] && user.streams['aux'].reset()
+        // 用户退出,释放引用,外部调用该 user 所有stream 的 playerContext.stop() 方法停止播放
+        // TODO 触发时机提前了,方便外部用户做出处理,时机仍需进一步验证
+        this._emitter.emit(EVENT.REMOTE_USER_LEAVE, { userID: userID, userList: this.userList, streamList: this.streamList })
+        user = undefined
+        this.userMap.delete(userID)
+        // console.log(TAG_NAME, 'removeUser', this.userMap)
+      })
+    }
+  }
+  /**
+   * 处理用户视频通知事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  updateUserVideo(data) {
+    console.log(TAG_NAME, 'updateUserVideo', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        const streamType = item.streamtype
+        const streamID = userID + '_' + streamType
+        const hasVideo = item.hasvideo
+        const src = item.playurl
+        const user = this.getUser(userID)
+        // 更新指定用户的属性
+        if (user) {
+          // 查找对应的 stream
+          let stream = user.streams[streamType]
+          console.log(TAG_NAME, 'updateUserVideo start', user, streamType, stream)
+          // 常规逻辑
+          // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户(有音频或视频)的 stream
+          if (!stream) {
+            // 不在 user streams 里,需要新建
+            user.streams[streamType] = stream = new Stream({ userID, streamID, hasVideo, src, streamType })
+            this._addStream(stream)
+          } else {
+            // 更新 stream 属性
+            stream.setProperty({ hasVideo })
+            if (!hasVideo && !stream.hasAudio) {
+              this._removeStream(stream)
+            }
+            // or
+            // if (hasVideo) {
+            //   stream.setProperty({ hasVideo })
+            // } else if (!stream.hasAudio) {
+            //   // hasVideo == false && hasAudio == false
+            //   this._removeStream(stream)
+            // }
+          }
+          // 特殊逻辑
+          if (streamType === 'aux') {
+            if (hasVideo) {
+              // 辅流需要修改填充模式
+              stream.objectFit = 'contain'
+              this._addStream(stream)
+            } else {
+              // 如果是辅流要移除该 stream,否则需要移除 player
+              this._removeStream(stream)
+            }
+          }
+          // 更新所属user 的 hasXxx 值
+          this.userList.find((item)=>{
+            if (item.userID === userID) {
+              item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Video`] = hasVideo
+              return true
+            }
+          })
+          console.log(TAG_NAME, 'updateUserVideo end', user, streamType, stream)
+          const eventName = hasVideo ? EVENT.REMOTE_VIDEO_ADD : EVENT.REMOTE_VIDEO_REMOVE
+          this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
+          // console.log(TAG_NAME, 'updateUserVideo', user, stream, this.userMap)
+        }
+      })
+    }
+  }
+  /**
+   * 处理用户音频通知事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  updateUserAudio(data) {
+    // console.log(TAG_NAME, 'updateUserAudio', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        // 音频只跟着 stream main ,这里只修改 main
+        const streamType = 'main'
+        const streamID = userID + '_' + streamType
+        const hasAudio = item.hasaudio
+        const src = item.playurl
+        const user = this.getUser(userID)
+        if (user) {
+          let stream = user.streams[streamType]
+          // if (!stream) {
+          //   user.streams[streamType] = stream = new Stream({ streamType: streamType })
+          //   this._addStream(stream)
+          // }
+
+          // 常规逻辑
+          // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户的 stream
+          if (!stream) {
+            // 不在 user streams 里,需要新建
+            user.streams[streamType] = stream = new Stream({ userID, streamID, hasAudio, src, streamType })
+            this._addStream(stream)
+          } else {
+            // 更新 stream 属性
+            stream.setProperty({ hasAudio })
+            if (!hasAudio && !stream.hasVideo) {
+              this._removeStream(stream)
+            }
+            // or
+            // if (hasAudio) {
+            //   stream.setProperty({ hasAudio })
+            // } else if (!stream.hasVideo) {
+            // // hasVideo == false && hasAudio == false
+            //   this._removeStream(stream)
+            // }
+          }
+
+          // stream.userID = userID
+          // stream.streamID = userID + '_' + streamType
+          // stream.hasAudio = hasAudio
+          // stream.src = src
+          // 更新所属 user 的 hasXxx 值
+          this.userList.find((item)=>{
+            if (item.userID === userID) {
+              item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Audio`] = hasAudio
+              return true
+            }
+          })
+          const eventName = hasAudio ? EVENT.REMOTE_AUDIO_ADD : EVENT.REMOTE_AUDIO_REMOVE
+          this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
+          // console.log(TAG_NAME, 'updateUserAudio', user, stream, this.userMap)
+        }
+      })
+    }
+  }
+  /**
+   *
+   * @param {String} userID 用户ID
+   * @returns {Object}
+   */
+  getUser(userID) {
+    return this.userMap.get(userID)
+  }
+  getStream({ userID, streamType }) {
+    const user = this.userMap.get(userID)
+    if (user) {
+      return user.streams[streamType]
+    }
+    return undefined
+  }
+  getUserList() {
+    return this.userList
+  }
+  getStreamList() {
+    return this.streamList
+  }
+  /**
+   * 重置所有user 和 steam
+   * @returns {Object}
+   */
+  reset() {
+    this.streamList.forEach((item)=>{
+      item.reset()
+    })
+    this.streamList = []
+    this.userList = []
+    this.userMap.clear()
+    return {
+      userList: this.userList,
+      streamList: this.streamList,
+    }
+  }
+  on(eventCode, handler, context) {
+    this._emitter.on(eventCode, handler, context)
+  }
+  off(eventCode, handler) {
+    this._emitter.off(eventCode, handler)
+  }
+  /**
+   * 删除用户和所有的 stream
+   * @param {String} userID 用户ID
+   */
+  _removeUserAndStream(userID) {
+    this.streamList = this.streamList.filter((item)=>{
+      return item.userID !== userID && item.userID !== ''
+    })
+    this.userList = this.userList.filter((item)=>{
+      return item.userID !== userID
+    })
+  }
+  _addStream(stream) {
+    if (!this.streamList.includes(stream)) {
+      this.streamList.push(stream)
+    }
+  }
+  _removeStream(stream) {
+    this.streamList = this.streamList.filter((item)=>{
+      if (item.userID === stream.userID && item.streamType === stream.streamType) {
+        return false
+      }
+      return true
+    })
+    const user = this.getUser(stream.userID)
+    user.streams[stream.streamType] = undefined
+  }
+}
+
+export default UserController

File diff suppressed because it is too large
+ 14 - 0
apps/4Dkankan/components/trtc-room/libs/mta_analysis.js


File diff suppressed because it is too large
+ 1 - 0
apps/4Dkankan/components/trtc-room/libs/tim-wx.js


+ 33 - 0
apps/4Dkankan/components/trtc-room/model/pusher.js

@@ -0,0 +1,33 @@
+import { DEFAULT_PUSHER_CONFIG } from '../common/constants.js'
+
+class Pusher {
+  constructor(options) {
+    Object.assign(this, DEFAULT_PUSHER_CONFIG, {
+      isVisible: true, // 手Q初始化时不能隐藏 puser和player 否则黑屏
+    }, options)
+  }
+  /**
+   * 通过wx.createLivePusherContext 获取<live-pusher> context
+   * @param {Object} context 组件上下文
+   * @returns {Object} livepusher context
+   */
+  getPusherContext(context) {
+    if (!this.pusherContext) {
+      this.pusherContext = wx.createLivePusherContext(context)
+    }
+    return this.pusherContext
+  }
+  reset() {
+    console.log('Pusher reset', this.pusherContext)
+    if (this.pusherContext) {
+      console.log('Pusher pusherContext.stop()')
+      this.pusherContext.stop()
+      this.pusherContext = null
+    }
+    Object.assign(this, DEFAULT_PUSHER_CONFIG, {
+      isVisible: true,
+    })
+  }
+}
+
+export default Pusher

+ 38 - 0
apps/4Dkankan/components/trtc-room/model/stream.js

@@ -0,0 +1,38 @@
+// 一个stream 对应一个 player
+import { DEFAULT_PLAYER_CONFIG } from '../common/constants.js'
+
+class Stream {
+  constructor(options) {
+    Object.assign(this, DEFAULT_PLAYER_CONFIG, {
+      userID: '', // 该stream 关联的userID
+      streamType: '', // stream 类型 [main small] aux
+      streamID: '', // userID + '_' + streamType
+      isVisible: true, // 手Q初始化时不能隐藏 puser和player 否则黑屏。iOS 微信初始化时不能隐藏,否则同层渲染失败,player会置顶
+      hasVideo: false,
+      hasAudio: false,
+      volume: 0, // 音量大小 0~100
+      playerContext: undefined, // playerContext 依赖component context来获取,目前只能在渲染后获取
+    }, options)
+  }
+  setProperty(options) {
+    Object.assign(this, options)
+  }
+  reset() {
+    if (this.playerContext) {
+      this.playerContext.stop()
+      this.playerContext = undefined
+    }
+    Object.assign(this, DEFAULT_PLAYER_CONFIG, {
+      userID: '', // 该stream 关联的userID
+      streamType: '', // stream 类型 [main small] aux
+      streamID: '',
+      isVisible: true,
+      hasVideo: false,
+      hasAudio: false,
+      volume: 0, // 音量大小 0~100
+      playerContext: undefined,
+    })
+  }
+}
+
+export default Stream

+ 0 - 0
apps/4Dkankan/components/trtc-room/model/user.js


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