瀏覽代碼

fix:新小程序

chenzhen2 1 年之前
當前提交
3925e73ed8
共有 100 個文件被更改,包括 28170 次插入0 次删除
  1. 9 0
      .editorconfig
  2. 1 0
      .eslintignore
  3. 30 0
      .eslintrc.js
  4. 4 0
      .gitignore
  5. 3 0
      .prettierrc
  6. 1 0
      .wepycache
  7. 4 0
      .wepyignore
  8. 7214 0
      package-lock.json
  9. 56 0
      package.json
  10. 44 0
      project.config.json
  11. 43 0
      src/api/company.js
  12. 9 0
      src/api/home.js
  13. 223 0
      src/api/meetingroom.js
  14. 10 0
      src/api/user.js
  15. 77 0
      src/app.wpy
  16. 53 0
      src/common/errorRequest.js
  17. 5 0
      src/common/eventHub.js
  18. 40 0
      src/common/loadFont.js
  19. 122 0
      src/common/request.js
  20. 1040 0
      src/common/utils.js
  21. 65 0
      src/components/common/FollowBtn.wpy
  22. 91 0
      src/components/common/action-sheet-list.wpy
  23. 119 0
      src/components/common/action-sheet.wpy
  24. 115 0
      src/components/common/carousel.wpy
  25. 51 0
      src/components/common/common-button.wpy
  26. 89 0
      src/components/common/data-none.wpy
  27. 76 0
      src/components/common/dot.wpy
  28. 96 0
      src/components/common/input-with-clear.wpy
  29. 60 0
      src/components/common/page-bottom-button.wpy
  30. 115 0
      src/components/common/page-pull-down-animation.wpy
  31. 75 0
      src/components/common/page-title-view-scroll.wpy
  32. 120 0
      src/components/common/page-top-bar-custom.wpy
  33. 132 0
      src/components/common/page-top-bar.wpy
  34. 144 0
      src/components/common/panel-can-close.wpy
  35. 314 0
      src/components/common/panel-step.wpy
  36. 160 0
      src/components/common/scroller-x.wpy
  37. 65 0
      src/components/common/select-switch.wpy
  38. 235 0
      src/components/common/slider-panel-v2.wpy
  39. 278 0
      src/components/common/slider-panel.wpy
  40. 60 0
      src/components/common/switch.wpy
  41. 83 0
      src/components/common/track-message.wpy
  42. 77 0
      src/components/counter.wpy
  43. 41 0
      src/components/group.wpy
  44. 28 0
      src/components/groupitem.wpy
  45. 48 0
      src/components/list.wpy
  46. 40 0
      src/components/panel.wpy
  47. 58 0
      src/components/wepy-list.wpy
  48. 54 0
      src/config/base.js
  49. 20 0
      src/config/demo.js
  50. 21 0
      src/config/dev.js
  51. 34 0
      src/config/index.js
  52. 58 0
      src/config/prod.js
  53. 21 0
      src/config/test.js
  54. 19 0
      src/mixins/test.js
  55. 150 0
      src/packagesEnv/api/intelligentControl.js
  56. 508 0
      src/packagesEnv/api/officehome.js
  57. 335 0
      src/packagesEnv/api/portrait.js
  58. 301 0
      src/packagesEnv/pages/common.js
  59. 1227 0
      src/packagesEnv/pages/intelligentControl/components/controlpanel/controlpanel.wpy
  60. 101 0
      src/packagesEnv/pages/intelligentControl/components/floor/floor.wpy
  61. 147 0
      src/packagesEnv/pages/intelligentControl/components/location/location.wpy
  62. 313 0
      src/packagesEnv/pages/intelligentControl/components/temperturecontrolpanel/temperturecontrolpanel.wpy
  63. 101 0
      src/packagesEnv/pages/intelligentControl/components/user-space-list/user-space-list.wpy
  64. 612 0
      src/packagesEnv/pages/intelligentControl/components/workovertime/workovertime.wpy
  65. 830 0
      src/packagesEnv/pages/intelligentControl/home.wpy
  66. 924 0
      src/packagesEnv/pages/intelligentControl/home2.wpy
  67. 142 0
      src/packagesEnv/pages/intelligentControl/meetingroomlist/meetingroomlist.wpy
  68. 305 0
      src/packagesEnv/pages/intelligentControl/search/search.wpy
  69. 168 0
      src/packagesEnv/pages/intelligentControl/service/location.js
  70. 68 0
      src/packagesEnv/pages/intelligentControl/service/search.js
  71. 476 0
      src/packagesEnv/pages/officehome/Curtain/curtain-card.wpy
  72. 247 0
      src/packagesEnv/pages/officehome/Curtain/curtainPop.wpy
  73. 176 0
      src/packagesEnv/pages/officehome/Curtain/curtainSlider.wpy
  74. 349 0
      src/packagesEnv/pages/officehome/FloorHeating/floorHeating.wpy
  75. 111 0
      src/packagesEnv/pages/officehome/FloorHeating/floorPop.wpy
  76. 178 0
      src/packagesEnv/pages/officehome/comComponents/CardSlider.wpy
  77. 161 0
      src/packagesEnv/pages/officehome/comComponents/FloorfeatSlider.wpy
  78. 158 0
      src/packagesEnv/pages/officehome/comComponents/Slider.wpy
  79. 112 0
      src/packagesEnv/pages/officehome/comComponents/Switch.wpy
  80. 96 0
      src/packagesEnv/pages/officehome/comComponents/SwitchLR.wpy
  81. 1219 0
      src/packagesEnv/pages/officehome/components/air-adjust-hand.wpy
  82. 644 0
      src/packagesEnv/pages/officehome/components/air-adjust.wpy
  83. 258 0
      src/packagesEnv/pages/officehome/components/air-feedback.wpy
  84. 174 0
      src/packagesEnv/pages/officehome/components/all-close.wpy
  85. 221 0
      src/packagesEnv/pages/officehome/components/book-equipments.wpy
  86. 160 0
      src/packagesEnv/pages/officehome/components/date-selector.wpy
  87. 66 0
      src/packagesEnv/pages/officehome/components/has-person.wpy
  88. 567 0
      src/packagesEnv/pages/officehome/components/lamp-adjust.wpy
  89. 153 0
      src/packagesEnv/pages/officehome/components/lamp-colour.wpy
  90. 1063 0
      src/packagesEnv/pages/officehome/components/period-time-slider-hand.wpy
  91. 895 0
      src/packagesEnv/pages/officehome/components/period-time-slider.wpy
  92. 321 0
      src/packagesEnv/pages/officehome/components/socket-card.wpy
  93. 139 0
      src/packagesEnv/pages/officehome/components/switch-small.wpy
  94. 150 0
      src/packagesEnv/pages/officehome/components/switch.wpy
  95. 215 0
      src/packagesEnv/pages/officehome/components/voice-adjust.wpy
  96. 219 0
      src/packagesEnv/pages/officehome/components/work-off.wpy
  97. 440 0
      src/packagesEnv/pages/officehome/components/work-scene-hand.wpy
  98. 437 0
      src/packagesEnv/pages/officehome/components/work-scene.wpy
  99. 483 0
      src/packagesEnv/pages/officehome/demo.wpy
  100. 0 0
      src/packagesEnv/pages/officehome/detail.wpy

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+dist/*

+ 30 - 0
.eslintrc.js

@@ -0,0 +1,30 @@
+module.exports = {
+  root: true,
+  globals: { wx: true },
+  parser: 'babel-eslint',
+  parserOptions: {
+    sourceType: 'module'
+  },
+  env: {
+    browser: true
+  },
+  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
+  extends: 'standard',
+  // required to lint *.wpy files
+  plugins: [
+    'html'
+  ],
+  settings: {
+    'html/html-extensions': ['.html', '.wpy']
+  },
+  // add your custom rules here
+  'rules': {
+    // allow paren-less arrow functions
+    'arrow-parens': 0,
+    // allow async-await
+    'generator-star-spacing': 0,
+    // allow debugger during development
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'space-before-function-paren': 0
+  }
+}

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+node_modules
+dist
+weapp
+.DS_Store

+ 3 - 0
.prettierrc

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

文件差異過大導致無法顯示
+ 1 - 0
.wepycache


+ 4 - 0
.wepyignore

@@ -0,0 +1,4 @@
+node_modules
+dist
+.DS_Store
+*.wpy___jb_tmp___

文件差異過大導致無法顯示
+ 7214 - 0
package-lock.json


+ 56 - 0
package.json

@@ -0,0 +1,56 @@
+{
+  "name": "sagaclound-wepy",
+  "version": "0.0.2",
+  "description": "环境智控",
+  "main": "weapp/app.js",
+  "scripts": {
+    "serve-dev": "cross-env NODE_ENV=dev wepy build --watch",
+    "build-prod": "cross-env NODE_ENV=prod wepy build --no-cache",
+    "clean": "rm -rf weapp",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "wepy": {
+    "module-a": false,
+    "./src/components/list": "./src/components/wepy-list.wpy"
+  },
+  "author": "chenzhen2 <chenzhen2@nucarf.com>",
+  "license": "MIT",
+  "dependencies": {
+    "@antv/wx-f2": "^2.1.1",
+    "@miniprogram-component-plus/index-list": "^1.0.0",
+    "@vant/weapp": "1.6.6",
+    "@wepy/core": "^2.0.0-alpha.9",
+    "@wepy/x": "^2.0.2",
+    "miniprogram-slide-view": "0.0.3",
+    "moment": "^2.29.1",
+    "umtrack-wx": "^2.7.1",
+    "vue": "^2.6.12",
+    "wepy": "^1.7.2",
+    "wepy-async-function": "^1.4.7",
+    "weui-miniprogram": "^1.0.7"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.1.0",
+    "@babel/preset-env": "^7.1.0",
+    "@wepy/babel-plugin-import-regenerator": "0.0.2",
+    "@wepy/cli": "^2.0.0-alpha.20",
+    "@wepy/compiler-babel": "^2.0.5",
+    "@wepy/compiler-less": "^2.0.7",
+    "@wepy/plugin-define": "^2.1.0",
+    "@wepy/plugin-uglifyjs": "^2.1.0",
+    "babel-eslint": "^7.2.1",
+    "babel-plugin-import": "^1.13.3",
+    "cross-env": "^5.1.3",
+    "eslint": "^3.18.0",
+    "eslint-config-standard": "^7.1.0",
+    "eslint-friendly-formatter": "^2.0.7",
+    "eslint-plugin-html": "^2.0.1",
+    "eslint-plugin-promise": "^3.5.0",
+    "eslint-plugin-standard": "^2.0.1",
+    "less": "^3.8.1",
+    "uglify-js": "^3.13.0",
+    "wepy-compiler-babel": "^1.5.3",
+    "wepy-compiler-less": "^1.3.14",
+    "wepy-eslint": "^1.5.3"
+  }
+}

+ 44 - 0
project.config.json

@@ -0,0 +1,44 @@
+{
+  "description": "项目配置文件",
+  "setting": {
+    "urlCheck": true,
+    "es6": false,
+    "enhance": false,
+    "postcss": false,
+    "preloadBackgroundData": false,
+    "minified": false,
+    "newFeature": false,
+    "coverView": true,
+    "nodeModules": false,
+    "autoAudits": false,
+    "showShadowRootInWxmlPanel": true,
+    "scopeDataCheck": false,
+    "uglifyFileName": false,
+    "checkInvalidKey": true,
+    "checkSiteMap": true,
+    "uploadWithSourceMap": true,
+    "compileHotReLoad": false,
+    "useMultiFrameRuntime": true,
+    "useApiHook": true,
+    "useApiHostProcess": true,
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "enableEngineNative": false,
+    "useIsolateContext": true,
+    "useCompilerModule": true,
+    "userConfirmedUseCompilerModuleSwitch": false,
+    "userConfirmedBundleSwitch": false,
+    "packNpmManually": false,
+    "packNpmRelationList": [],
+    "minifyWXSS": true
+  },
+  "compileType": "miniprogram",
+  "appid": "wxda2ef261ac3cca32",
+  "projectname": "sagaclound-wepy",
+  "miniprogramRoot": "weapp/",
+  "srcMiniprogramRoot": "weapp/",
+  "condition": {}
+}

+ 43 - 0
src/api/company.js

@@ -0,0 +1,43 @@
+import $http from '@/common/request.js';
+
+export {
+  // 在资管方搜索公司
+  companySearchUnderOwner,
+  getCompanyById,
+  // 获取该公司的Oa系统
+  getCompanyOa,
+  // 获取公司配置
+  getCompanyConfig
+};
+
+
+function getCompanyById(id) {
+  return $http({
+    url: `/company/${id}`,
+    method: 'GET',
+  });
+}
+
+function getCompanyOa(companyId) {
+  return $http({
+    url: `/company/oa/${companyId}`,
+    method: 'GET',
+  });
+}
+
+
+function companySearchUnderOwner(query, isNotShowErrorToast=false) {
+  query = decodeURIComponent(query);
+  return $http({
+    url: `/company/underOwner/${query}`,
+    method: 'GET',
+    isNotShowErrorToast
+  });
+}
+
+function getCompanyConfig(companyId){
+  return $http({
+    url: `/company/config/${companyId}`,
+    method: 'GET'
+  });
+} 

+ 9 - 0
src/api/home.js

@@ -0,0 +1,9 @@
+function getUserHasFollowed() {
+    return $http({
+        url: `/home/indexWechatPublic`,
+    })
+}
+
+export {
+    getUserHasFollowed
+}

+ 223 - 0
src/api/meetingroom.js

@@ -0,0 +1,223 @@
+import $http from '@/common/request.js';
+
+export {
+  // 会议室详情接口(图片,基本信息,设备列表)
+  getMeetingroomDetail,
+  // 我的预约列表
+  getMyReserveList,
+  // 预约详情
+  getReserveDetail,
+  // 取消预约
+  cancelReserve,
+  // 会议室列表
+  getMeetingList,
+  // 预约会议室
+  reserveMeetingroom,
+  // 更新会议室成员
+  updateRerverMember,
+  // 根据部门id 获取部门用户
+  getUserByDept,
+  // 获取指定时间已预约的会议室
+  getFiterTimeRerveRoomList,
+  // 获取指定时间推荐的会议室
+  getFiterTimeRecommandRoomList,
+  // 获取收藏会议室列表
+  getCollectRoomList,
+  // 收藏会议室
+  collectRoom,
+  // 取消收藏会议室
+  cancelCollectRoom,
+  // 获取推荐会议参与人员
+  getRecommandAttendees,
+  // 结束会议室预约
+  endMeetingReserve,
+  // 更改会议标题
+  updateMeetingTitle,
+  // 支付会议室预约订单
+  payMeetingReserve,
+  // 预约会议室的收费列表
+  getRerveRoomChargeList,
+  // -会议室订单详情发起结束会议
+  meetingBookingEndFromOrder,
+  // -会议室订单详情发起取消会议
+  meetingBookingCancelFromOrder,
+  // 获取会议室运营时间
+  getMeetingRoomServerTime
+};
+
+function meetingBookingCancelFromOrder(orderId) {
+  return $http({
+    url: `/meetingBooking/cancelFromOrder/${orderId}`,
+    method: 'PUT'
+  });
+}
+
+function meetingBookingEndFromOrder(orderId) {
+  return $http({
+    url: `/meetingBooking/endFromOrder/${orderId}`,
+    method: 'PUT'
+  });
+}
+
+function getMeetingroomDetail(id) {
+  return $http({
+    url: `/meeting/user/${id}`,
+    method: 'GET'
+  });
+}
+
+function getMyReserveList() {
+  return $http({
+    url: '/meetingBooking/user',
+    method: 'GET'
+  });
+}
+
+function getReserveDetail(id) {
+  return $http({
+    url: `/meetingBooking/${id}`,
+    method: 'GET'
+  });
+}
+
+function cancelReserve(id) {
+  return $http({
+    url: `/meetingBooking/cancel/${id}`,
+    method: 'PUT'
+  });
+}
+
+function getMeetingList() {
+  return $http({
+    url: `/meeting/v2/all`,
+    method: 'GET'
+  });
+}
+
+function reserveMeetingroom(id, params) {
+  return $http({
+    url: `/meetingBooking`,
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    },
+    data: JSON.stringify({ ...params, meetingId: id }),
+    isNotShowErrorToast: true
+  });
+}
+
+function updateRerverMember(params) {
+  return $http({
+    url: `/meetingBooking/updateMember`,
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    },
+    data: JSON.stringify({ ...params })
+  });
+}
+function updateMeetingTitle(id, params) {
+  return $http({
+    url: `/meetingBooking/${id}`,
+    method: 'PUT',
+    headers: {
+      'content-type': 'application/json'
+    },
+    data: JSON.stringify({ ...params })
+  });
+}
+function getUserByDept(ids) {
+  return $http({
+    url: '/dingding/userByDept',
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    },
+    data: JSON.stringify(ids)
+  });
+}
+
+function getFiterTimeRerveRoomList(params) {
+  return $http({
+    url: '/meetingBooking/user/booking',
+    method: 'GET',
+    data: { ...params }
+  });
+}
+
+function getFiterTimeRecommandRoomList(params) {
+  return $http({
+    url: '/meeting/recommend',
+    method: 'GET',
+    data: { ...params }
+  });
+}
+
+function getCollectRoomList() {
+  return $http({
+    url: '/meetingFavorite',
+    method: 'GET'
+  });
+}
+
+function collectRoom(meetingId) {
+  return $http({
+    url: `/meetingFavorite?meetingId=${meetingId}`,
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    }
+  });
+}
+
+function cancelCollectRoom(meetingId) {
+  return $http({
+    url: `/meetingFavorite?meetingId=${meetingId}`,
+    method: 'DELETE',
+    headers: {
+      'content-type': 'application/json'
+    }
+  });
+}
+
+function getRecommandAttendees() {
+  return $http({
+    url: '/persagy/meeting/attendees',
+    method: 'GET'
+  });
+}
+
+function endMeetingReserve(reserveId) {
+  return $http({
+    url: `/meetingBooking/end/${reserveId}`,
+    method: 'PUT'
+  });
+}
+
+function payMeetingReserve(orderId) {
+  return $http({
+    url: `/user/order/meetingOrderPay`,
+    method: 'GET',
+    data: { id: orderId }
+  });
+}
+
+function getRerveRoomChargeList(id, params) {
+  return $http({
+    url: `/meetingBooking/meetingBookBudget`,
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    },
+    data: JSON.stringify({ ...params, meetingId: id }),
+    isNotShowErrorToast: true
+  });
+}
+
+function getMeetingRoomServerTime(data) {
+  return $http({
+    url: `/meeting/queryMeetingUsableTime`,
+    method: 'GET',
+    data: { ...data }
+  });
+}

+ 10 - 0
src/api/user.js

@@ -0,0 +1,10 @@
+function getCompmayUsers() {
+    return $http({
+        url: `/user/companyUsers`,
+        method: 'GET',
+    });
+}
+
+export {
+    getCompmayUsers
+}

+ 77 - 0
src/app.wpy

@@ -0,0 +1,77 @@
+<style lang="less">
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+}
+</style>
+
+<script>
+import wepy from '@wepy/core';
+import eventHub from './common/eventHub';
+import vuex from '@wepy/x';
+
+wepy.use(vuex);
+
+
+wepy.app({
+  hooks: {
+    // App 级别 hook,对整个 App 生效
+    // 同时存在 Page hook 和 App hook 时,优先执行 Page hook,返回值再交由 App hook 处
+    'before-setData': function (dirty) {
+      console.log('setData dirty: ', dirty);
+      return dirty;
+    }
+  },
+  globalData: {
+    userInfo: null
+  },
+
+  onLaunch() {
+    //this.testAsync();
+   // eventHub.$on('app-launch', (...args) => {
+    //  console.log('app-launch event emitted, the params are:');
+    //  console.log(args);
+   // });
+  },
+
+  methods: {
+  }
+});
+</script>
+<config>
+{
+    pages: [
+      'pages/index'
+    ],
+    "subpackages":[
+     {
+        "root": "packagesEnv/",
+        "pages": [
+          'pages/officehome/index',
+          'pages/officehome/detail',
+          'pages/officehome/demo',
+          'pages/portrait/home',
+          'pages/portrait/visitor',
+          'pages/portrait/meeting',
+          'pages/portrait/energy',
+          'pages/portrait/energyPoint',
+          'pages/portrait/todayPoint',
+          'pages/portrait/pointExplain',
+          'pages/portrait/pointRanking',
+          'pages/portrait/ajustRecord',
+          'pages/portrait/carbonEnterprise',
+        ]
+      },
+    ],
+    window: {
+      backgroundTextStyle: 'light',
+      navigationBarBackgroundColor: '#fff',
+      navigationBarTitleText: ' ',
+      navigationBarTextStyle: 'black'
+    }
+}
+</config>

+ 53 - 0
src/common/errorRequest.js

@@ -0,0 +1,53 @@
+import config from '@/config';
+import store from '@/store';
+// 错误接口信息存到后台数据库  persagy定制
+
+export default function (pobj) {
+  var params = {
+    name: pobj.name, // 类型:String  必有字段  备注:接口名字
+    type: 2, // 类型:String  必有字段  备注:类型,默认传 2 (表示异常)
+    success: false, // 类型:Boolean  必有字段  备注:默认false
+    input: pobj.input, // 类型:String  必有字段  备注:请求输入参数
+    exception: pobj.exception // 类型:String  必有字段  备注:异常信息
+  };
+  var info = {
+    url: `${config.brsgServer.duoduoUrl}/server/systemLog/save`,
+    method: 'POST',
+    data: JSON.stringify(params)
+  };
+
+  const token = store.state.user.token;
+  if (token) {
+    info.header = Object.assign({
+      token
+    }, info.header)
+  }
+
+  info.timeout = info.timeout || 10 * 1000; // 默认超时为10s
+
+  return new Promise((resolve, reject) => {
+    wx.request(
+      Object.assign(info, {
+        success(res) {
+          if (res.statusCode >= 400) {
+            console.log(
+              `systemLog/save接口请求失败:code=${res.statusCode},msg=${
+                res.message || JSON.stringify(res)
+              }`
+            );
+            reject(res.errMsg);
+          } else if (res.data.code !== 1 && res.data.code) {
+            console.log('请systemLog/save求失败:' + `${res.data.msg}`);
+            reject(res.data);
+          } else {
+            resolve(res.data);
+          }
+        },
+        fail(res) {
+          reject({ ...res, reason: 'wxRequestFail' });
+          console.log(`微信接口systemLog/save调用失败:${JSON.stringify(res)}`);
+        }
+      })
+    );
+  });
+}

+ 5 - 0
src/common/eventHub.js

@@ -0,0 +1,5 @@
+import wepy from '@wepy/core';
+
+let eventHub = new wepy();
+
+export default eventHub;

+ 40 - 0
src/common/loadFont.js

@@ -0,0 +1,40 @@
+import config from '@/config';
+function loadFont(weight = 400, global = false) {
+  const source = {
+    400: `url("${config.h5StaticPath}/font/Montserrat/Montserrat-Regular.ttf")`, // Regular
+    500: `url("${config.h5StaticPath}/font/Montserrat/Montserrat-Medium.ttf")` // Medium
+  }
+  wx.loadFontFace({
+    family: 'Montserrat',
+    source: source[weight],
+    desc: {
+      weight: weight
+    },
+    global,
+    success: function (message) {
+      // console.log('load font-family success:', message)
+    },
+    fail: function (message) {
+      console.log('load font-family fail: ', message)
+    }
+  })
+}
+
+export default loadFont
+
+export function loadFontUrl(fontSource, fontFamily, global) {
+  wx.loadFontFace({
+    family: fontFamily,
+    source: fontSource,
+    desc: {
+      weight: 400
+    },
+    global,
+    success: function (message) {
+      // console.log('load font-family success:', message)
+    },
+    fail: function (message) {
+      console.log('load font-family fail: ', message)
+    }
+  })
+}

+ 122 - 0
src/common/request.js

@@ -0,0 +1,122 @@
+/* eslint-disable prefer-promise-reject-errors */
+import config from '@/config';
+import store from '@/store';
+import errorRequest from './errorRequest.js';
+
+export default function (info) {
+  if (info.url.startsWith('/')) {
+    const serverSp = info.serverSp ? info.serverSp : config.serverUrl
+    info.url = serverSp + info.url
+  }
+
+  const token = store.state.user.token;
+  if (token) {
+    info.header = Object.assign({
+      token
+    }, info.header)
+  }
+
+  info.timeout = info.timeout || 10 * 1000; // 默认超时为10s
+
+  const isNotShowErrorToast = info.isNotShowErrorToast || false;
+  delete info.isNotShowErrorToast;
+
+  const errorSave = info.errorSave || false;
+  delete info.errorSave;
+
+  return new Promise((resolve, reject) => {
+    // 在这里的请求success 和fail 里进行hideLoading 原因有2点
+    // 1.是有些请求showLoading了,但是在异常情况不需要处理,就可能未hideLoading导致页面异常
+    // 2.是有些是在这里showToast 需要先将有showLoading 的 hide掉才能showToast出来
+
+    wx.request(Object.assign(info, {
+      success: (res) => {
+        try {
+          wx.hideLoading({
+            fial: () => { },
+            noConflict: true
+          });
+        } catch (error) {
+          console.error(error);
+        }
+        if (res.statusCode === 401) {
+          if (store.state.user.tokenIsValid) {
+            // 刷新首页
+            store.commit('setTokenIsValid', false);
+            store.commit('setToken', '');
+            wx.redirectTo({ url: '/pages/homev2' });
+          }
+        } else if (res.statusCode >= 400) {
+          reject(res.errMsg);
+          if (!isNotShowErrorToast) {
+            setTimeout(() => {
+              wx.showToast({
+                title: `接口请求失败:code=${res.statusCode},msg=${res.message || JSON.stringify(res)}`,
+                icon: 'none',
+                duration: 5000
+              })
+            }, 0);
+          } else {
+            console.log(`接口请求失败:code=${res.statusCode},msg=${res.message || JSON.stringify(res)}`)
+          }
+          // 错误接口信息存到后台数据库  persagy定制
+          errorSave && errorRequest({
+            name: info.url, // 类型:String  必有字段  备注:接口名字
+            input: info.data || '', // 类型:String  必有字段  备注:请求输入参数
+            exception: JSON.stringify(res.errMsg) // 类型:String  必有字段  备注:异常信息
+          });
+        } else if (res.data.code !== 1 && res.data.code) {
+
+          reject(res.data);
+          if (!isNotShowErrorToast) {
+            // 防止代码里异常情况下的业务代码里hideLoading, 会把此时的toast 关掉里, 故用延迟处理, 让业务代码hideloading执行完, 再showtoast, 这样就不回关闭里
+            setTimeout(() => {
+              wx.showToast({
+                title: `${res.data.msg}`,
+                icon: 'none',
+                duration: 5000
+              })
+            }, 0);
+          } else {
+            console.log('请求失败:' + `${res.data.msg}`)
+          }
+          errorSave && errorRequest({
+            name: info.url, // 类型:String  必有字段  备注:接口名字
+            input: info.data || '', // 类型:String  必有字段  备注:请求输入参数
+            exception: JSON.stringify(res.data) // 类型:String  必有字段  备注:异常信息
+          });
+        } else {
+          resolve(res.data);
+        }
+      },
+      fail: (res) => {
+        try {
+          wx.hideLoading({
+            fial: () => { },
+            noConflict: true
+          });
+        } catch (error) {
+          // console.error(error);
+        }
+        reject({ ...res, reason: 'wxRequestFail' });
+        errorSave && errorRequest({
+          name: info.url, // 类型:String  必有字段  备注:接口名字
+          input: info.data || '', // 类型:String  必有字段  备注:请求输入参数
+          exception: JSON.stringify(res) // 类型:String  必有字段  备注:异常信息
+        });
+        if (!isNotShowErrorToast) {
+          // 防止有loading的时候toast 闪现,故用settimout 来延迟,让reject函数先hideLoading ,再toast
+          setTimeout(() => {
+            wx.showToast({
+              title: `网络异常,请稍后重试`,
+              icon: 'none',
+              duration: 5000
+            })
+          }, 0);
+        } else {
+          console.log(`微信接口request调用失败:${JSON.stringify(res)}`)
+        }
+      }
+    }))
+  })
+}

文件差異過大導致無法顯示
+ 1040 - 0
src/common/utils.js


+ 65 - 0
src/components/common/FollowBtn.wpy

@@ -0,0 +1,65 @@
+<style lang="less">
+.component-follow-btn {
+  width: 100%;
+  height: 100%;
+}
+</style>
+
+<template>
+  <block v-if="followed !== null">
+    <block v-if="followed">
+      <slot name="followed-content"></slot>
+    </block>
+    <slot name="un-followed-content" v-else v-on:click="toFollow"></slot>
+  </block>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import store from '@/store';
+import { mapState } from '@wepy/x';
+import { getUserHasFollowed } from '@/api/home';
+wepy.component({
+  store,
+  mame: 'FollowBtn',
+  data: {
+    followed: null,
+    linkUrl: ''
+  },
+  options: {
+    multipleSlots: true
+  },
+  props: {},
+  created() {
+    if (this.token) {
+      this.getData();
+    }
+  },
+  computed: {
+    ...mapState({ token: state => state.user.token })
+  },
+  watch: {
+    token(curVal, oldVal) {
+      if (curVal && !oldVal) {
+        this.getData();
+      }
+    }
+  },
+  methods: {
+    getData() {
+      getUserHasFollowed().then(res => {
+        let result = res.data || {};
+        this.followed = result.subscribe;
+        this.linkUrl = result.publicWechatUrl;
+      });
+    },
+    toFollow() {
+      wx.navigateTo({
+        url: `/h5/pages/external-view/h5?url=${encodeURIComponent(
+          this.linkUrl
+        )}`
+      });
+    }
+  }
+});
+</script>

+ 91 - 0
src/components/common/action-sheet-list.wpy

@@ -0,0 +1,91 @@
+<style lang="less">
+.component-action-sheet-list {
+  background: rgba(196, 196, 196, 0.2);
+  border-radius: 40rpx 40rpx 0 0;
+  overflow: hidden;
+  .option-list {
+    background: #fff;
+    width: 100%;
+    margin-bottom: 20rpx;
+    .option-item {
+      width: 100%;
+      height: 110rpx;
+      font-size: 32rpx;
+      line-height: 110rpx;
+      color: #000000;
+      text-align: center;
+    }
+  }
+  .btn-cancel {
+    height: 178rpx;
+    width: 100%;
+    background: #fff;
+    font-size: 32rpx;
+    line-height: 110rpx;
+    color: #000000;
+    text-align: center;
+  }
+}
+</style>
+
+<template>
+  <slider-panel
+    slidDoneEmitId="hidePanel"
+    @hidePanel="hidePanel"
+    showClose="{{false}}"
+    showSliderIcon="{{false}}"
+  >
+    <div class="component-action-sheet-list">
+      <div class="option-list">
+        <div
+          class="option-item"
+          v-for="item in options"
+          v-on:click="selectOption(item)"
+        >{{item.label}}</div>
+      </div>
+      <div class="btn-cancel" v-on:click="cancel">{{cancelText}}</div>
+    </div>
+  </slider-panel>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+let pageTouchStartY = 0;
+let isPageTouchStart = false;
+let componentTop = 0;
+wepy.component({
+  props: {
+    options: Array,
+    cancelText: {
+      type: String,
+      default: '取消'
+    }
+  },
+  ready() {},
+  data: {},
+  methods: {
+    hidePanel() {
+      this.$emit('component-action-sheet-close');
+    },
+    selectOption(item) {
+      this.$emit('component-action-sheet-slide-choose', item);
+    },
+    cancel() {
+      this.$emit('component-action-sheet-close');
+    }
+  }
+});
+</script>
+
+
+<config>
+{
+    usingComponents: {
+      'slider-panel': '~@/components/common/slider-panel',
+    },
+    "componentPlaceholder": {
+      "slider-panel": "view"
+    }
+}
+</config>

+ 119 - 0
src/components/common/action-sheet.wpy

@@ -0,0 +1,119 @@
+<style lang="less">
+.component-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: #0000001f;
+  z-index: 20; //因为导航栏的z-index 为11 故为了遮住导航栏
+  .content-wrap {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    height: 420rpx;
+    border-radius: 24rpx 24rpx 0px 0px;
+    background: #f2f2f2;
+    overflow: hidden;
+    .title {
+      height: 112rpx;
+      line-height: 112rpx;
+      text-align: center;
+      border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+      background: #ffffff;
+      span,
+      label {
+        font-size: 28rpx;
+        color: #000000;
+        mix-blend-mode: normal;
+        opacity: 0.5;
+      }
+    }
+    .confrim {
+      height: 112rpx;
+      line-height: 112rpx;
+      text-align: center;
+      border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+      background: #ffffff;
+      span,
+      label {
+        font-size: 34rpx;
+        color: #fa5151;
+        mix-blend-mode: normal;
+        opacity: 0.9;
+      }
+    }
+    .cancel {
+      position: absolute;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      height: 180rpx;
+      padding-top: 32rpx;
+      text-align: center;
+      background: #ffffff;
+      box-sizing: border-box;
+      span,
+      label {
+        font-size: 34rpx;
+        line-height: 48rpx;
+        color: #000000;
+        mix-blend-mode: normal;
+        opacity: 0.9;
+      }
+    }
+  }
+}
+</style>
+
+<template>
+  <div class="component-container">
+    <div class="content-wrap">
+      <div class="title">
+        <span>{{ title }}</span>
+      </div>
+      <div class="confrim">
+        <span @click="confirm">{{ confirmText }}</span>
+      </div>
+      <div class="cancel">
+        <span @click="cancel">{{ cancelText }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+let pageTouchStartY = 0;
+let isPageTouchStart = false;
+let componentTop = 0;
+wepy.component({
+  props: {
+    // 注意同一个页面使用多个该组件时该值是否要保持唯一
+    confirmEmitId: String,
+    cancelEmitId: String,
+    title: String,
+    confirmText: String,
+    cancelText: {
+      type: String,
+      default: '取消',
+    },
+  },
+  ready() {},
+  data: {},
+  methods: {
+    confirm() {
+      if (this.confirmEmitId) {
+        this.$emit(this.confirmEmitId);
+      }
+    },
+    cancel() {
+      if (this.cancelEmitId) {
+        this.$emit(this.cancelEmitId);
+      }
+    },
+  },
+});
+</script>

+ 115 - 0
src/components/common/carousel.wpy

@@ -0,0 +1,115 @@
+<style lang="less">
+.carousel-container {
+  width: 100%;
+  height: 100%;
+  .imgs-container {
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    position: relative;
+    .swiper {
+      height: 100%;
+    }
+    .imgs-wrap {
+      height: 100%;
+      display: flex;
+      justify-items: flex-start;
+      align-items: flex-start;
+      flex-wrap: nowrap;
+    }
+    .wrap-transition {
+      transition: ease-in-out .5s;
+    }
+    .img-block {
+      width: 100%;
+      height: 100%;
+      background-size: cover;
+      background-position: center;
+      background-repeat: no-repeat;
+      flex: none;
+    }
+  }
+  .dots {
+    position: absolute;
+    z-index: 1;
+    bottom: 16rpx;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 50%;
+    height: 16rpx;
+    display: flex;
+    justify-content: center;
+    align-items: flex-start;
+    .dot {
+      display: inline-block;
+      height: 16rpx;
+      width: 16rpx;
+      border-radius: 50%;
+      border: 1rpx solid #FFFFFF;
+      background: transparent;
+      margin-right: 12rpx;
+    }
+    .dot:nth-last-child(1) {
+      margin-right: 0;
+      display: none;
+    }
+    .dot-actived {
+      background: #FFFFFF;
+    }
+  }
+}
+</style>
+
+<template>
+  <div class="carousel-container" ref="container">
+    <div class="imgs-container" v-if="imgs.length">
+      <swiper
+        class="swiper"
+        :indicator-dots="true" :autoplay="true" :circular="true"
+        :interval="3000" easing-function="easeInOutCubic"
+        indicator-color="rgba(255, 255, 255, .5)" indicator-active-color="#ffffff">
+        <swiper-item v-for="(img, index) in imgs" :key="index">
+          <div  class="img-block"
+            :style="{ 'background-image': 'url('+h5Origin+'/'+img.imageUrl+')'}"
+            @click="goDetail(img.innerUrl)"
+          ></div>
+          <view class="swiper-item {{item}}"></view>
+        </swiper-item>
+      </swiper>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+import utils from '../../common/utils.js';
+
+const DEFAULT_INTERVAL = 3000
+const DEFAULT_ANIMATION_TIME = 500
+wepy.component({
+  props: {
+    imgs: Array,
+    interval: Number
+  },
+  data: {
+    h5Origin: config.h5Origin,
+  },
+  computed: {
+  },
+  watch: {
+    imgs (val) {
+      // console.log(val)
+    }
+  },
+  methods: {
+    goDetail(absUrl) {
+      wx.navigateTo({
+        url: `/h5/pages/external-view/h5?url=${absUrl}`,
+      });
+    }
+  },
+  ready() {
+  }
+});
+</script>

+ 51 - 0
src/components/common/common-button.wpy

@@ -0,0 +1,51 @@
+<style lang="less">
+.button-wrap {
+  margin: 0 auto;
+  width: 480rpx;
+  height: 112rpx;
+  line-height: 112rpx;
+  background: #ffda1a;
+  box-shadow: 0px 4px 12px rgba(255, 218, 26, 0.3);
+  border-radius: 40rpx;
+  text-align: center;
+  font-weight: 500;
+  font-size: 32rpx;
+  color: rgba(13, 13, 61, 0.86);
+  .icon {
+    margin-right: 16rpx;
+    width: 28rpx;
+    height: 28rpx;
+    vertical-align: middle;
+  }
+  .text {
+    vertical-align: middle;
+  }
+}
+</style>
+
+<template>
+  <div class="button-wrap" v-on:click="submit">
+    <image class="icon" v-if="icon" src="{{icon}}" />
+    <span class="text" v-if="text">{{ text }}</span>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  props: {
+    icon: String,
+    text: String,
+    clickEmitId: String,
+  },
+  ready() {},
+  methods: {
+    submit() {
+      if (this.clickEmitId) {
+        this.$emit(this.clickEmitId);
+      }
+    },
+  },
+});
+</script>

+ 89 - 0
src/components/common/data-none.wpy

@@ -0,0 +1,89 @@
+<style lang="less">
+.components-date-none {
+  position: fixed;
+  bottom: 43.3%;
+  left: 0;
+  right: 0;
+  .wrap {
+    text-align: center;
+    &.center {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      height: 100%;
+    }
+
+    .desc {
+      font-family: PingFang SC;
+      margin-top: 32rpx;
+      font-size: 34rpx;
+      line-height: 48rpx;
+      color: #c4c4c4;
+    }
+
+    .poster {
+      margin: 0 auto;
+      width: 200rpx;
+      height: 200rpx;
+      display: block;
+    }
+  }
+}
+</style>
+
+<template>
+  <div class="components-date-none">
+    <div class="wrap {{position}}">
+      <Icon
+        v-if="iconName"
+        :name="iconName"
+        size="100"
+        color="rgba(196, 196, 196, 0.4)"
+        weight="200"
+      />
+      <img v-else-if="imgUrl" class="poster" :src="imgUrl" />
+
+      <div class="desc">
+        <span>{{ desc }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  props: {
+    desc: String,
+    // 有的需要在剩余位置中居中处理
+    position: {
+      type: String,
+      default: ''
+    },
+    iconName: {
+      type: String
+    },
+    iconColor: {
+      type: String,
+      default: ''
+    },
+    imgUrl: {
+      type: String,
+      default: ''
+    }
+  }
+});
+</script>
+
+<config>
+{
+  "usingComponents": {
+    "Icon": "~@/components/TUI/Icon"
+  },
+  "componentPlaceholder": {
+    "Icon": "view"
+  }
+}
+</config>

+ 76 - 0
src/components/common/dot.wpy

@@ -0,0 +1,76 @@
+<style lang="less">
+.dot {
+  width: 5px;
+  height: 5px;
+  border-radius: 50%;
+}
+.orange {
+  background: rgba(255, 136, 77, 0.8);
+  box-shadow: 0px 4px 12px rgba(255, 136, 77, 0.8),
+    0px 0px 4px rgba(255, 136, 77, 0.8);
+}
+.blue {
+  background: rgba(77, 148, 255, 0.8);
+  box-shadow: 0px 4px 12px rgba(77, 148, 255, 0.8),
+    0px 0px 4px rgba(77, 148, 255, 0.8);
+}
+.green {
+  background: rgba(52, 209, 157, 0.8);
+  box-shadow: 0px 4px 12px rgba(52, 209, 157, 0.8),
+    0px 0px 4px rgba(52, 209, 157, 0.8);
+}
+.dot-flicker {
+  animation: flicker 2s infinite linear;
+  -webkit-animation: flicker 2s infinite linear;
+}
+@keyframes flicker {
+  0% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0;
+  }
+  100% {
+    opacity: 1;
+  }
+}
+@-webkit-keyframes flicker {
+  0% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0;
+  }
+  100% {
+    opacity: 1;
+  }
+}
+</style>
+
+<template>
+  <div
+    class="dot"
+    :class="{'dot-flicker': isFlicker,
+      'orange': colorType=='orange',
+      'blue': colorType=='blue',
+      'green': colorType=='green'}">
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  props: {
+    colorType: {
+      type: String,
+      default: 'orange'
+    },
+    // 是否闪烁
+    isFlicker: {
+      type: Boolean,
+      default: false
+    }
+  }
+});
+</script>

+ 96 - 0
src/components/common/input-with-clear.wpy

@@ -0,0 +1,96 @@
+<style lang="less">
+.component-input {
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  position: relative;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  background: rgba(196, 196, 196, 0.2);
+  border-radius: 44rpx;
+  padding-left: 80rpx;
+}
+
+.icon-search-wrap {
+  position: absolute;
+  left: 28rpx;
+  top: 28rpx;
+  .icon {
+    width: 32rpx;
+    height: 32rpx;
+    display: block;
+  }
+}
+.component-input .input {
+  height: 100%;
+  flex: 1;
+  box-sizing: border-box;
+  padding: 0;
+  background: transparent;
+  font-size: 48rpx;
+  font-size: 30rpx;
+  line-height: 100%;
+  color: #000;
+}
+
+.component-input .clear {
+  height: 100%;
+  width: 72rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+</style>
+
+<template>
+  <div class="component-input">
+    <label v-if="isShowSearch" class="icon-search-wrap" for="component-input">
+      <image class="icon" src="{{h5StaticPath+ '/icon-search.svg'}}" />
+    </label>
+    <input
+      id="component-input"
+      class="input"
+      :placeholder="placeholder"
+      :value="value"
+      bindinput="changeValue"
+      placeholder-style="{{placeholderStyle || 'color:#AFAFC1;'}}"
+    />
+    <div class="clear" v-if="value" v-on:click="clear()">
+      <icon type="clear" size="16" class="icon-close"></icon>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+wepy.component({
+  data: {
+    h5StaticPath: config.h5StaticPath
+  },
+  props: {
+    placeholder: String,
+    value: String,
+    placeholderStyle: String,
+    isShowSearch: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    clear() {
+      this.changeValueFun('');
+    },
+    changeValue(e) {
+      let value = e.$wx.detail.value;
+      this.changeValueFun(value);
+    },
+    changeValueFun(text) {
+      this.$emit('component-input-with-clear-change', text);
+    }
+  }
+});
+</script>

+ 60 - 0
src/components/common/page-bottom-button.wpy

@@ -0,0 +1,60 @@
+<style lang="less">
+.page-bottom-button {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  height: 280rpx;
+  background-size: 100%;
+  z-index: 1;
+  .button-container {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 80rpx;
+  }
+}
+</style>
+
+<template>
+    <div class="page-bottom-button" :style="{'background-image': 'url('+h5StaticPath+'/page_bottom_btn_bg.png)'}">
+        <div class="button-container">
+            <common-button text="{{text}}" icon="{{icon}}" click-emit-id="pageBottomBtnEmitId" v-on:pageBottomBtnEmitId="submit" />
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+wepy.component({
+  props: {
+    icon: String,
+    text: String,
+    clickEmitId: String
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath
+  },
+  ready() {},
+  methods: {
+    submit() {
+      if (this.clickEmitId) {
+        this.$emit(this.clickEmitId);
+      }
+    }
+  }
+});
+</script>
+
+<config>
+{
+    usingComponents: {
+      'common-button': '~@/components/common/common-button'
+    },
+    "componentPlaceholder": {
+      "common-button": "view"
+    }
+}
+</config>

+ 115 - 0
src/components/common/page-pull-down-animation.wpy

@@ -0,0 +1,115 @@
+<style lang="less">
+.page-pull-down-animation {
+  width: 100%;
+  .page-top-pic {
+    width: 100%;
+    // overflow-x: hidden;
+    overflow: hidden;
+    image {
+      width: 100%;
+      height: 100%;
+      transition: all 0.1s;
+      transform-origin: bottom center;
+    }
+  }
+  .page-content {
+    width: 100%;
+  }
+}
+</style>
+
+<template>
+  <!-- <div class="page-pull-down-animation" v-on:touchstart="pageTouchStart" v-on:touchmove="pageTouchMove" v-on:touchend="pageTouchEnd"> -->
+  <div class="page-pull-down-animation">
+    <div class="page-top-pic" :style="{'margin-top': picTop+'rpx', 'height': picHeight+'rpx', 'background-color': bgColor}">
+      <image :style="{'transform': 'scale('+picScale+')'}" src="{{pic}}" />
+    </div>
+    <div class="page-content">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+let pageTouchStartY = 0;
+let isPageTouchStart = false;
+let picTop = 0;
+let picHeight = 0;
+wepy.component({
+  props: {
+    // 顶部图片地址
+    pic: String,
+    // 图片尺寸-高,单位rpx
+    height: Number,
+    // 顶部图片超出上边界的距离,单位rpx
+    top: Number,
+    // 组件id
+    elementId: String,
+    // 背景颜色
+    bgColor: String
+  },
+  data: {
+    picScale: 1,
+    picHeight: 0,
+    picTop: 0,
+    pageContentPaddingTop: 0
+  },
+  ready() {
+    picHeight = Number(this.height || 0);
+    if (!this.top && this.top != 0) {
+      picTop = -(picHeight / 3);
+    } else {
+      picTop = Number(this.top);
+    }
+    this.picTop = picTop;
+    this.picHeight = picHeight;
+    this.pageContentPaddingTop = picHeight + picTop;
+    if(!this.bgColor){
+      this.bgColor = "transparent"
+    }
+  },
+  methods: {
+    pageTouchStart(e) {
+      wx.createSelectorQuery()
+        .select('#' + this.elementId)
+        .boundingClientRect(function(rect) {
+          let top = rect.top;
+          if (top >= 0) {
+            try {
+              let touch = e.touches[0]; //获取第一个触点
+              //记录触点初始位置
+              pageTouchStartY = Number(touch.pageY); //页面触点Y坐标
+              isPageTouchStart = true;
+            } catch (err) {
+              console.log(err.message);
+            }
+          }
+        })
+        .exec();
+    },
+    pageTouchMove(e) {
+      if (!isPageTouchStart) {
+        return;
+      }
+      try {
+        let touch = e.touches[0], //获取第一个触点
+          y = Number(touch.pageY); //页面触点Y坐标
+        let touchLength = (y - pageTouchStartY);
+        if (touchLength >= 0) {
+          this.picScale = (touchLength * 2 + picHeight) / picHeight;
+        } else {
+          this.picScale = 1;
+        }
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    pageTouchEnd(e) {
+      isPageTouchStart = false;
+      this.picScale = 1;
+    }
+  }
+});
+</script>

+ 75 - 0
src/components/common/page-title-view-scroll.wpy

@@ -0,0 +1,75 @@
+<style lang="less">
+  .page-top {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    background-color: transparent;
+    z-index: 1;
+
+    .page-title {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      height: 48rpx;
+      line-height: 48rpx;
+      font-size: 32rpx;
+      color: #ffffff;
+    }
+  }
+</style>
+
+<template>
+  <div class="page-top" :style="{'top': pageTopBarTop + 'px'}">
+    <slot></slot>
+    <div class="page-title">
+      <span v-show="isViewPageTitle" :style="{'color': pageTitleColor}">{{pageTitle}}</span>
+    </div>
+  </div>
+</template>
+
+<script>
+  import wepy from '@wepy/core';
+
+  let menuButtonBoundingClientRect;
+  wepy.component({
+    props: {
+      pageTitle: String,
+      pageTitleColor: String,
+      observeTarget: String
+    },
+    data: {
+      isViewPageTitle: false,
+      pageTopBarTop: 0
+    },
+    ready() {
+      menuButtonBoundingClientRect = wx.getMenuButtonBoundingClientRect();
+      this.pageTopBarTop =
+        menuButtonBoundingClientRect.top +
+        menuButtonBoundingClientRect.height / 2;
+      this.observePageTitle();
+    },
+    methods: {
+      observePageTitle() {
+        let top = menuButtonBoundingClientRect.top + menuButtonBoundingClientRect.bottom + menuButtonBoundingClientRect.height;
+        var query = wx.createSelectorQuery();
+        query.select(this.observeTarget).boundingClientRect();
+        query.exec(res => {
+          top -= res[0].height;
+          wx.createIntersectionObserver()
+            .relativeToViewport({
+              top: -top
+            })
+            .observe(this.observeTarget, res => {
+              if (res.intersectionRatio === 0) {
+                this.isViewPageTitle = true;
+              } else {
+                this.isViewPageTitle = false;
+              }
+            });
+        });
+      }
+    }
+  });
+</script>

+ 120 - 0
src/components/common/page-top-bar-custom.wpy

@@ -0,0 +1,120 @@
+<style lang="less">
+.component-page-top-bar-custom {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  padding-bottom: 16px;
+  box-sizing: border-box;
+  z-index: 3;
+  .page-top-bar {
+    position: absolute;
+    bottom: 16px;
+    left: 0;
+    right: 0;
+    min-height: 30px;
+    .icon {
+      position: absolute;
+      left: 32rpx;
+      top: 0;
+      width: 76rpx;
+      height: 30px;
+      line-height: 30px;
+      .icon-content {
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+      .icon_home {
+        width: 64rpx;
+        height: 64rpx;
+      }
+      .icon_define {
+        width: 24rpx;
+        height: 45rpx;
+      }
+    }
+    .title {
+      font-weight: 500;
+      font-size: 34rpx;
+      line-height: 48rpx;
+      color: rgba(0, 0, 0, 0.8);
+      text-align: center;
+    }
+  }
+}
+</style>
+
+<template>
+  <div
+    class="component-page-top-bar-custom"
+    :style="{'height': pageTopBarHeight + 'px', 'background-color': bgColor}"
+  >
+    <div class="page-top-bar">
+      <slot></slot>
+      <div v-if="icon || iconType" class="icon" v-on:click="emitIconClick">
+        <block v-if="iconType == 'home' || isFirstPage">
+          <image class="icon_home icon-content" src="{{h5StaticPath}}/icon_navigate_home.svg" />
+        </block>
+        <block v-else>
+          <image class="icon_define icon-content" src="{{icon}}" />
+        </block>
+      </div>
+      <div class="title" v-if="title">{{title}}</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+const BottomHeight = 16;
+('单位px');
+wepy.component({
+  props: {
+    bgColor: {
+      type: String,
+      default: 'transparent'
+    },
+    iconType: String,
+    icon: String,
+    iconClickedEmitId: String,
+    title: String
+  },
+  ready() {
+    let menuButtonBoundingClientRect = wx.getMenuButtonBoundingClientRect();
+
+    // 加上右上角胶囊按钮的边框2
+    this.pageTopBarHeight =
+      menuButtonBoundingClientRect.top +
+      menuButtonBoundingClientRect.height +
+      BottomHeight;
+    this.$emit('component-page-top-bar-custom-ready', this.pageTopBarHeight);
+
+    let pages = getCurrentPages();
+    if (pages.length <= 1) {
+      this.isFirstPage = true;
+    }
+  },
+  data: {
+    isFirstPage: false,
+    pageTopBarTop: 0,
+    pageTopBarHeight: 72,
+    h5StaticPath: config.h5StaticPath
+  },
+  methods: {
+    emitIconClick(e) {
+      if (this.iconClickedEmitId) {
+        this.$emit(this.iconClickedEmitId);
+      } else {
+        if (this.isFirstPage) {
+          wx.redirectTo({ url: '/pages/homev2' });
+        } else {
+          wx.navigateBack();
+        }
+      }
+    }
+  }
+});
+</script>

+ 132 - 0
src/components/common/page-top-bar.wpy

@@ -0,0 +1,132 @@
+<style lang="less">
+.component-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  z-index: 100;
+  .page-top-bar {
+    position: relative;
+    .title {
+      position: absolute;
+      width: 100%;
+      bottom: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 36rpx;
+      color: #ffffff;
+    }
+    .icon {
+      position: absolute;
+      left: 32rpx;
+      bottom: 0;
+      width: 24rpx;
+      height: 45rpx;
+      .icon_home {
+        width: 76rpx;
+        height: 56rpx;
+      }
+      .icon_define {
+        width: 24rpx;
+        height: 45rpx;
+      }
+      &.white {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 42px;
+        height: 32px;
+        background: rgba(255, 255, 255, 0.596637);
+        border: 0.5px solid rgba(151, 151, 151, 0.201002);
+        box-sizing: border-box;
+        border-radius: 25px;
+        .icon_define {
+          width: 8px;
+          height: 16px;
+        }
+      }
+    }
+  }
+}
+</style>
+
+<template>
+  <div class="component-container"
+       :style="{'padding-top': pageTopBarTop + 'px','padding-bottom': pageTopBarBottom + 'px', 'height': pageTopBarHeight + 'px', 'background-color': bgColor}">
+    <div class="page-top-bar"
+         :style="{height: pageTopBarHeight + 'px'}">
+      <div class="title"
+           :style="{'color': titleColor, height: pageTopBarHeight + 'px'}">
+        <span v-show="title">{{title}}</span>
+      </div>
+      <div v-if="icon || iconType"
+           class="icon"
+           v-on:click="emitIconClick"
+           :style="{bottom: bottom+'px'}"
+           :class="{'white': iconBg === 'white'}">
+        <block v-if="iconType == 'home'">
+          <image class="icon_home"
+                 src="{{h5StaticPath}}/static/icon_home.svg" />
+        </block>
+        <block v-else>
+          <image class="icon_define"
+                 src="{{icon}}" />
+        </block>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+wepy.component({
+  props: {
+    title: String,
+    iconType: String,
+    icon: String,
+    // white
+    iconBg: String,
+    bgColor: {
+      type: String,
+      default: 'transparent'
+    },
+    titleColor: {
+      type: String,
+      default: '#fff'
+    },
+    pageTopBarBottom:{
+      type: Number,
+      default: 0
+    },
+    iconClickedEmitId: String
+  },
+  ready() {
+    let menuButtonBoundingClientRect = wx.getMenuButtonBoundingClientRect();
+    this.pageTopBarHeight = menuButtonBoundingClientRect.height;
+
+    // 加上右上角胶囊按钮的边框2
+    this.pageTopBarTop = menuButtonBoundingClientRect.top;
+    if(!this.iconBg) {
+      this.bottom = (this.pageTopBarHeight - 22) / 2
+    }
+  },
+  data: {
+    pageTopBarTop: 0,
+    pageTopBarHeight: 48,
+    bottom: 0,
+    h5StaticPath: config.h5StaticPath,
+  },
+  methods: {
+    emitIconClick(e) {
+      if (this.iconClickedEmitId) {
+        this.$emit(this.iconClickedEmitId);
+      } else {
+        wx.navigateBack();
+      }
+    }
+  }
+});
+</script>

+ 144 - 0
src/components/common/panel-can-close.wpy

@@ -0,0 +1,144 @@
+<style lang="less">
+.component-panel-can-close-wrapper {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
+  background-color: rgba(0, 0, 0, 0.4);
+}
+.component-panel-can-close {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 3;
+  box-shadow: 0px 0px 4px rgba(1, 1, 51, 0.02),
+    0px 0px 40px rgba(1, 1, 51, 0.08);
+  border-radius: 32rpx 32rpx 0 0;
+  min-height: 400rpx;
+  animation: slideUp 0.3s linear;
+  -webkit-animation: slideUp 0.3s linear;
+  background-color: #fff;
+}
+
+.component-panel-can-close .content {
+  position: relative;
+}
+
+.component-panel-can-close .closed-wrapper {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 112rpx;
+  height: 112rpx;
+  z-index: 3;
+}
+
+.component-panel-can-close .closed {
+  position: absolute;
+  top: 24rpx;
+  right: 24rpx;
+  background: rgba(2, 2, 82, 0.03);
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}
+
+.icon-close-image {
+  width: 20rpx;
+  height: 20rpx;
+  display: block;
+}
+</style>
+
+<template>
+  <div
+    class="component-panel-can-close-wrapper"
+    style="{{'display:'+ (show?'block;': 'none;')}}"
+    v-if="show"
+  >
+    <div
+      class="component-panel-can-close"
+      style="{{('transform:translateY('+translateY+'px)'+';')}}"
+      v-on:touchmove.stop="pageTouchMove"
+      v-on:touchstart="pageTouchStart"
+      v-on:touchend="pageTouchEnd"
+    >
+      <div class="content">
+        <div class="closed-wrapper" v-on:click="closePanel">
+          <div class="closed">
+            <image class="icon-close-image" :src="h5StaticPath+'/icon_close_gray.png'" />
+          </div>
+        </div>
+        <slot></slot>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+let pageTouchStartY = 0;
+wepy.component({
+  data: {
+     h5StaticPath: config.h5StaticPath,
+    showPanel: false,
+    translateY: 0
+  },
+  props: {
+    show: Boolean,
+    containerId: String
+  },
+  watch:{
+     show: function(newShow, oldShow) {
+      if (oldShow !== newShow) {
+        this.show = newShow;
+      }
+    }
+  },
+  methods: {
+    closePanel() {
+     this.show = false;
+     this.$emit('compnent-panel-close-close');
+    },
+    pageTouchStart(e) {
+      try {
+        let touch = e.touches[0]; //获取第一个触点
+        //记录触点初始位置
+        pageTouchStartY = Number(touch.pageY); //页面触点Y坐标
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    pageTouchMove(e) {
+      try {
+        let touch = e.touches[0], //获取第一个触点
+          y = Number(touch.pageY); //页面触点Y坐标
+        let touchLength = parseInt(y - pageTouchStartY);
+        // 下滑
+        if (touchLength >= 0) {
+         this.translateY =  touchLength;
+        }
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    pageTouchEnd(e) {
+      if (this.translateY >= 200) {
+        this.closePanel();
+      }
+      this.translateY =  0;
+    },
+    stopPropagation() {
+      // 空方法,阻止冒泡用
+    }
+  }
+});
+</script>

+ 314 - 0
src/components/common/panel-step.wpy

@@ -0,0 +1,314 @@
+<style lang="less">
+.component-step-panel-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
+  background-color: rgba(0, 0, 0, 0.4);
+}
+.component-step-panel-content {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 3;
+  box-shadow: 0px 0px 4px rgba(1, 1, 51, 0.02),
+    0px 0px 40px rgba(1, 1, 51, 0.08);
+  border-radius: 32rpx 32rpx 0 0;
+  background: #fff;
+  &.hasAnimation {
+    transition: transform 0.3s ease-out;
+  }
+}
+
+.component-step-panel {
+  position: relative;
+}
+.component-step-panel .pre-step-wrapper {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 112rpx;
+  width: 112rpx;
+  z-index: 3;
+}
+.component-step-panel .step-content {
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+  overflow-x: hidden;
+  transition: transform 0.3s ease-out;
+}
+
+.component-step-panel .step-item {
+  overflow: hidden;
+  display: block;
+}
+
+.component-step-panel .pre-step {
+  position: absolute;
+  top: 24rpx;
+  left: 24rpx;
+  width: 48rpx;
+  height: 48rpx;
+  background: rgba(2, 2, 82, 0.03);
+  border-radius: 50%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+.component-step-panel .pre-step .icon-arrow {
+  width: 16rpx;
+  height: 24rpx;
+}
+
+.component-step-panel .closed-wrapper {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 112rpx;
+  height: 112rpx;
+  z-index: 3;
+}
+
+.component-step-panel .closed {
+  position: absolute;
+  top: 24rpx;
+  right: 24rpx;
+  background: rgba(2, 2, 82, 0.03);
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}
+
+.component-step-panel .icon-close-image {
+  width: 20rpx;
+  height: 20rpx;
+  display: block;
+}
+</style>
+
+<template>
+  <div
+    class="component-step-panel-mask"
+    v-if="show"
+    v-on:touchmove="stopPropagation"
+    v-on:touchstart="stopPropagation"
+    v-on:touchend="stopPropagation"
+  >
+    <div
+      class="component-step-panel-content"
+      :class="{'hasAnimation': hasAnimation}"
+      style="{{('transform:translateY('+translateY+')'+';')}}"
+      v-on:touchmove="pageTouchMove"
+      v-on:touchstart="pageTouchStart"
+      v-on:touchend="pageTouchEnd"
+    >
+      <div class="component-step-panel">
+        <div class="pre-step-wrapper" v-on:click.stop="goPreStep" v-if="step!=1">
+          <div class="pre-step">
+            <image class="icon-arrow" :src="h5StaticPath+'/icon_back_gray_weight.png'" />
+          </div>
+        </div>
+        <div class="closed-wrapper" @click="closePanel">
+          <div class="closed">
+            <image class="icon-close-image" :src="h5StaticPath+'/icon_close_gray.png'" />
+          </div>
+        </div>
+        <div
+          class="step-content"
+          :style="{width:(screenWidth*steps+'px'),transform:('translate('+(-(step-1)*screenWidth)+'px,'+(0)+'px)')}"
+        >
+          <div
+            v-for="(item, index) in stepsArr"
+            :key="index"
+            class="{{'step-item '+(item)}}"
+            style="{{'width:'+screenWidth+'px'}}"
+          >
+            <slot :name="item"></slot>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+let pageTouchStartY = 0;
+let moveY = 0;
+wepy.component({
+  options: {
+    multipleSlots: true // 在组件定义时的选项中启用多slot支持
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath,
+    stepsArr: [],
+    stepHeights: [],
+    contanierHeight: 0,
+    translateY: '100%',
+    translateX: 0,
+    screenWidth: 375,
+    oldStep: 0,
+    hiddenOldStep: false,
+    hasAnimation: false
+  },
+  props: {
+    steps: Number,
+    step: Number,
+    panelHeightHasChange: Boolean,
+    show: Boolean,
+    containerId: String
+  },
+  ready() {
+    this.generateStepArrar();
+    // 防止内部元素未加载完毕
+    setTimeout(() => {
+      this.getStepHieght();
+    }, 20);
+    this.setStepContanierWidth();
+  },
+  watch: {
+    step: function(newStep, oldStep) {
+      if (oldStep !== newStep) {
+        //更新时候重新设置值
+        // 当首次更改的时候,第一次设置值,此时元素还未加载完,故加上延迟
+        if (!oldStep) {
+          setTimeout(() => {
+            this.getStepHieght();
+          }, 200);
+        } else {
+          this.getStepHieght();
+        }
+      }
+    },
+    panelHeightHasChange: function(newPanelHeightChange, oldPanelHeightChange) {
+      if (oldPanelHeightChange == false && newPanelHeightChange == true) {
+        this.getStepHieght(); //更新时候重新设置值
+      }
+    },
+    show: function(newShow, oldShow) {
+      if (oldShow !== newShow) {
+        this.show = newShow;
+        // 当首次更改的时候,第一次设置值,此时元素还未加载完,故加上延迟
+        if (oldShow == null) {
+          setTimeout(() => {
+            this.getStepHieght();
+          }, 200);
+        } else {
+          this.getStepHieght();
+        }
+
+        if (!oldShow && newShow) {
+          this.hasAnimation = true;
+          setTimeout(() => {
+            this.hasAnimation = false;
+          }, 300);
+        }
+      }
+    }
+  },
+  methods: {
+    generateStepArrar() {
+      const { steps } = this;
+      let stepsArr = [];
+      for (let i = 1; i <= steps; i++) {
+        let stepClassName = 'step' + i;
+        stepsArr.push(stepClassName);
+      }
+      this.stepsArr = stepsArr;
+    },
+    getStepHieght() {
+      const { show, step } = this;
+      if (!show) {
+        return;
+      }
+      this.setStepTranslste();
+    },
+    setStepTranslste() {
+      const { step, containerId } = this;
+      if (!this.stepsArr.length) {
+        this.generateStepArrar();
+      }
+      let { stepsArr } = this;
+      let selector = this.$wx
+        .createSelectorQuery()
+        .select('.step-content')
+        .boundingClientRect();
+      let stepClassName = stepsArr[step - 1];
+      selector = selector.select(`.${stepClassName}`).boundingClientRect();
+
+      selector.exec(ret => {
+        this.translateY = ret[0].height - ret[1].height + 'px';
+      });
+    },
+    setStepContanierWidth() {
+      wx.getSystemInfo({
+        success: res => {
+          let screenWidth = res.screenWidth;
+          this.screenWidth = screenWidth;
+        }
+      });
+    },
+    goPreStep() {
+      this.$emit('component-panel-step-back');
+    },
+    closePanel() {
+      this.show = false;
+      this.$emit('component-panel-step-close');
+    },
+    pageTouchStart(e) {
+      try {
+        let touch = e.touches[0]; //获取第一个触点
+        //记录触点初始位置
+        pageTouchStartY = Number(touch.pageY); //页面触点Y坐标
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    pageTouchMove(e) {
+      try {
+        let touch = e.touches[0], //获取第一个触点
+          y = Number(touch.pageY); //页面触点Y坐标
+
+        let touchLength = parseInt(y - pageTouchStartY);
+        pageTouchStartY = y;
+        moveY = moveY + touchLength;
+        // 下滑
+        let oldTranslateY = parseInt(
+          this.translateY.substring(0, this.translateY.length - 2)
+        );
+        if (touchLength > 0) {
+          this.translateY = oldTranslateY + touchLength + 'px';
+        } else {
+          // 重新计算,防止下滑后再上滑,更改了值
+          // this.getStepHieght();
+        }
+      } catch (err) {}
+    },
+    pageTouchEnd(e) {
+      if (!moveY) {
+        return;
+      }
+      if (moveY >= 200) {
+        this.closePanel();
+      } else {
+        this.getStepHieght();
+      }
+      moveY = 0;
+    },
+    stopPropagation() {
+      // 空方法,阻止冒泡用
+    }
+  }
+});
+</script>

+ 160 - 0
src/components/common/scroller-x.wpy

@@ -0,0 +1,160 @@
+<style lang="less">
+.component-scroller-x {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  white-space: nowrap;
+}
+
+.component-scroller-x .scroller-content {
+  white-space: nowrap;
+  display: inline-block;
+}
+
+.scroller-bar {
+  position: absolute;
+  height: 8rpx;
+  background: rgba(0, 0, 0, 0.3);
+  border-radius: 4rpx;
+  /* opacity: 0; */
+  transition: opacity 2s ease 0s;
+  left: 0;
+  bottom: 0;
+}
+</style>
+
+<template>
+  <div
+    class="{{containerId+'-scroller-x' + ' component-scroller-x'}}"
+    v-on:touchmove="touchMove"
+    v-on:touchstart="touchStart"
+    v-on:touchend="touchEnd"
+  >
+    <div class="scroller-content" style="{{'transform:translateX('+translateX+'px);'}}">
+      <slot></slot>
+    </div>
+    <div
+      class="scroller-bar"
+      style="{{'width:'+barWidth+'px;opacity:' + (showScrollBar?'1':'0')+';'+'transform:translateX('+scrollBartranslateX*2+'rpx);'}}"
+      v-if="hasScrollBar"
+    ></div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+let Timer = null;
+let startX = 0;
+let startTmie;
+let scale;
+wepy.component({
+  data: {
+    barWidth: 0,
+    hasScrollBar: true,
+    showScrollBar: false,
+    translateX: 0,
+    scrollBartranslateX: 0,
+    maxTranslate: 0
+  },
+  props: {
+    canMove: {
+      type: Boolean,
+      default: true
+    },
+    scrollLeft: {
+      type: Number,
+      default: 0
+    },
+    containerId: String
+  },
+  ready() {
+    const { containerId } = this;
+    // 防止内容还未加载完毕,获取内容宽度是0,计算有误
+    setTimeout(() => {
+      this.$wx
+        .createSelectorQuery()
+        .select(`.${containerId}-scroller-x`)
+        .boundingClientRect()
+        .select(`.${containerId}-scroller-x .scroller-content`)
+        .boundingClientRect()
+        .exec(ret => {
+          let wrapperWidth = ret[0].width;
+          let listWidth = ret[1].width;
+          scale = wrapperWidth / listWidth;
+          let maxTranslate = wrapperWidth - listWidth;
+          this.barWidth = wrapperWidth * scale;
+          this.hasScrollBar = maxTranslate < 0;
+          this.maxTranslate = maxTranslate;
+          this.setScrollLeft(this.scrollLeft);
+        });
+    }, 200);
+  },
+  watch: {
+    scrollLeft: function(newScrollLeft, oldScrollLeft) {
+      if (oldScrollLeft !== newScrollLeft && this.maxTranslate !== 0) {
+        this.setScrollLeft(newScrollLeft);
+      }
+    }
+  },
+  methods: {
+    setScrollLeft(scrollLeft) {
+      const { maxTranslate } = this;
+      let translateX = scrollLeft < maxTranslate ? maxTranslate : scrollLeft; //如果超过了边界,设为最大值为边界
+      if (scrollLeft == null) {
+        return;
+      }
+      this.translateX = translateX;
+    },
+    touchStart(e) {
+      if (!this.judgeCanMove()) {
+        return;
+      }
+      if (Timer) {
+        clearInterval(Timer); //当手指按下的时候清除掉MTween中的定时器,目的就是当手指按下的时候,如果动画还在执行,让其停止
+      }
+      startX = Number(e.touches[0].pageX);
+      startTmie = new Date().getTime();
+      // 产品要求隐藏滚动条
+      // this.showScrollBar();
+    },
+    touchMove(e) {
+      if (!this.judgeCanMove()) {
+        return;
+      }
+      let touch = e.touches[0],
+        x = Number(touch.pageX);
+      let touchLength = parseInt(x - startX);
+      startX = x;
+
+      let translateX = this.translateX + touchLength;
+
+      const { maxTranslate } = this;
+      if (Math.abs(translateX) > Math.abs(maxTranslate)) {
+        // this.hideScrollBar();
+        return;
+      }
+
+      if (translateX > 0) {
+        // console.log('h滑动了最左边');
+        return;
+      }
+      this.translateX = translateX;
+      this.scrollBartranslateX = -translateX * scale;
+      this.$emit('componet-scroller-x-scroll', translateX);
+    },
+    touchEnd() {
+      this.hideScrollBar();
+      this.$emit('componet-scroller-x-scrollend');
+    },
+    showScrollBar() {
+      this.showScrollBar = true;
+    },
+    hideScrollBar() {
+      this.showScrollBar = false;
+    },
+    judgeCanMove() {
+      return this.canMove && this.hasScrollBar;
+    }
+  }
+});
+</script>

+ 65 - 0
src/components/common/select-switch.wpy

@@ -0,0 +1,65 @@
+<style lang="less">
+.component-select-switch {
+  width: 192rpx;
+  height: 64rpx;
+  background: rgba(2, 2, 82, 0.05);
+  border-radius: 96rpx;
+}
+
+.component-select-switch .item {
+  display: inline-block;
+  text-align: center;
+  width: 50%;
+  font-size: 24rpx;
+  line-height: 64rpx;
+  color: rgba(13, 13, 61, 0.86);
+}
+
+.component-select-switch .item.selected {
+  background: linear-gradient(0deg, #ffffff, #ffffff);
+  border: 2rpx solid rgba(2, 2, 82, 0.05);
+  box-sizing: border-box;
+  box-shadow: 0rpx 4rpx 4rpx rgba(1, 1, 51, 0.03);
+  border-radius: 96rpx;
+}
+</style>
+
+<template>
+  <view class="{{(('component-select-switch') + ' ' +(className))}}">
+    <block v-for="(item, idx) in options">
+      <view
+        class="{{(('item') + ' ' +((selected == item.value)?'selected': ''))}}"
+        data-value="{{item.value}}"
+        @tap.stop="changeOptions"
+      >{{item.text}}</view>
+    </block>
+  </view>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+wepy.component({
+  data: {
+    // selectedOption: ''
+  },
+  props: {
+    options: [],
+    selected: '',
+    className: ''
+  },
+  didMount() {
+    // this.selectedOption = this.selected;
+  },
+  didUpdate() {},
+  didUnmount() {},
+  methods: {
+    changeOptions(e) {
+      let value = e.target.dataset.value;
+    //   this.selectedOption = selected;
+      this.$emit('component-select-switch-change', value); 
+    }
+  }
+});
+</script>

+ 235 - 0
src/components/common/slider-panel-v2.wpy

@@ -0,0 +1,235 @@
+<style lang="less">
+.slider-panel-v2 {
+  position: absolute;
+  left: 0;
+  right: 0;
+  padding-top: 30rpx;
+  bottom: 0;
+  text-align: center;
+  background: #f8f8f9;
+  border-radius: 32rpx 32rpx 0 0;
+  box-shadow: 0px 0px 4px rgba(1, 1, 51, 0.04),
+    0px 0px 40px rgba(1, 1, 51, 0.12);
+  box-sizing: border-box;
+}
+
+.slider-panel-v2 .bar {
+  width: 88rpx;
+  height: 8rpx;
+  border-radius: 4rpx;
+  background: rgba(0, 0, 0, 0.1);
+  margin: 0 auto;
+}
+</style>
+
+<template>
+  <view
+    class="slider-panel-v2"
+    data-source="slider"
+    style="{{'height: '+expandPanelHeight+'rpx;'+ (translateY!==0?'transform:translateY('+translateY+'rpx)':'')}}"
+    v-on:touchstart="panelTouchStart"
+    v-on:touchmove="panelTouchMove"
+    v-on:touchend="panelTouchEnd"
+  >
+    <view class="bar" v-if="hasTopBar"></view>
+    <view class="content-container">
+      <slot></slot>
+    </view>
+  </view>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+// 注意单位都是rpx
+// 在收缩/伸展两状态之间切换时至少要滑动的距离
+const downTouchSensitivity = 320 * 2;
+const upTouchSensitivity = 120 * 2;
+let pageTouchStartY = 0;
+let touchLength;
+// 收缩状态下的卡片的TranslateY
+let diffPanelHeight = 0;
+let screenInfo = {};
+const standHeight = 667;
+const shieldHeight = 30; //屏蔽底部30以内面板不滑动,防止全面屏手势导致界面异常
+
+wepy.component({
+  props: {
+    foldStatus: {
+      type: String,
+      default: 'fold'
+    },
+    // 伸展状态下的面板高度
+    expandPanelHeight: {
+      type: Number,
+      default: 399 * 2
+    },
+    // 收缩状态下的面板高度
+    foldPanelHeight: {
+      type: Number,
+      default: 88 * 2
+    },
+    isInCustomTopbar: {
+      type: Boolean,
+      default: false
+    },
+    canSlide: {
+      type: Boolean,
+      default: true
+    },
+    hasTopBar: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data: {
+    translateY: 399*2
+  },
+  ready() {
+    diffPanelHeight = this.expandPanelHeight - this.foldPanelHeight;
+    if (this.foldStatus == 'fold') {
+      this.translateY = diffPanelHeight;
+    } else if (this.foldStatus == 'expand') {
+      this.translateY = 0;
+    }
+  },
+  watch: {
+    foldStatus: function(newFoldStatus, oldFoldStatus) {
+      if (
+        oldFoldStatus !== newFoldStatus &&
+        (this.translateY == 0 || this.translateY == diffPanelHeight)
+      ) {
+        if (newFoldStatus === 'fold') {
+          this.translateY = diffPanelHeight;
+        } else {
+          this.translateY = 0;
+        }
+      }
+    }
+  },
+  methods: {
+    getScreenInfo() {
+      if (screenInfo && screenInfo.screenWidth && screenInfo.screenHeight) {
+        return Promise.resolve(screenInfo);
+      }
+      return new Promise((resolve, reject) => {
+        wx.getSystemInfo({
+          success: res => {
+            screenInfo.screenHeight = res.screenHeight;
+            screenInfo.screenWidth = res.screenWidth;
+            resolve({
+              screenWidth: res.screenWidth,
+              screenHeight: res.screenHeight
+            });
+          }
+        });
+      });
+    },
+    juchageConmove(e) {
+      //从长按按钮冒泡上来的
+      //   if (e.target.source && e.target.source.indexOf('btn') !== -1) {
+      //     return false;
+      //   } else {
+      //     return true;
+      //   }
+      return this.canSlide;
+    },
+    startNotFromBottom(pageStartY) {
+      return this.getScreenInfo().then(res => {
+        let fullHeight = res.screenHeight;
+        let bottomY = fullHeight; //获取屏幕高度
+        if (!this.isInCustomTopbar) {
+          bottomY = bottomY - 72; //不是自定义高度,判断的时候,pageStartY 范围在screenHeight-72(默认导航栏高度) 到0区间
+        }
+        if (
+          pageStartY <
+          bottomY - Number((fullHeight / standHeight) * shieldHeight)
+        ) {
+          return Promise.resolve();
+        } else {
+          return Promise.reject();
+        }
+      });
+    },
+    panelTouchStart(e) {
+      if (!this.juchageConmove(e)) {
+        return;
+      }
+      try {
+        let touchY = Number(e.touches[0].pageY); //获取第一个触点
+        this.startNotFromBottom(touchY).then(() => {
+          //记录触点初始位置
+          pageTouchStartY = touchY; //页面触点Y坐标
+        });
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    panelTouchMove(e) {
+      //   console.log('move control panel', e);
+      if (!this.juchageConmove(e)) {
+        return;
+      }
+      if (!pageTouchStartY) {
+        return;
+      }
+
+      try {
+        let touch = e.touches[0], //获取第一个触点
+          y = Number(touch.pageY); //页面触点Y坐标
+        touchLength = parseInt(y - pageTouchStartY);
+        pageTouchStartY = y;
+        let newTranslateY = touchLength * 2 + this.translateY;
+        if (touchLength == 0) {
+          //在停止中不计算并且不做任何处理
+          return;
+        }
+        if (newTranslateY > 0 && newTranslateY < diffPanelHeight) {
+          this.translateY = newTranslateY;
+        }
+
+        if (newTranslateY > diffPanelHeight) {
+          this.foldPanel();
+        }
+
+        if (touchLength < 0 || newTranslateY < 0) {
+          this.expandPanel();
+        }
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    panelTouchEnd(e) {
+      if (!pageTouchStartY) {
+        return;
+      }
+      if (touchLength > 0) {
+        //下滑到一定距离松手就可以折叠,不够保持展开原装,同时设置位置
+        if (this.translateY >= this.expandPanelHeight - downTouchSensitivity) {
+          this.foldPanel();
+          this.translateY = diffPanelHeight;
+        } else {
+          this.translateY = 0;
+        }
+      } else {
+        //上滑到一定距离就松手就展开了,不够才折叠,因为一旦上滑就展开,但是偏移量不对,故再次甚至
+        if (this.translateY <= this.expandPanelHeight - upTouchSensitivity) {
+          // this.expandPanel();
+          this.translateY = 0;
+        } else {
+          this.foldPanel();
+          this.translateY = diffPanelHeight;
+        }
+      }
+      touchLength = 0;
+      pageTouchStartY = 0;
+    },
+    foldPanel() {
+      this.$emit('component-slider-panel-v2-fold');
+    },
+    expandPanel() {
+      this.$emit('component-slider-panel-v2-expand');
+    }
+  }
+});
+</script>

+ 278 - 0
src/components/common/slider-panel.wpy

@@ -0,0 +1,278 @@
+<style lang="less">
+@keyframes opacity_processing {
+  from {
+    background: rgba(0, 0, 0, 0);
+  }
+  to {
+    background: rgba(0, 0, 0, 0.4);
+  }
+}
+
+@keyframes hoverbar_opacity_hide {
+  from {
+    opacity: 1;
+  }
+  to {
+    opacity: 0;
+  }
+}
+
+@keyframes hoverbar_opacity_show {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes hoverbar_width_show {
+  from {
+    width: 80rpx;
+  }
+  to {
+    width: 100%;
+  }
+}
+
+@keyframes hoverbar_width_hide {
+  from {
+    width: 100%;
+  }
+  to {
+    width: 80rpx;
+  }
+}
+.component-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.4);
+  z-index: 29; //因为导航栏的z-index 为11 故为了遮住导航栏
+  animation: opacity_processing 0.3s linear;
+  .panel-container {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    animation: slideUp 0.3s linear;
+    animation-fill-mode:forwards;
+    display: flex;
+    flex-direction: column;
+    .content-area-wrapper {
+      flex: 1;
+      box-shadow: 0px -4px 12px rgba(1, 1, 51, 0.03),
+        0px 0px 4px rgba(1, 1, 51, 0.03);
+      background-color: white;
+      border-top-left-radius: 24px;
+      border-top-right-radius: 24px;
+    }
+    .slider-icon {
+      margin: 15px auto 0;
+      width: 44px;
+      height: 4px;
+      border-radius: 4px;
+      background-color: rgba(2, 2, 82, 0.12);
+    }
+    .icon-close {
+      position: absolute;
+      top: 32rpx;
+      right: 32rpx;
+      width: 48rpx;
+      height: 48rpx;
+      z-index: 2;
+    }
+    .hover-bar-wrapper {
+      display: flex;
+      flex-direction: row;
+      justify-content: center;
+      width: 100%;
+      padding: 0 50rpx;
+      box-sizing: border-box;
+      margin: 0 auto;
+      overflow: hidden;
+      animation: hoverbar_opacity_show 0.4s ease-in-out;
+      &.hide {
+        opacity: 0;
+        animation: hoverbar_opacity_hide 0.4s ease-in-out;
+        .hover-bar {
+          width: 80rpx;
+          animation: hoverbar_width_hide 0.3s ease-in-out;
+        }
+      }
+      .hover-bar {
+        width: 80rpx;
+        animation: hoverbar_width_show 0.3s ease-in-out 0.2s;
+        animation-fill-mode:forwards;
+      }
+    }
+  }
+}
+.component-container-hidden {
+  height: 0;
+}
+</style>
+
+<template>
+  <div
+    class="component-container"
+    v-on:touchstart="pageTouchStart"
+    v-on:touchmove.stop="pageTouchMove"
+    v-on:touchend="pageTouchEnd"
+    v-on:click="hidePanel"
+    :style="{'background': maskBackground}"
+  >
+    <div
+      class="panel-container"
+      style="{{(top?('top:'+(top+'rpx;')):'') + (height?('height:'+(height+'rpx;')):'') +'animation-fill-mode:' + (animationDone?'none;': 'forwards;')+(animationDone?('transform:translateY('+translateY+'px)'):'')}}"
+      v-on:click.stop="stopPropagation"
+    >
+      <div
+        class="hover-bar-wrapper"
+        v-if="hasHoverBar"
+        :class="{'hide': translateY>hideHoverBarTranslateY || !showHoverBar}"
+      >
+        <div class="hover-bar">
+          <slot name="hover"></slot>
+        </div>
+      </div>
+      <div class="content-area-wrapper">
+        <div class="slider-icon" v-if="showSliderIcon"></div>
+        <image
+          v-if="showClose"
+          class="icon-close"
+          src="{{h5StaticPath}}/static/icon-close.svg"
+          @click="hidePanel"
+        />
+        <div class="content-container">
+          <slot></slot>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+let pageTouchStartY = 0;
+let isPageTouchStart = false;
+let componentTop = 0;
+let HideHoverBarTranslateY = 300;
+wepy.component({
+  options: {
+    multipleSlots: true // 在组件定义时的选项中启用多slot支持
+  },
+  props: {
+    // 注意同一个页面使用多个该组件时该值是否要保持唯一
+    slidDoneEmitId: String,
+    top: String,
+    height: String,
+    showClose: {
+      type: Boolean,
+      default: false
+    },
+    hasHoverBar: {
+      type: Boolean,
+      default: false
+    },
+    showHoverBar: {
+      type: Boolean,
+      default: false
+    },
+    hideTovebarEmitId: {
+      type: String,
+      default: ''
+    },
+    showSliderIcon: {
+      type: Boolean,
+      default: true
+    }
+  },
+  ready() {
+    // 当动画执行完毕, 进行标记,更改样式, 此时才使用滑动的transform, 本是0.3s担心动画性能慢,跑的慢了点故加长时间为350s
+    setTimeout(() => {
+     this.animationDone = true;
+    }, 350);
+  },
+  attached() {
+    //获取内容高度
+    let selector = this.$wx
+      .createSelectorQuery()
+      .select('.component-container .content-area-wrapper')
+      .boundingClientRect()
+      .exec(ret => {
+        this.modelHeight = ret[0].height;
+      });
+  },
+  data: {
+    hideHoverBarTranslateY: HideHoverBarTranslateY,
+    translateY: 0,
+    h5StaticPath: config.h5StaticPath,
+    maskBackground: 'rgba(0, 0, 0, 0.4);',
+    modelHeight: 400, //整个模态框的高度
+    animationDone: false
+  },
+  methods: {
+    pageTouchStart(e) {
+      try {
+        let touch = e.touches[0]; //获取第一个触点
+        //记录触点初始位置
+        pageTouchStartY = Number(touch.pageY); //页面触点Y坐标
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    pageTouchMove(e) {
+      try {
+        let touch = e.touches[0], //获取第一个触点
+          y = Number(touch.pageY); //页面触点Y坐标
+        let touchLength = parseInt(y - pageTouchStartY);
+        // 下滑
+        if (touchLength >= 0) {
+          this.translateY = touchLength;
+          let opacity =
+            1 - Math.abs(this.translateY) / parseInt(this.modelHeight);
+          this.maskBackground = `rgba(0, 0, 0, ${0.4 * opacity});`;
+        }
+
+        if (touchLength > HideHoverBarTranslateY) {
+          this.$emit(this.hideTovebarEmitId, true);
+        } else {
+          this.$emit(this.hideTovebarEmitId, false);
+        }
+      } catch (err) {
+        console.log(err.message);
+      }
+    },
+    pageTouchEnd(e) {
+      if (this.translateY >= 200) {
+        this.hidePanel();
+      } else {
+        this.emitHideToverBar(false);
+      }
+      this.translateY = 0;
+      this.maskBackground = 'rgba(0, 0, 0,0.4)';
+    },
+    hidePanel() {
+      if (this.slidDoneEmitId) {
+        this.$emit(this.slidDoneEmitId);
+      }
+
+      this.emitHideToverBar(true);
+    },
+    emitHideToverBar(hideStatus) {
+      if (!this.hideTovebarEmitId) {
+        return;
+      }
+      this.$emit(this.hideTovebarEmitId, hideStatus);
+    },
+    stopPropagation() {
+      // 空方法,阻止冒泡用
+    }
+  }
+});
+</script>

+ 60 - 0
src/components/common/switch.wpy

@@ -0,0 +1,60 @@
+<style lang="less">
+.component-switch {
+  width: 80rpx;
+  height: 48rpx;
+  /* padding:0 4rpx; */
+  background-color: #EBEBF2;
+  display: inline-block;
+  box-sizing: border-box;
+  position: relative;
+  border-radius: 40rpx;
+  cursor: pointer;
+  transition: background-color 0.3s;
+}
+
+.component-switch .checked-circle {
+  position: absolute;
+  top: 50%;
+  left: 4rpx;
+  z-index: 1;
+  width: 40rpx;
+  height: 40rpx;
+  border-radius: 40rpx;
+  transform: translate(0, -50%);
+  background: #FFFFFF;
+  box-shadow: 0px 5rpx 6rpx rgba(0, 0, 0, 0.15), 0px 5rpx 2rpx rgba(0, 0, 0, 0.06);
+  transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
+}
+
+.component-switch.checked .checked-circle {
+  transform: translate(80%, -50%);
+}
+</style>
+
+<template>
+ <div class="component-switch" :class="{'checked': checked}" style="{{'background-color:'+(checked? activeColor||defaultActiveColor: inactiveColor||defaultInactiveColor)+';' }}"   v-on:click.stop="changChecked">
+  <div class="checked-circle"></div>
+</div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  data: {
+    defaultInactiveColor: '#EBEBF2',
+    defaultActiveColor: 'rgba(77, 148, 255, 0.8)'
+  },
+  props: {
+    checked: Boolean,
+    activeColor: String,
+    inactiveColor: String
+  },
+  methods: {
+    changChecked() {
+      let checked = this.checked;
+      this.$emit('component-switch-change', !checked); 
+    }
+  }
+});
+</script>

+ 83 - 0
src/components/common/track-message.wpy

@@ -0,0 +1,83 @@
+<style lang="less">
+</style>
+
+<template>
+  <div class="component-follow-btn" v-if="tracked !== null">
+    <div v-if="tracked">
+      <slot name="tracked-content"></slot>
+    </div>
+    <div v-else v-on:click="toTrack">
+      <slot name="un-tracked-content"></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import store from '@/store';
+import { mapState } from '@wepy/x';
+import { trackMessages } from '@/service/subscribeMessage';
+import { trackOrder, checkOrderSubscribed } from '@/api/subscribe';
+
+wepy.component({
+  store,
+  mame: 'FollowBtn',
+  data: {
+    tracked: null,
+    linkUrl: ''
+  },
+  options: {
+    multipleSlots: true
+  },
+  props: {
+    templateId: String, //该订阅消息模版,
+    businessId: String, //事务id
+    businessType: String //业务类类型 1身份认证 ,2发票
+  },
+  created() {
+    if (this.token) {
+      this.getData();
+    }
+  },
+  computed: {
+    ...mapState({ token: state => state.user.token })
+  },
+  watch: {
+    token(curVal, oldVal) {
+      if (curVal && !oldVal) {
+        this.getData();
+      }
+    }
+  },
+  methods: {
+    getData() {
+      // 判断当前id 是否跟踪过
+      checkOrderSubscribed({
+        businessId: this.businessId,
+        msgSubscriptionType: Number(this.businessType) //业务类类型 1身份认证 ,2发票
+      }).then(res => {
+        let result = res.data;
+        this.tracked = result;
+      });
+    },
+    toTrack() {
+      trackMessages(this.templateId).then(res => {
+        // 如果返回的是订阅消息拒绝过,就不再订阅
+        if (res && res.reject) {
+          return;
+        }
+        setTimeout(()=> {
+          //发请求跟踪消息
+          trackOrder({
+            businessId: this.businessId,
+            msgSubscriptionType: Number(this.businessType) //业务类类型 1身份认证 ,2发票
+          }).then(() => {
+            this.tracked = true;
+          });
+        }, 3e3);
+       
+      });
+    }
+  }
+});
+</script>

+ 77 - 0
src/components/counter.wpy

@@ -0,0 +1,77 @@
+<style lang="less">
+  .counter {
+    text-align: left;
+    font-size: 12px;
+  }
+  .count {
+    font-size: 18px;
+    font-weight: bold;
+    &.red {
+      color: red;
+    }
+    &.green {
+      color: green;
+    }
+  }
+</style>
+<template>
+  <div class="counter {{style}}">
+    <button @tap="plus" size="mini">  +  </button>
+    <button @tap="minus" size="mini">  -  </button>
+    <button @tap="increment" size="mini">  INCREMENT  </button>
+    <button @tap="decrement" size="mini">  DECREMENT  </button>
+    <button @tap="incrementAsync" size="mini">  ASYNC INCREMENT  </button>
+    <span class="count"> {{counter}} </span>
+  </div>
+</template>
+<script>
+  import wepy from '@wepy/core'
+  import store from '../store';
+  import { mapState, mapActions } from '@wepy/x';
+
+  wepy.component({
+    store,
+    props: {
+      num: {
+        type: [Number, String],
+        coerce: function (v) {
+          return +v
+        },
+        default: 50
+      }
+    },
+  computed: mapState([ 'counter' ]),
+
+    events: {
+      'index-broadcast': (...args) => {
+        let $event = args[args.length - 1]
+        console.log(`${this.$name} receive ${$event.name} from ${$event.source.$name}`)
+      }
+    },
+
+    watch: {
+      num (curVal, oldVal) {
+        console.log(`旧值:${oldVal},新值:${curVal}`)
+      }
+    },
+
+    methods: {
+      ...mapActions([
+        'increment',
+        'decrement',
+        'incrementAsync'
+      ]),
+      plus () {
+        this.num = this.num + 1
+        console.log('plus tapped in component');
+
+        this.$emit('index-emit', this.num);
+      },
+      minus () {
+        this.num = this.num - 1
+        console.log(this.$name + ' minus tap')
+      }
+    }
+  })
+</script>
+

+ 41 - 0
src/components/group.wpy

@@ -0,0 +1,41 @@
+<style type="less">
+  .group {}
+</style>
+<template>
+  <div class="group">
+    <span class="id">{{grouplist.id}}. </span>
+    <span class="name" @tap="tap">{{grouplist.name}}</span>
+    <div>
+      <div v-for="item in grouplist.list">
+        <groupitem :gitem="item"></groupitem>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  import wepy from '@wepy/core';
+  //import GroupItem from './groupitem'
+
+  wepy.component({
+    props: {
+      grouplist: {},
+      index: {}
+    },
+
+    methods: {
+      tap () {
+        this.grouplist.name = `Parent Random(${Math.random()})`
+        let groups = this.$parent.$children.filter(com => com.$is === 'components/group');
+        let index = groups.indexOf(this);
+        console.log(`Clicked Group ${index}, ID is ${this.grouplist.id}`)
+      }
+    }
+  });
+</script>
+<config>
+{
+  usingComponents: {
+    'groupitem': './groupitem'
+  }
+}
+</config>

+ 28 - 0
src/components/groupitem.wpy

@@ -0,0 +1,28 @@
+<style type="less">
+.groupitem {
+}
+</style>
+<template>
+  <div class="groupitem">
+    --<span class="id">{{gitem.childid}}.</span>
+    <span class="name" @tap="tap"> {{gitem.childname}}</span>
+  </div>
+</template>
+<script>
+  import wepy from '@wepy/core';
+
+  wepy.component({
+    props: {
+      gitem: {}
+    },
+    data: {
+    },
+    methods: {
+      tap () {
+        this.gitem.childname = `Child Random(${Math.random()})`
+        let index = this.$parent.$children.indexOf(this);
+        console.log(`Item ${index}, ID is ${this.gitem.childid}`)
+      }
+    }
+  });
+</script>

+ 48 - 0
src/components/list.wpy

@@ -0,0 +1,48 @@
+<style lang="less">
+  .mylist:odd {
+    color: red;
+  }
+  .mylist:even {
+    color: green;
+  }
+</style>
+<template>
+  <div class="list">
+     This component is not used. because list is an aliasField in package.json 
+  </div>
+</template>
+<script>
+  import wepy from '@wepy/core'
+
+  wepy.component({
+    data: {
+      list: [
+        {
+          id: '0',
+          title: 'loading'
+        }
+      ]
+    },
+
+    events: {
+      'index-broadcast': (...args) => {
+        let $event = args[args.length - 1]
+        console.log(`${this.$name} receive ${$event.name} from ${$event.source.name}`)
+      }
+    },
+
+    methods: {
+      tap () {
+        // this.num = this.num + 1
+        console.log(this.$name + ' tap')
+      },
+      add () {
+        let len = this.list.length
+        this.list.push({id: len + 1, title: 'title_' + len})
+      }
+    }
+
+    onLoad () {
+    }
+  });
+</script>

+ 40 - 0
src/components/panel.wpy

@@ -0,0 +1,40 @@
+<style lang="less">
+  panel {
+    width: 100%;
+  }
+  .panel {
+    width: 100%;
+    margin-top: 20rpx;
+    text-align: left;
+    font-size: 12px;
+    padding-top: 20rpx;
+    padding-left: 50rpx;
+    padding-bottom: 20rpx;
+    border: 1px solid #ccc;
+
+    .title {
+      padding-bottom: 20rpx;
+      font-size: 14px;
+      font-weight: bold;
+    }
+    .info {
+      padding: 15rpx;
+    }
+    .testcounter {
+      margin-top: 15rpx;
+      position: absolute;
+    }
+    .counterview {
+      margin-left: 120rpx;
+    }
+  }
+</style>
+<template>
+  <div class="panel">
+    <slot name="title">
+      Title
+    </slot>
+    <slot>
+    </slot>
+  </div>
+</template>

+ 58 - 0
src/components/wepy-list.wpy

@@ -0,0 +1,58 @@
+<style lang="less">
+    .mylist:odd {
+        color: red;
+    }
+    .mylist:even {
+        color: green;
+    }
+</style>
+<template>
+    <div class="wepy-list">
+        <div>
+            <button @tap="add" size="mini">添加列表another</button>
+        </div>
+        <div v-for="(item, index) in list">
+            <div @tap="tap" class="mylist">
+              <span>{{item.id}}</span>: {{item.title}}  <span @tap="remove(index)"> X </span>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+  import wepy from '@wepy/core'
+
+  wepy.component({
+    data: {
+      list: [
+        {
+          id: '0',
+          title: 'loading'
+        }
+      ]
+    },
+
+    events: {
+      'index-broadcast': (...args) => {
+        let $event = args[args.length - 1]
+        console.log(`${this.$name} receive ${$event.name} from ${$event.source.name}`)
+      }
+    },
+
+    methods: {
+      tap () {
+        // this.num = this.num + 1
+        console.log(this.$name + ' tap')
+      },
+      add () {
+        let len = this.list.length
+        this.list.push({id: len + 1, title: 'title_' + len})
+      },
+      remove (index) {
+        this.$delete(this.list, index);
+      }
+    },
+
+    onLoad () {
+    }
+  });
+</script>

+ 54 - 0
src/config/base.js

@@ -0,0 +1,54 @@
+// 上格云
+const brsgServer = {
+  duoduoUrl: 'https://duoduoenv.sagacloud.cn',
+  projectId: 'Pj1101080259',
+  pubname: 'sagacareAndtenantslink',
+  openid: 'sagacareAndtenantslinkTest'
+}
+
+const companyName = ' tenantslink';
+// 是否显示企业智慧空间首页的定位按钮
+const isShowLocationEntrance = true;
+// 是否显示面容通行入口(领取邀请函页面)
+const isShowFaceEntrance = true;
+// 提审微信审核或测试用的邀请函id
+const testInvitationId = 'b147722800b645748499cae71a8b863e'; // 团队版
+// const testInvitationId = 'a195db0b47b94c12904d024d8ea690b6'; // 个人版
+
+// 邀请函上的地址信息
+const AppLocation = {
+  building: {
+    invitationAddress: '东三环北路嘉铭中心',
+    latitude: 39.928029,
+    longitude: 116.460451,
+    name: '东三环北路嘉铭中心',
+    address: '北京市朝阳区东三环北路27号'
+  },
+  enterprise: {
+    invitationAddress: '金隅智造工场N2号楼3层',
+    latitude: 40.057800,
+    longitude: 116.357544,
+    name: '博锐尚格总部',
+    address: '北京市海淀区金隅智造工场N2号楼3层'
+  },
+  experience: {
+    invitationAddress: '荷华明城Funhub2楼',
+    latitude: 39.904241,
+    longitude: 116.43446,
+    name: '北京大方空间科技有限公司',
+    address: '北京市东城区建国门南大街7号'
+  }
+}
+
+// 是否开启距离权限控制
+const isOpenLocationPermisson = true;
+
+export default {
+  companyName,
+  isShowLocationEntrance,
+  isShowFaceEntrance,
+  testInvitationId,
+  AppLocation,
+  brsgServer,
+  isOpenLocationPermisson
+};

+ 20 - 0
src/config/demo.js

@@ -0,0 +1,20 @@
+// 演示版小程序
+console.log('演示版小程序-tenantslink体验版');
+// const serverUrl = 'https://brsg-test.tenants.link/api';
+// let h5Origin = 'https://brsg-test.tenants.link';
+// const websoketUrl='wss://demo.tenants.link';
+// 演示版小程序
+console.log('演示版小程序-tenantslink体验版');
+const serverUrl = '';
+let h5Origin = '';
+const websoketUrl='';
+h5Origin = 'http://localhost:8081';
+// 是否开启距离权限控制
+const isOpenLocationPermisson = false;
+
+export default {
+  serverUrl,
+  h5Origin,
+  isOpenLocationPermisson,
+  websoketUrl
+};

+ 21 - 0
src/config/dev.js

@@ -0,0 +1,21 @@
+// const serverUrl = 'https://dev.tenants.link/api';
+// let h5Origin = 'https://dev.tenants.link';
+// let appKey = '6166782f14e22b6a4f1ef5a0'; // um 蓝色图标小程序
+// const websoketUrl = 'wss://dev.tenants.link';
+
+const serverUrl = '';
+let h5Origin = '';
+let appKey = ''; // um 蓝色图标小程序
+const websoketUrl = '';
+
+h5Origin = 'http://192.168.31.96:8080';
+
+const isOpenLocationPermisson = false;
+
+export default {
+  serverUrl,
+  h5Origin,
+  isOpenLocationPermisson,
+  appKey,
+  websoketUrl
+}

+ 34 - 0
src/config/index.js

@@ -0,0 +1,34 @@
+
+// featuresVersion属于哪个功能版本: experience-体验版, building-写字楼版, enterprise-企业版
+// npm run server-test-building 通过增加node_env 参数来判断test-building, test 是环境是test 还是prod ,building参数即是featuresVersion参数值
+let envValus = NODE_ENV.split('-');
+let env = envValus[0];
+let featuresVersion = envValus[1];
+let projectName = envValus[2] || '';
+// 根据在package.json中配置的不同编译命令,引用/config下不同的配置文件
+import base from './base';
+import demo from './demo';
+import dev from './dev';
+import test from './test';
+import prod from './prod';
+let config;
+if (env == 'dev') {
+  config = dev;
+}
+
+if (env === 'prod') {
+  config = prod;
+}
+
+
+config = Object.assign(base, config, {
+  // h5PagePath: config.h5Origin + '/#',
+  h5PagePath: "http://localhost:8081/" + '/#',
+  h5StaticPath: "http://localhost:8081/" + '/static/WeChat',
+  // h5StaticPath: config.h5Origin + '/static/WeChat',
+  featuresVersion,
+  projectName,
+  env
+});
+
+export default Object.freeze(config)

+ 58 - 0
src/config/prod.js

@@ -0,0 +1,58 @@
+/* eslint-disable no-undef */
+
+let serverUrl;
+let h5Origin;
+let appKey;
+let envValus = NODE_ENV.split('-');
+let featuresVersion = envValus[1];
+let websoketUrl;
+let projectName = envValus[2] || '';
+
+if (featuresVersion === 'enterprise') {
+  // serverUrl = 'https://sagacare-h5.tenants.link/api';
+  // h5Origin = 'https://sagacare-h5.tenants.link';
+  // appKey = '61663db014e22b6a4f1e66b6'; // um 黄色图标小程序
+  // websoketUrl = 'wss://sagacare-h5.tenants.link';
+
+  const serverUrl = '';
+  let h5Origin = '';
+  let appKey = ''; // um 蓝色图标小程序
+  const websoketUrl = '';
+} else {
+  // 楼板各个项目域名映射
+  const ProjectNameDomainMap = {
+    // 'jibao': 'bizpace-h5.tenants.link',
+    // 'zhijiang': 'zjlab-h5.tenants.link',
+    // 'zhangjiang': 'jnb-h5.tenants.link',
+    // 'MUSEPoD': 'musepod-h5.tenants.link',
+    // 'yueoffice': 'yueoffice-h5.tenants.link',
+    // 'zhonghai': 'cooc-h5.tenants.link',
+    // 'yintai': 'h5.yintai-foundation.com',
+    'jibao': '',
+    'zhijiang': '',
+    'zhangjiang': '',
+    'MUSEPoD': '',
+    'yueoffice': '',
+    'zhonghai': '',
+    'yintai': ''
+  }
+
+  // const DefaultDomain = 'jmc-h5.tenants.link'; // 默认域名为嘉铭
+  const DefaultDomain = '';
+  let projectDomain = DefaultDomain;
+  if (projectName && ProjectNameDomainMap[projectName]) {
+    projectDomain = ProjectNameDomainMap[projectName];
+  }
+
+  serverUrl = `https://${projectDomain}/api`;
+  // h5Origin = `https://${projectDomain}`;
+  h5Origin = 'http://localhost:8081/';
+  websoketUrl = `wss://${projectDomain}`
+}
+
+export default {
+  serverUrl,
+  h5Origin,
+  appKey,
+  websoketUrl
+};

+ 21 - 0
src/config/test.js

@@ -0,0 +1,21 @@
+// const serverUrl = 'https://beta.tenants.link/api';
+// let h5Origin = 'https://beta.tenants.link';
+// let appKey = '6166782f14e22b6a4f1ef5a0'; // um 蓝色图标小程序
+// const websoketUrl = 'wss://beta.tenants.link';
+
+const serverUrl = '';
+let h5Origin = '';
+let appKey = ''; // um 蓝色图标小程序
+const websoketUrl = '';
+
+h5Origin = 'http://localhost:8081/';
+
+const isOpenLocationPermisson = false;
+
+export default {
+  serverUrl,
+  h5Origin,
+  isOpenLocationPermisson,
+  appKey,
+  websoketUrl
+}

+ 19 - 0
src/mixins/test.js

@@ -0,0 +1,19 @@
+
+
+export default {
+  data: {
+    mixin: 'MixinText'
+  },
+  methods: {
+    mixintap () {
+      this.mixin = 'MixinText' + (Math.random() + '').substring(3, 7);
+      console.log('mixin method tap');
+    },
+    tap () {
+      console.log('tap in mixin');
+    }
+  },
+  created () {
+    console.log('created in mixin');
+  }
+}

+ 150 - 0
src/packagesEnv/api/intelligentControl.js

@@ -0,0 +1,150 @@
+import $http from '@/common/request.js';
+import config from '@/config';
+import store from '@/store';
+
+function getMapInfoHttp(companyId) {
+    var url = ''
+    if (companyId) {
+        url = `/persagy/spaces?companyId=${companyId}`;
+    } else {
+        url = `/persagy/spaces`;
+    }
+
+    return $http({
+        url: url,
+        method: 'GET'
+    })
+}
+
+function getSpaceLightsHttp(spaceId, companyId) {
+    if (config.featuresVersion == 'experience') {
+        let testResult = {
+            'result': 'success',
+            'content': JSON.stringify([{
+                    runStatus: 1,
+                    lampType: '顶灯',
+                    id: 1
+                },
+                {
+                    runStatus: 1,
+                    lampType: '射灯',
+                    id: 2
+                }
+            ])
+        };
+        return Promise.resolve({
+            data: testResult
+        })
+    } else {
+        return $http({
+            url: `/persagy/lights/${spaceId}?companyId=${companyId}`,
+            method: 'GET'
+        })
+    }
+}
+
+function setSpaceLightHttp(lightId, opened, companyId) {
+    // 体验版默认不发请求默认设置成功
+    if (config.featuresVersion == 'experience') {
+        return Promise.resolve({
+            data: JSON.stringify({ 'result': 'success', 'content': [{ 'result': 'success', 'state': 200, 'exeResult': 'success' }] })
+        })
+    } else {
+        return $http({
+            url: '/persagy/lightSwitch' + `?onOrOff=${opened}&companyId=${companyId}`,
+            method: 'POST',
+            data: JSON.stringify([lightId])
+        })
+    }
+}
+
+function getEnvironmentInfoHttp(spaceId, companyId) {
+    return $http({
+        url: `/persagy/space/environment/${spaceId}?companyId=${companyId}`,
+        method: 'GET'
+    })
+}
+
+function getAirConditionStatusHttp(spaceId, companyId) {
+    return $http({
+        url: `/persagy/airCondition/${spaceId}?companyId=${companyId}`,
+        method: 'GET'
+    })
+}
+
+function changeAirconditionHttp(spaceId, status, companyId) {
+    if (config.featuresVersion == 'experience') {
+        return Promise.resolve();
+    } else {
+        return $http({
+            url: `/persagy/temperature/${spaceId}/${status}?companyId=${companyId}`,
+            method: 'GET'
+        })
+    }
+}
+
+function getSpaceBytoothHttp(devices, companyId) {
+  return $http({
+      url: `/persagy/space/device2?companyId=${companyId}`,
+      method: 'POST',
+      data: JSON.stringify(devices)
+  })
+}
+
+function checkChangeLightStatusSuccessHttp(params, companyId) {
+    // 体验版不控制数据
+    if (config.featuresVersion == 'experience') {
+        return Promise.resolve({
+            data: JSON.stringify({ 'result': 'success', 'content': [{ 'result': 'success', 'state': 200, 'exeResult': 'success' }] })
+        })
+    } else {
+        return $http({
+            url: `/persagy/lightQuery?companyId=${companyId}`,
+            method: 'POST',
+            headers: {
+                'content-type': 'application/json'
+            },
+            data: JSON.stringify(params)
+        })
+    }
+}
+
+function getCustomServerTimeHttp(spaceId) {
+    return $http({
+        serverSp: config.brsgServer.duoduoUrl,
+        url: `/tenantslink/customScene/queryCustom?projectId=${store.state.company.companyConfig.sagacareProjectId}&spaceId=${spaceId}`,
+        method: 'GET'
+    })
+}
+
+function saveCustomServerTimeHttp(params) {
+    let projectId = store.state.company.companyConfig.sagacareProjectId;
+    return $http({
+        serverSp: config.brsgServer.duoduoUrl,
+        url: `/tenantslink/customScene/save?projectId=${projectId}`,
+        method: 'POST',
+        data: JSON.stringify({...params, projectId: projectId })
+    })
+}
+
+
+function getUserSpaceList(companyId) {
+    return $http({
+        url: `/space/company/userSpace?companyId=${companyId}`,
+        method: 'GET'
+    })
+}
+
+export {
+    getMapInfoHttp,
+    getSpaceLightsHttp,
+    setSpaceLightHttp,
+    getEnvironmentInfoHttp,
+    getAirConditionStatusHttp,
+    changeAirconditionHttp,
+    getSpaceBytoothHttp,
+    checkChangeLightStatusSuccessHttp,
+    getCustomServerTimeHttp,
+    saveCustomServerTimeHttp,
+    getUserSpaceList
+}

+ 508 - 0
src/packagesEnv/api/officehome.js

@@ -0,0 +1,508 @@
+import $http from '@/common/request.js';
+import config from '@/config';
+import store from '@/store/index.js';
+
+function storeUser() {
+  return store.state.user.userInfo;
+}
+
+function getProjectId() {
+  return (
+    store.state.company.companyConfig &&
+    store.state.company.companyConfig.sagacareProjectId
+  );
+}
+
+function commonParams() {
+  return `openid=${storeUser().userId}&pubname=${
+    config.brsgServer.pubname
+  }&projectId=${getProjectId()}&userName=${storeUser().userName}&userPhone=${
+    storeUser().phone
+  }&userId=${storeUser().userId}`;
+}
+
+// 获取地暖
+function querySapceFloorHeating(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    isNotShowErrorToast: true,
+    url: `/duoduo-service/duoduoenv-service/ctrl/querySapceFloorHeating?${commonParams()}&spaceId=${params.spaceId}`,
+    errorSave: true,
+    method: 'get'
+  });
+}
+
+//  设置设备
+function setEquipeHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/object/eqp/infocode/set?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+// 获取设备
+function getObjectDataEqpGroupHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/object/objectDataEqpGroup/query?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ ...params })
+  });
+}
+
+// 改变场景
+function switchoverSceneHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/meeting/room/scene/switchover?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ ...params, projectId: getProjectId() })
+  });
+}
+// 获得空间当前会议室场景
+function getMeetingSceneHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/meeting/room/scene/getMeetingScene/V2?${commonParams()}&spaceId=${
+      params.spaceId
+    }`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'get'
+  });
+}
+// 获得空间场景
+function querySpaceSceneConfigHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/setup-service/scene/querySpaceSceneConfig?${commonParams()}&spaceId=${
+      params.spaceId
+    }`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'get'
+  });
+}
+
+// 设备
+function getEquipmentListHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/object/equipment/findForServe?${commonParams()}&spaceId=${
+      params.spaceId
+    }`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'POST'
+  });
+}
+// set 设备
+function setEquipmentHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/object/equipment/setRunStatus?${commonParams()}`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'POST',
+    data: JSON.stringify({ ...params, projectId: getProjectId() })
+  });
+}
+
+// 详情页 面积图
+function getChartListHTTP(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    url: `/server/spaceAdjust/queryPropertyData?${commonParams()}&spaceId=${
+      params.spaceId
+    }&funcid=${params.funcid}`,
+    method: 'get'
+  });
+}
+// 办公空间
+function getSpaceAdjustList(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    // serverSp: 'http://192.168.0.47:52009',
+    url: `/server/space/property?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({
+      ...params,
+      criteria: { ...params.criteria, projectId: getProjectId() }
+    })
+  });
+}
+
+//  温度上下限
+function getTempRangeHttp() {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/space/getTempRange?${commonParams()}`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'get'
+  });
+}
+
+// 天气预报
+function getWeather() {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    isNotShowErrorToast: true,
+    url: `/duoduo-service/object-service/weather/getCurrentWeather?${commonParams()}`,
+    errorSave: true,
+    method: 'get'
+  });
+}
+//  空调 - 调节文案/空调信息
+function getFeedbackDocumentsHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/custom/feedback/documents?${commonParams()}`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'POST',
+    data: JSON.stringify(params)
+  });
+}
+
+//  空调 调节
+function changeTempHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/custom/feedbackCreate/v2?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ ...params, projectId: getProjectId() })
+  });
+}
+
+//  手动控制 下发指令
+function changeManualTempHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/ctrl/set?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+// 查询空间空调信息
+function querySpaceConditioners(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    // serverSp: 'http://192.168.0.47:52009',
+    url: `/server/ctrl/querySpaceConditioners?projectId=${params.projectId}&spaceId=${
+      params.spaceId
+    }`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'GET'
+  });
+}
+
+// // 空调 调节
+// function changeTempHttp(params) {
+//   return $http({
+//     serverSp: config.brsgServer.duoduoUrl,
+//     url: `/server/custom/feedbackCreate?pubname=${config.brsgServer.pubname}&openid=${config.brsgServer.openid}&userName=${storeUser().userName}&userPhone=${storeUser().phone}`,
+//     method: 'POST',
+//     data: JSON.stringify(params)
+//   });
+// }
+/*       灯的接口     */
+
+// 查询定制时间
+function getSetTimeHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/customScene/queryCustom?${commonParams()}&spaceId=${
+      params.spaceId
+    }`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'GET'
+  });
+}
+
+// 更改 定制时间
+function changeSetTimeHttp(params) {
+  params = { ...params, projectId: getProjectId() };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/customScene/create?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+// 灯的状态
+function getLampHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/light/status/query?${commonParams()}&spaceId=${
+      params.spaceId
+    }`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'GET'
+  });
+}
+
+// 批量 开关灯
+function setallLampHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/light/onoff/set?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+// 全部关闭 关灯 走廊的灯 晚关
+function specialLightSetHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/light/onoff/specialSet?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 开关失败时 定时查询灯状态
+function getStatusHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/light/onoff/setSatus?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 插座-列表
+function getSocketHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/ctrl/queryEquipmentStatusInfo?${commonParams()}&spaceId=${
+        params.spaceId
+      }&equipType=socket`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'GET'
+  });
+}
+// 插座-控制
+function setSocketHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/ctrl/set?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 查询是否是过渡季
+function getSeasonType(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/custom-service/custom/getSeasonType?projectId=${params.projectId}&date=${params.date}`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'GET'
+  });
+}
+// 查询所有空间设备
+function queryEquipStatistics(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/object-service/object/equipment/queryEquipStatistics?projectId=${params.projectId}&floorId=${params.floorId}`,
+    isNotShowErrorToast: true,
+    errorSave: true,
+    method: 'GET'
+  });
+}
+//  批量空调 调节
+function changeAllAirHttp(params) {
+  /// duoduoenv.sagacloud.cn/server/custom/feedbackCreate/v2Batch
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/custom/feedbackCreate/v2Batch?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+// 批量取消和申请加班
+function saveBatchCustomScene(params) {
+  let paramsArr = JSON.parse(JSON.stringify(params.historyBookList));
+  paramsArr.find((item) => {
+    item.customSceneEqType = 'ALL';
+    if (!item.sceneEquipList) item.sceneEquipList = [];
+  });
+  if (params.type !== 'workOff' && params.type !== 'delete') {
+    if (
+      params.newBooklist &&
+      params.newBooklist.startTime &&
+      params.newBooklist.startTime !== ''
+    ) {
+      params.newBooklist.projectId = getProjectId();
+      params.newBooklist.sourceType = config.brsgServer.pubname;
+      params.newBooklist.userId = storeUser().userId;
+      params.newBooklist.userPhone = storeUser().phone;
+      params.newBooklist.userName = storeUser().userName;
+    }
+    if (paramsArr.length > 0) {
+      paramsArr.find((item) => {
+        item.startDate = item.startDate.replace(/-/g, '');
+        item.endDate = item.startDate;
+      });
+    }
+    params.newBooklist && paramsArr.push(params.newBooklist);
+  }
+  if (params.type === 'delete') {
+    paramsArr.find((item) => {
+      item.startDate = item.startDate.replace(/-/g, '');
+      item.endDate = item.startDate;
+      item.customSceneEqType = 'ALL';
+      item.projectId = getProjectId();
+      item.sourceType = config.brsgServer.pubname;
+      item.userId = storeUser().userId;
+      item.userPhone = storeUser().phone;
+      item.userName = storeUser().userName;
+    });
+  }
+
+  console.log(paramsArr, 'paramsArrparamsArr');
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/customScene/saveBatchCustomScene?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(paramsArr)
+  });
+}
+// 查询加班记录
+function queryCustomAndScence(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/customScene/queryCustomAndScence?${commonParams()}&spaceId=${
+      params.spaceId
+    }`,
+    method: 'GET',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+
+function changeDefaultSpace(params) {
+  return $http({
+    url: `/user/defaultSpace?spaceId=${params}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+// 设备运行配置
+function queryEquipmentRunConfig(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/custom-service/equipmentRun/queryEquipmentRunConfig?projectId=${getProjectId()}&spaceId=${params}`,
+    method: 'GET',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+
+// ---权限控制---
+// 项目控制权限
+function queryProjectConfig() {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/user/permissions/queryProjectConfig?projectId=${getProjectId()}`,
+    method: 'GET',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+// 空间控制权限
+function queryUserControlInfo() {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/user/permissions/queryUserControlInfo?${commonParams()}`,
+    method: 'GET',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+// 会议室控制权限
+function querySpaceMeetingInfo(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/meeting/querySpaceMeetingInfo?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+export {
+  querySapceFloorHeating,
+  querySpaceMeetingInfo,
+  queryUserControlInfo,
+  queryProjectConfig,
+  queryEquipmentRunConfig,
+  setSocketHttp,
+  getSocketHttp,
+  queryCustomAndScence,
+  saveBatchCustomScene,
+  setEquipeHttp,
+  getObjectDataEqpGroupHttp,
+  getFeedbackDocumentsHttp,
+  getTempRangeHttp,
+  getStatusHttp,
+  setallLampHttp,
+  specialLightSetHttp,
+  getLampHttp,
+  changeSetTimeHttp,
+  getSetTimeHttp,
+  getSpaceAdjustList,
+  getWeather,
+  changeTempHttp,
+  getChartListHTTP,
+  getEquipmentListHttp,
+  setEquipmentHttp,
+  getMeetingSceneHttp,
+  querySpaceSceneConfigHttp,
+  switchoverSceneHttp,
+  getSeasonType,
+  queryEquipStatistics,
+  changeAllAirHttp,
+  querySpaceConditioners,
+  changeManualTempHttp,
+  changeDefaultSpace
+};

+ 335 - 0
src/packagesEnv/api/portrait.js

@@ -0,0 +1,335 @@
+import $http from '@/common/request.js';
+import config from '@/config';
+import store from '@/store/index.js';
+/* 使用统计 */
+function storeUser() {
+  return store.state.user.userInfo;
+}
+
+function getProjectId() {
+  return (
+    store.state.company.companyConfig &&
+    store.state.company.companyConfig.sagacareProjectId
+  );
+}
+
+function commonParams() {
+  return `openid=${storeUser().userId}&pubname=${
+    config.brsgServer.pubname
+  }&projectId=${getProjectId()}&userName=${storeUser().userName}&userPhone=${
+    storeUser().phone
+  }&userId=${storeUser().userId}`;
+}
+
+// 企业碳账户-总积分查询
+function queryIntegralByAccount(accountId) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/userIntegral/queryIntegralByAccount?accountId=${accountId}`,
+    method: 'get',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+
+// 企业碳账户-积分查询
+function operationsQuery(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/userIntegral/operationsQuery`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 邀请函埋点
+function buriedEventLogHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/buriedEvent/buriedEventLog/add?${commonParams()}`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({
+      ...params,
+      projectId: getProjectId()
+    })
+  });
+}
+
+// 获取邀请函埋点信息
+function queeyBuriedEventLogHttp(params) {
+  let criteria = {
+    ...params,
+    projectId: getProjectId(),
+    userId: storeUser().userId
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/buriedEvent/buriedEventLog/query`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({criteria: criteria})
+  });
+}
+
+// 访客
+function queryInvitationStatisticsHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userPortrait/invitation/queryInvitationStatistics?${commonParams()}`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ ...params, userId: storeUser().userId, projectId: getProjectId() })
+  });
+}
+
+// 开关记录调节
+function lightFeedbackQuery(params) {
+  params.criteria = {
+    ...params.criteria,
+    projectId: getProjectId(),
+    userId: storeUser().userId
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/custom/feedback/light/query`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 空调记录调节
+function tempFeedbackQuery(params) {
+  params.criteria = {
+    ...params.criteria,
+    projectId: getProjectId(),
+    // userId:'564a8ab745de45c2aa6060f0c28e7372'
+    userId: storeUser().userId
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/custom/feedbackQuery`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 获取季节类型
+function getSeasonBySeasonDate(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/custom/getSeasonBySeasonDate?projectId=${getProjectId()}&dateTime=${
+      params.dateTime
+    }`,
+    method: 'get',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+// 空调统计数据  chart详情
+function queryPortraitTempUserDetail(params) {
+  params.criteria = {
+    ...params.criteria,
+    projectId: getProjectId(),
+    // userId:'7a925778ee184f58a8f4ef73f5196887'
+    userId: storeUser().userId
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userPortrait/portraitTemp/queryPortraitTempUserDetail`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 空调统计数据  汇总
+function queryPortraitTempUserTotal() {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userPortrait/portraitTemp/queryPortraitTempUserTotal?userId=${
+      storeUser().userId
+    }`,
+    method: 'get',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+
+// 开关灯 累计使用次数  chart详情
+function queryPortraitLightUser() {
+  var params = {
+    criteria: {
+      userId: storeUser().userId
+    }
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userPortrait/portraitLight/queryPortraitLightUser`,
+    method: 'post',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+// 开关灯 累计使用次数  汇总
+function queryTotalPortraitLight() {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userPortrait/portraitLight/queryTotalPortraitLight?userId=${
+      storeUser().userId
+    }`,
+    method: 'get',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+
+//  查询 今日新增  排名
+function queryTotalIntegral(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userIntegral/queryUserMessage?userId=${
+      storeUser().userId
+    }&projectId=${getProjectId()}&date=${params.date}&month=${params.month}`,
+    method: 'get',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+// 查询当前用户积分
+function queryAllIntegral(params) {
+  // var criteria = { ...params, projectId: getProjectId(), userId: storeUser().userId };
+  params.criteria = {
+    ...params.criteria,
+    projectId: getProjectId(),
+    userId: storeUser().userId
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userIntegral/query`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+// 查询所有用户积分排名
+function queryIntegralRank(params) {
+  // var criteria = { ...params, projectId: getProjectId(), userId: storeUser().userId };
+  params.criteria = {
+    ...params.criteria,
+    projectId: getProjectId()
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userIntegral/query`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify(params)
+  });
+}
+
+// function queryUserIntegral(params) {
+//     return $http({
+//       serverSp: config.brsgServer.duoduoUrl,
+//       url: `/duoduo-service/duoduoenv-service/userIntegral/query`,
+//       method: 'POST',
+//       isNotShowErrorToast: true,
+//       errorSave: true,
+//       data: JSON.stringify({...params})
+//     })
+//   }
+
+// 会议室-首页:累计使用次数
+function portraitMeetingHttp(params) {
+  let criteria = {
+    ...params,
+    userId: storeUser().userId,
+    projectId: getProjectId()
+  };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/userPortrait/portraitMeeting/query?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ criteria: criteria })
+  });
+}
+
+// 会议室-详情页:累计使用次数
+function summaryHttp(params) {
+  let criteria = { ...params, projectId: getProjectId() };
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/userPortrait/portraitMeeting/query/summary?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ criteria: criteria })
+  });
+}
+// 会议室-详情页:常用会议室
+function queryComsHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/userPortrait/portraitMeetingUser/queryComs?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ ...params, projectId: getProjectId() })
+  });
+}
+// 会议室-详情页:参与同事统计
+function portraitMeetingUserHttp(params) {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/server/userPortrait/portraitMeetingUser/query?${commonParams()}`,
+    method: 'POST',
+    isNotShowErrorToast: true,
+    errorSave: true,
+    data: JSON.stringify({ ...params, projectId: getProjectId() })
+  });
+}
+// 模块显示
+function queryPortraitModule() {
+  return $http({
+    serverSp: config.brsgServer.duoduoUrl,
+    url: `/duoduo-service/duoduoenv-service/userPortrait/queryPortraitModuleList?${commonParams()}`,
+    method: 'get',
+    isNotShowErrorToast: true,
+    errorSave: true
+  });
+}
+
+export {
+  queryIntegralByAccount,
+  operationsQuery,
+  queeyBuriedEventLogHttp,
+  buriedEventLogHttp,
+  queryInvitationStatisticsHttp,
+  queryTotalIntegral,
+  queryAllIntegral,
+  portraitMeetingHttp,
+  summaryHttp,
+  queryComsHttp,
+  portraitMeetingUserHttp,
+  queryIntegralRank,
+  queryTotalPortraitLight,
+  queryPortraitLightUser,
+  queryPortraitTempUserTotal,
+  queryPortraitTempUserDetail,
+  getSeasonBySeasonDate,
+  tempFeedbackQuery,
+  lightFeedbackQuery,
+  queryPortraitModule
+};

+ 301 - 0
src/packagesEnv/pages/common.js

@@ -0,0 +1,301 @@
+import config from '@/config';
+import uma from 'umtrack-wx';
+
+function umaInit() {
+  config.appKey &&
+    uma.init({
+      appKey: config.appKey, // 由友盟分配的APP_KEY
+      // 使用Openid进行统计,此项为false时将使用友盟+uuid进行用户统计。
+      // 使用Openid来统计微信小程序的用户,会使统计的指标更为准确,对系统准确性要求高的应用推荐使用Openid。
+      useOpenid: false,
+      // 使用openid进行统计时,是否授权友盟自动获取Openid,
+      // 如若需要,请到友盟后台"设置管理-应用信息"(https://mp.umeng.com/setting/appset)中设置appId及secret
+      autoGetOpenid: false
+      // debug: true, // 是否打开调试模式
+      // uploadUserInfo: true // 自动上传用户信息,设为false取消上传,默认为false
+    });
+}
+
+const SliderUnit = 0.5; // 一个色块代表0.5个小时
+const TimeStart = 0;
+const TimeEnd = 24;
+
+function generateTimeSliders(openStartTime, openEndTime, disabledPast = true) {
+  let start = TimeStart,
+    end = TimeEnd;
+  let duration = end - start;
+  let times = [];
+  let { juggeValue } = getNowTime();
+  for (let i = 0; i < duration / SliderUnit; i++) {
+    let value = start + i * SliderUnit;
+    let values = value.toString().split('.');
+    let status = '';
+    let label = '';
+    if (values.length == 1) {
+      // 代表是整数
+      label =
+        parseInt(values[0]) < 10 ? '0' + values[0] + ':00' : values[0] + ':00';
+    } else {
+      // 代表是有分钟
+      let mniuts = SliderUnit * 60; // 转化分钟;
+      label =
+        parseInt(values[0]) < 10
+          ? '0' + values[0] + ':' + mniuts
+          : values[0] + ':' + mniuts;
+    }
+    if (disabledPast && value < juggeValue) {
+      status = 'past'; // 过去时间
+    }
+
+    if (openEndTime && value >= openStartTime && value <= openEndTime) {
+      status = 'unOpened'; // 不在开放时间内
+    }
+
+    times.push({
+      value,
+      label,
+      status
+    });
+  }
+  return times;
+}
+function getNowTime() {
+  let now = new Date();
+  let hour = now.getHours();
+  let minuts = now.getMinutes();
+  // let hour = 16;
+  // let minuts = 30;
+  let minutsValue = 0;
+  if (minuts < 30) {
+    // 在30分钟内,都可以预约当前小时,故取0
+    minutsValue = 0;
+  } else {
+    minutsValue = 0.5; // 在30分钟-59分钟内,都可以预约当前小时+半小时之后,故取0.5
+  }
+  let juggeValue = hour + minutsValue;
+
+  return {
+    juggeValue,
+    trueValue: {
+      hour,
+      minuts
+    }
+  };
+}
+
+function getNowDate(date) {
+  let now = new Date();
+  return getDate(now);
+}
+
+function getDate(now) {
+  let year = now.getFullYear();
+  let month = now.getMonth() + 1;
+  let date = now.getDate();
+  return [year, getTowNum(month), getTowNum(date)].join('');
+}
+
+function getTowNum(num) {
+  num = parseInt(num);
+  return num < 10 ? '0' + num : num;
+}
+
+function formateTimeValue(timeValue) {
+  let value = parseFloat(timeValue);
+  let hour = parseInt(timeValue);
+  let minits = (value - hour) * 60;
+  return `${getTowNum(hour)}${getTowNum(minits)}00`;
+}
+
+function formateDateValue(dateValue) {
+  return dateValue.replace(/-/g, '');
+}
+
+function formateTimeToValue(time) {
+  if (time === '') return 0;
+  let hours = time.substring(0, 2);
+  let mins = time.substring(2, 4);
+  let hoursValue = parseInt(hours);
+  let mintusValue = parseInt(mins) / 60;
+  return hoursValue + mintusValue;
+}
+
+function formateTimeToColon(time) {
+  let hours = time.substring(0, 2);
+  let mins = time.substring(2, 4);
+  return `${hours}:${mins}`;
+}
+
+function endtimeToUnitTime(time) {
+  let hours = time.substring(0, 2);
+  let mins = time.substring(2, 4);
+  if (mins < 30) {
+    mins = '00';
+  } else {
+    mins = '30';
+  }
+  return `${hours}${mins}00`;
+}
+function startimeToUnitTime(time) {
+  let hours = time.substring(0, 2);
+  let mins = time.substring(2, 4);
+  if (mins < 30 && mins !== '00') {
+    mins = '30';
+  } else if (mins > 30) {
+    mins = '00';
+    hours = parseInt(hours) + 1;
+    hours = hours < 10 ? `0${hours}` : hours
+  } else {
+  }
+  return `${hours}${mins}00`;
+}
+
+function formateBookedList(bookedList) {
+  let list = {};
+  if (!bookedList || (bookedList && bookedList.length == 0)) {
+    return list;
+  }
+
+  bookedList.forEach((item) => {
+    let { startTime, endTime, startDate } = item;
+    let key = startDate;
+    let data = {
+      start: formateTimeToValue(endtimeToUnitTime(startTime)),
+      end: formateTimeToValue(startimeToUnitTime(endTime)),
+      ...item
+    };
+    if (!list[key]) {
+      list[key] = [data];
+    } else {
+      list[key].push(data);
+    }
+  });
+  return list;
+}
+
+function dateToDate(val) {
+  const year = val.slice(0, 4);
+  const month = val.slice(4, 6);
+  const day = val.slice(6, 8);
+  return `${year}-${month}-${day}`;
+}
+
+function getWeekDay(date) {
+  let week = date.getDay();
+  let weekday = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
+  return weekday[week];
+}
+function checkSpaceControl(val) {
+  if (!val) {
+    wx.showToast({
+      title: '您没有权限!',
+      icon: 'none',
+      duration: 1500
+    });
+    return false;
+  } else {
+    return true
+  }
+}
+
+function carbonCredit(projectId) {
+  if (projectId === 'Pj1101020007') {
+    return '碳积分';
+  }
+  return '节能分';
+}
+
+function carbonCreditExplain(params) {
+  const arr = [
+    {
+      id: '',
+      sty: '',
+      text: '空调加时积分规则:'
+    },
+    {
+      id: '1.',
+      sty: '',
+      text: '空调/照明加时申请,增加积分1.5;'
+    },
+    {
+      id: '2.',
+      sty: '',
+      text: '空调/照明加时时段内,点击“我要离开”主动结束加时,增加积分0.5。'
+    },
+    {
+      id: '',
+      sty: '',
+      text: '空调&照明操作积分规则:'
+    },
+    {
+      id: '1.',
+      sty: '',
+      text: '当日主动关闭空调或照明设备,且持续时间大于1小时,空调积2分,照明积0.3分;'
+    },
+    {
+      id: '2.',
+      sty: '',
+      text: '夏季,空调调节温度大于等于26℃,且持续时间大于30分钟,积0.3分;'
+    },
+    {
+      id: '3.',
+      sty: '',
+      text: '冬季,空调调节温度小于等于21℃,且持续时间大于30分钟,积0.3分;'
+    },
+    {
+      id: '4.',
+      sty: '',
+      text: '办公室出现大于等于2个空调或照明设备时,用户开启空调或照明30分钟后,未开启的空调或照明数量大于等于1个时,空调积1分,照明积0.2分。'
+    },
+    {
+      id: '',
+      sty: '',
+      text: '会议场景积分规则:'
+    },
+    {
+      id: '1.',
+      sty: '',
+      text: '会议期间提前结束会议,从讨论或投影场景切换至散会场景时,积2分。'
+
+    },
+    {
+      id: '',
+      sty: 'color:#9FA3AF;padding-top:20rpx;',
+      text: '说明:'
+    },
+    {
+      id: '1.',
+      sty: 'color:#9FA3AF;font-size:24rpx',
+      text: '以上低碳行为会根据系统规则在正常使用空调/照明等设备的合理范围内限制积分次数;'
+    },
+    {
+      id: '2.',
+      sty: 'color:#9FA3AF;font-size:24rpx',
+      text: '低碳积分将同时计入个人和企业积分账户。'
+    }
+
+  ]
+  return arr
+}
+
+export {
+  carbonCreditExplain,
+  carbonCredit,
+  checkSpaceControl,
+  umaInit,
+  generateTimeSliders,
+  getNowTime,
+  formateBookedList,
+  formateTimeValue,
+  formateTimeToValue,
+  formateDateValue,
+  getNowDate,
+  formateTimeToColon,
+  dateToDate,
+  getWeekDay,
+  endtimeToUnitTime,
+  startimeToUnitTime,
+  TimeEnd,
+  TimeStart,
+  SliderUnit
+};

文件差異過大導致無法顯示
+ 1227 - 0
src/packagesEnv/pages/intelligentControl/components/controlpanel/controlpanel.wpy


+ 101 - 0
src/packagesEnv/pages/intelligentControl/components/floor/floor.wpy

@@ -0,0 +1,101 @@
+<style lang="less">
+.component-floor {
+  border-radius: 16rpx;
+  background: rgba(255, 255, 255, 0.9);
+  .floor-wrapper{
+    display: flex;
+    flex-direction: column-reverse;
+  }
+  
+}
+.component-floor.fold .floor-item {
+  background: none;
+}
+
+.component-floor .floor-item {
+  width: 96rpx;
+  height: 96rpx;
+
+  font-weight: bold;
+  font-size: 36rpx;
+  line-height: 96rpx;
+  color: rgba(13, 13, 61, 0.86);
+  text-align: center;
+}
+
+.floor-item:first-child {
+  border-radius: 16rpx 16rpx 0 0;
+}
+.floor-item.selected {
+  background: #fbf0e0;
+}
+.floor-item:last-child {
+  border-radius: 0 0 8px 8px;
+}
+.icon-arrow {
+  width: 96rpx;
+  height: 96rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+.icon-arrow .icon {
+  width: 20rpx;
+  height: 24rpx;
+}
+</style>
+
+<template>
+  <div class="{{'component-floor '+status}}">
+    <div class="floor-wrapper">
+      <div
+        class="floor-item"
+        :class="{'selected' :selectedFloor == item.floorId}"
+        v-for="(item,index) in floors"
+        v-on:click.stop="changeFloor(item.floorId)"
+        v-if="status=='expand' || (status=='fold' && selectedFloor == item.floorId)"
+      >{{item.text}}</div>
+    </div>
+    <div class="icon-arrow" wx:if="{{status=='expand'}}">
+    <image
+      mode="scaleToFill"
+      class="icon"
+      src="{{h5StaticPath+ 'icon-floor-arrow.svg'}}"
+      v-on:click.stop="flodFloor"
+    />
+  </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+let h5StaticPath = config.h5StaticPath + '/page-intelligent-control/';
+
+wepy.component({
+  data: {
+    h5StaticPath
+  },
+  props: {
+    floors: Array,
+    status: 'fold',
+    selectedFloor: String //选中的floor id
+  },
+  created() {},
+  didUpdate() {},
+  didUnmount() {},
+  methods: {
+    changeFloor(floorId) {
+      if (floorId !== this.selectedFloor) {
+        this.$emit('component-floor-change', floorId);
+      }
+      this.$emit('component-floor-click');
+    },
+    flodFloor() {
+      this.$emit('component-floor-fold');
+    }
+  }
+});
+</script>

+ 147 - 0
src/packagesEnv/pages/intelligentControl/components/location/location.wpy

@@ -0,0 +1,147 @@
+<style lang="less">
+.component-location {
+  border-radius: 16rpx;
+  background: rgba(255, 255, 255, 0.9);
+  width: 96rpx;
+  height: 96rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 16rpx;
+  .icon {
+    width: 56rpx;
+    height: 56rpx;
+  }
+}
+</style>
+<template>
+  <view class="component-location" v-on:click.stop="position">
+    <image class="icon" src="{{h5StaticPath+'icon-location.svg'}}" />
+  </view>
+</template>
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+let hasInit = false;
+let h5StaticPath = config.h5StaticPath + '/page-intelligent-control/';
+wepy.component({
+  data: {
+    h5StaticPath
+  },
+  methods: {
+    position() {
+      return this.startBeaconDiscovery().then(res => {
+        let devices = [];
+        wx.showLoading({
+          title: '正在定位..', //提示的内容,
+          mask: true, //显示透明蒙层,防止触摸穿透,
+          success: res => {}
+        });
+        
+        wx.onBeaconUpdate(res => {
+          if (res && res.beacons && res.beacons.length > 0) {
+            res.beacons.forEach(beacon => {
+              devices.push(beacon);
+            });
+          } 
+        });
+        setTimeout(() => {
+          wx.hideLoading();
+          wx.offBeaconUpdate();
+          this.stopBeaconDiscovery();
+          if(!devices.length){
+            wx.showToast({
+              title: '未搜索到蓝牙信标',
+              icon: 'none'
+            });
+            return;
+          }
+          this.$emit('component-location-position', devices);
+        }, 4*1000);
+      });
+    },
+    getBeacons() {
+      return new Promise((resolve, reject) => {
+        wx.getBeacons({
+          success: res => {
+            resolve(res);
+          },
+          fail: res => {
+            wx.showToast({
+              title: res.errorMessage,
+              duration: 1000,
+              icon: 'none'
+            });
+            reject(res);
+          }
+        });
+      });
+    },
+    startBeaconDiscovery() {
+      return new Promise((resolve, reject) => {
+        wx.startBeaconDiscovery({
+          uuids: ['00000000-0000-0000-0000-000000000006'],
+          success: res => {
+            resolve(res);
+          },
+          fail: res => {
+            let msg = res.errMsg;
+            switch (res.errCode) {
+              case 11000:
+                msg = '未开启蓝牙权限';
+                break;
+              case 11001:
+                msg = '未开启蓝牙权限';
+                break;
+              case 11003:
+                msg = '定位中,请稍候';
+                break;
+            }
+            wx.showToast({
+              title: msg,
+              duration: 1000,
+              icon: 'none'
+            });
+            reject(res);
+          }
+        });
+      });
+    },
+    stopBeaconDiscovery() {
+      return new Promise((resolve, reject) => {
+        wx.stopBeaconDiscovery({
+          success: res => {
+            resolve(res);
+          },
+          fail: res => {
+            dd.showToast({
+              content: res.errorMessage,
+              duration: 1000
+            });
+            reject(res);
+          }
+        });
+      });
+    },
+    connectBLEDevice(devicedId) {
+      return new Promise((resolve, reject) => {
+        wx.startBeaconDiscovery({
+          deviceId: devicedId,
+          success: res => {
+            resolve(res);
+          },
+          fail: res => {
+            wx.showToast({
+              title: res.errorMessage,
+              duration: 1000,
+              icon: 'none'
+            });
+            reject(res);
+          }
+        });
+      });
+    }
+  }
+});
+</script>

+ 313 - 0
src/packagesEnv/pages/intelligentControl/components/temperturecontrolpanel/temperturecontrolpanel.wpy

@@ -0,0 +1,313 @@
+<style lang="less">
+.temperture-control-panel {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: #ebeef5;
+  box-sizing: border-box;
+  padding-top: 288rpx;
+  z-index:2;
+}
+
+.temperture-control-panel.hide {
+  display: none;
+}
+
+.temperture-control-panel.show {
+  display: block;
+}
+
+.slot-panel.hide {
+  display: none;
+}
+
+.slot-panel.show {
+  display: block;
+}
+
+.temperture-control-panel .title {
+  font-size: 32rpx;
+  line-height: 44rpx;
+  text-align: center;
+  color: rgba(9, 9, 61, 0.45);
+}
+
+.temperture-control-panel.down .slider {
+  margin-top: 116rpx;
+}
+
+.temperture-control-panel.up .slider {
+  margin-bottom: 116rpx;
+}
+
+.slider .rect-slider {
+  width: 184rpx;
+  height: 174rpx;
+  display: block;
+  margin: 0 auto;
+}
+.temperture-control-panel.down .rect-slider {
+  background: rgba(77, 148, 255, 0.12);
+}
+
+.temperture-control-panel.up .rect-slider {
+  background: rgba(255, 136, 77, 0.14);
+}
+
+.temperture-control-panel.down .rect-slider.bright {
+  background-color: #4d94ff;
+}
+
+.temperture-control-panel.up .rect-slider.bright {
+  background-color: #ff884d;
+}
+
+.slider .split {
+  width: 184rpx;
+  height: 2rpx;
+  background-color: white;
+  margin: 0 auto;
+}
+.temperture-control-panel.up .slider {
+  transform: rotate(180deg);
+}
+
+.slider .arrow-slider {
+  height: 232rpx;
+  width: 296rpx;
+  background-size: 100%;
+  background-repeat: no-repeat;
+  margin: 0 auto;
+  display: block;
+}
+
+.icon-wrapper {
+  width: 68rpx;
+  height: 68rpx;
+  padding: 10rpx;
+  box-sizing: border-box;
+}
+
+.icon-wrapper .icon {
+  width: 100%;
+  height: 100%;
+}
+.icon-wrapper.weight {
+  padding: 0;
+}
+
+.temperture-control-panel .icon-wrapper {
+  display: block;
+
+  margin: 44rpx auto 0 auto;
+}
+</style>
+
+<template>
+  <view
+    v-on:touchstart="touchStart"
+    v-on:touchmove.stop="touchMove"
+    v-on:touchend="touchEnd"
+    data-source="temerturerSlider"
+  >
+    <view
+      class="{{(('temperture-control-panel') + ' ' +((show === true)?'show': 'hide') + ' ' +(type))}}"
+    >
+      <view class="title" wx:if="{{type=='down'}}">
+        <block wx:if="{{moveTimes==0}}">下滑降温</block>
+        <block wx:if="{{moveTimes==1}}">松开降点温</block>
+        <block wx:if="{{moveTimes==2}}">松开快速降温</block>
+      </view>
+      <view class="slider">
+        <view class="rect-slider" :class="{ 'bright': moveTimes>=1 }"></view>
+        <view class="split"></view>
+        <image
+          class="{{(('arrow-slider') + ' ' +((moveTimes>1)?'bright': ''))}}"
+          mode="scaleToFill"
+          src="{{arrowSliderUrl}}"
+        />
+        <view class="icon-wrapper" :class="{ 'weight': moveTimes==2 }">
+          <image src="{{iconUrl}}" class="icon" />
+        </view>
+      </view>
+      <view class="title" wx:if="{{type=='up'}}">
+        <block wx:if="{{moveTimes==0}}">上滑升温</block>
+        <block wx:if="{{moveTimes==1}}">松开升点温</block>
+        <block wx:if="{{moveTimes==2}}">松开快速升温</block>
+      </view>
+    </view>
+
+    <view class="{{(('slot-panel') + ' ' +((show === true)?'hide': 'show') + ' ' +(type))}}">
+      <slot></slot>
+    </view>
+  </view>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+let h5StaticPath = config.h5StaticPath + '/page-intelligent-control/';
+let startY = 0;
+wepy.component({
+  data: {
+    moveTimes: 0
+  },
+  props: {
+    show: Boolean,
+    type: {
+      type: String,
+      default: 'down'
+    }
+  },
+  computed: {
+    iconUrl: function() {
+      let url = h5StaticPath;
+      if (this.type === 'down') {
+        switch (this.moveTimes) {
+          case 0:
+            url += 'snow_light.svg';
+            break;
+          case 1:
+            url += 'snow.svg';
+            break;
+          case 2:
+            url += 'snow_big.svg';
+            break;
+        }
+      } else if (this.type == 'up') {
+        switch (this.moveTimes) {
+          case 0:
+            url += 'sun_light.svg';
+            break;
+          case 1:
+            url += 'sun.svg';
+            break;
+          case 2:
+            url += 'sun_big.svg';
+            break;
+        }
+      }
+      return url;
+    },
+    arrowSliderUrl: function() {
+      let url = h5StaticPath;
+      if (this.type === 'down') {
+        switch (this.moveTimes) {
+          case 0:
+            url += 'icon_down_arrow_light.png';
+            break;
+          case 1:
+            url += 'icon_down_arrow_light.png';
+            break;
+          case 2:
+            url += 'icon_down_arrow.png';
+            break;
+        }
+      } else if (this.type == 'up') {
+        switch (this.moveTimes) {
+          case 0:
+            url += 'icon_up_arrow_light.png';
+            break;
+          case 1:
+            url += 'icon_up_arrow_light.png';
+            break;
+          case 2:
+            url += 'icon_up_arrow.png';
+            break;
+        }
+      }
+      return url;
+    }
+  },
+  methods: {
+    juchangeCanmove(e) {
+      return this.show;
+    },
+    touchStart(e) {
+      startY = Number(e.touches[0].pageY);
+    },
+    touchMove(e) {
+      let touch = e.touches[0], //获取第一个触点
+        y = Number(touch.pageY); //页面触点Y坐标
+      if (!this.juchangeCanmove(e)) {
+        return;
+      }
+
+      let touchLength = parseInt(y - startY);
+      this.calsliderNumber(touchLength, y);
+    },
+    calsliderNumber(touchLength, touchPageY) {
+      if (Math.abs(touchLength) > 30) {
+        //距离够了才算有效操作
+        let moveTimes = this.moveTimes;
+        if (this.type == 'down') {
+          if (touchLength > 0) {
+            //向下加一
+            if (moveTimes >= 2) {
+              return;
+            }
+            ++moveTimes;
+          } else if (touchLength < 0) {
+            //向上减1
+            if (moveTimes <= 0) {
+              return;
+            }
+            --moveTimes;
+          }
+        } else if (this.type == 'up') {
+          //向上滑动
+          if (touchLength < 0) {
+            //向上加一
+            if (moveTimes >= 2) {
+              return;
+            }
+            ++moveTimes;
+          } else if (touchLength > 0) {
+            //向下减1
+            if (moveTimes <= 0) {
+              return;
+            }
+            --moveTimes;
+          }
+        }
+        this.recordInfo(moveTimes, touchPageY);
+      }
+    },
+    recordInfo(moveTimes, touchPageY) {
+      this.moveTimes = moveTimes;
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      }
+      startY = Number(touchPageY);
+    },
+    touchCancel(e) {
+      if (!this.juchangeCanmove(e)) {
+        return;
+      }
+      this.reset();
+    },
+    touchEnd(e) {
+      if (!this.juchangeCanmove(e)) {
+        return;
+      }
+      setTimeout(() => {
+        //松手后0.1s再隐藏
+        this.reset();
+      }, 100);
+    },
+    reset() {
+      this.$emit('component-temperture-control-end', {
+        type: this.type,
+        level: this.moveTimes
+      });
+      startY = 0;
+      this.moveTimes = 0;
+    }
+  }
+});
+</script>
+
+

+ 101 - 0
src/packagesEnv/pages/intelligentControl/components/user-space-list/user-space-list.wpy

@@ -0,0 +1,101 @@
+<style lang="less">
+.component-user-space-list {
+  padding: 48rpx 58rpx 0;
+}
+
+.title {
+  font-family: PingFang SC;
+  font-style: normal;
+  font-weight: normal;
+  font-size: 12px;
+  line-height: 32rpx;
+  color: rgba(9, 9, 61, 0.45);
+}
+
+.list {
+  font-family: PingFang SC;
+  font-style: normal;
+  font-weight: normal;
+}
+
+.list .item {
+  width: 100%;
+  height: 112rpx;
+  line-height: 112rpx;
+  font-size: 14px;
+  border-bottom: 1px solid rgba(2, 2, 82, 0.03);
+}
+
+.list .item .icon {
+  width: 48rpx;
+  height: 48rpx;
+  vertical-align: middle;
+}
+
+.list .item .value {
+  margin-left: 24rpx;
+  color: rgba(13, 13, 61, 0.86);
+}
+
+.list .item .dot {
+  margin-left: 16rpx;
+  width: 2px;
+  height: 2px;
+  vertical-align: middle;
+}
+
+.list .item .space-name {
+  margin-left: 16rpx;
+  color: rgba(6, 6, 61, 0.3);
+}
+</style>
+
+<template>
+  <view class="component-user-space-list">
+    <view class="title">
+      <label>{{title}}</label>
+    </view>
+    <view class="list">
+      <view class="item" v-for="(item, index) in list" v-on:click="toMapPage(item)" >
+        <block wx:if="{{item.key == 'userName'}}">
+          <image class="icon" src="{{h5StaticPath +'icon_user.svg'}}" />
+          <label class="value">{{item.value}}</label>
+          <image class="dot" src="{{h5StaticPath +'dot.svg'}}" />
+          <label class="space-name">{{item.spaceName}}</label>
+        </block>
+        <block wx:if="{{item.key == 'title'}}">
+          <image
+            class="icon"
+            src="{{item.roomFuncType==320?(h5StaticPath+'icon_meetingroom.svg'):(h5StaticPath+'icon_location.svg')}}"
+          />
+          <label class="value">{{item.value}}</label>
+        </block>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+let h5StaticPath = config.h5StaticPath + '/page-intelligent-control/';
+wepy.component({
+  data: {
+    h5StaticPath
+  },
+  props: {
+    title: String,
+    list: Array,
+    onClickItem: Function,
+  },
+  created() {},
+  didUpdate() {},
+  didUnmount() {},
+  methods: {
+    toMapPage(space) {
+      this.$emit('component-user-space-list-click', space);
+    }
+  }
+});
+</script>

+ 612 - 0
src/packagesEnv/pages/intelligentControl/components/workovertime/workovertime.wpy

@@ -0,0 +1,612 @@
+<style lang="less">
+.work-overtime.disabled .tip-text {
+  color: rgba(6, 6, 61, 0.3);
+}
+
+.work-overtime .btn-off-work {
+  width: 128rpx;
+  height: 40rpx;
+  background: rgba(13, 13, 61, 0.86);
+  border-radius: 4rpx;
+  font-size: 24rpx;
+  line-height: 40rpx;
+  text-align: center;
+  color: #ffffff;
+  margin-right: 18rpx;
+}
+
+.work-overtime .main-tip {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.work-overtime .icon-clock {
+  width: 32rpx;
+  height: 32rpx;
+  margin-right: 12rpx;
+  display: block;
+}
+
+.work-overtime .tip-text {
+  font-size: 28rpx;
+  line-height: 28rpx;
+  color: rgba(13, 13, 61, 0.86);
+  margin-right: 8rpx;
+}
+
+.work-overtime .icon-arrow {
+  width: 32rpx;
+  height: 32rpx;
+  display: block;
+}
+
+.work-overtime .choose-work-overtime-panel {
+  position: fixed;
+  bottom: 0;
+  height: 820rpx;
+  left: 0;
+  right: 0;
+  background: #fff;
+  z-index: 2;
+}
+
+.work-overtime .choose-work-overtime-panel .top {
+  height: 200rpx;
+  padding-left: 46rpx;
+  padding-top: 46rpx;
+  text-align: left;
+  box-sizing: border-box;
+  background-color: #f7f7fa;
+}
+
+.work-overtime .choose-work-overtime-panel .tip-text {
+  font-size: 28rpx;
+  line-height: 28rpx;
+  color: rgba(9, 9, 61, 0.45);
+  margin-bottom: 38rpx;
+}
+
+.work-overtime .choose-work-overtime-panel .time-text {
+  font-size: 40rpx;
+  line-height: 40rpx;
+  color: rgba(13, 13, 61, 0.86);
+}
+
+.work-overtime .choose-work-overtime-panel .picker {
+  height: 432rpx;
+  width: 100%;
+  overflow: hidden;
+  .picker-contanier {
+    height: 100%;
+  }
+}
+
+.work-overtime .choose-work-overtime-panel .bottom {
+  height: 188rpx;
+  position: relative;
+}
+
+.work-overtime .choose-work-overtime-panel .cancle-btn {
+  font-size: 32rpx;
+  line-height: 44rpx;
+  color: #c4c4c4;
+  position: absolute;
+  top: 38rpx;
+  left: 48rpx;
+}
+
+.work-overtime .choose-work-overtime-panel .confirm-btn {
+  width: 264rpx;
+  height: 100rpx;
+  background: #ffda1a;
+  box-shadow: 0px 8rpx 12rpx rgba(0, 0, 0, 0.07);
+  border-radius: 60rpx;
+  font-weight: 500;
+  font-size: 28rpx;
+  line-height: 100rpx;
+  text-align: center;
+  color: #000000;
+  margin: 10rpx auto 0;
+}
+
+.work-overtime .picker-row-item {
+  font-size: 46rpx;
+  line-height: 56rpx;
+  text-align: center;
+  color: #232326;
+}
+</style>
+
+<template>
+  <div class="work-overtime" v-if="canset">
+    <div class="main-tip" v-on:click="showPanel">
+      <div class="btn-off-work" v-if="showWorkOff" v-on:click.stop="workOff">提前关闭</div>
+      <div class="tip-text">{{tipText}}</div>
+      <image
+        mode="scaleToFill"
+        class="icon-arrow"
+        src="{{h5StaticPath}}icon_arrow-right-weight-gray.svg"
+      />
+    </div>
+    <div class="choose-work-overtime-panel" v-if="show" v-on:touchmove.stop="stopPragtion">
+      <div class="top">
+        <div class="tip-text">空间设备运行时间</div>
+        <div class="time-text">{{pickerTipText}}</div>
+      </div>
+      <div class="picker">
+        <picker-view :value="value" bindchange="changeServerTime" class="picker-contanier">
+          <picker-view-column>
+            <div
+              class="picker-row-item"
+              v-for="(item,index) in startList"
+              :key="index"
+            >{{item.label}}</div>
+          </picker-view-column>
+          <picker-view-column>
+            <div
+              class="picker-row-item"
+              v-for="(itemName,index) in endList"
+              :key="index"
+            >{{itemName.label}}</div>
+          </picker-view-column>
+        </picker-view>
+      </div>
+      <div class="bottom">
+        <div class="cancle-btn" v-on:click="doCancel">取消</div>
+        <div class="confirm-btn" v-on:click="doConfirm">确定</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+import {
+  getCustomServerTimeHttp,
+  saveCustomServerTimeHttp
+} from '@/packagesEnv/api/intelligentControl';
+import utils from '@/common/utils';
+let h5StaticPath = config.h5StaticPath + '/page-intelligent-control/';
+
+const LimitTime = 8;
+
+wepy.component({
+  data: {
+    h5StaticPath: h5StaticPath,
+    hasNocus: false,
+    canset: true,
+    showWorkOff: false,
+    value: [0, 0],
+    show: false,
+    startList: [],
+    endList: [],
+    record: {},
+    tipText: '',
+    pickerTipText: ''
+  },
+  props: {
+    spaceId: String
+  },
+  // created() {
+  //   this.getTimeInfo();
+  // },
+  watch: {
+    spaceId: function(newValue, oldValue) {
+      if (newValue !== oldValue && newValue) {
+        this.reset();
+        this.getTimeInfo();
+      }
+    }
+  },
+  didUpdate() {},
+  didUnmount() {},
+  methods: {
+    reset() {
+      this.hasNocus = false;
+      this.canset = true;
+      this.showWorkOff = false;
+      this.show = false;
+    },
+    getTimeInfo() {
+      getCustomServerTimeHttp(this.spaceId).then(res => {
+        let result = res.content || {};
+        this.record = result;
+        this.canset = result.isOpen;
+        let nowTimeValue = getNowTimeValue();
+        if (nowTimeValue < LimitTime) {
+          // 8点前的逻辑
+          this.showBeforeLimit(result);
+        } else {
+          // 8点后的多级
+          this.judgeShowWorkOff(result.cusEndTime, result.endTime);
+
+          if (!result.cusStartTime && !result.cusEndTime) {
+            // 没有服务时间且没有加班时间
+            if (!result.startTime && !result.endTime) {
+              this.tipConfigCusTime();
+            } else {
+              // 没有有服务时间有加班时间
+              this.showNoCusWithOverwork(result.endTime);
+            }
+          } else {
+            // 有定制时间的时候
+            this.showWithCus(result);
+          }
+        }
+      });
+    },
+    showPanel() {
+      this.show = true;
+    },
+    changeServerTime(e) {
+      let value = e.$wx.detail.value;
+      let { startList, endList } = this;
+      let pickerTipText = `${startList[value[0]].label}-${
+        endList[value[1]].label
+      }`;
+      if (endList[value[1]].label == '通宵') {
+        pickerTipText = '设备将通宵运行';
+      }
+      this.pickerTipText = pickerTipText;
+      this.value = [...value];
+    },
+    doConfirm() {
+      let endIndex = this.value[1] == -1 ? 0 : this.value[1];
+      let start = formatTimeStrToParamstr(this.startList[this.value[0]].value);
+      let end = formatTimeStrToParamstr(this.endList[endIndex].value);
+
+      let data = {
+        objectId: this.spaceId // 空间ID
+      };
+      if (!this.record.cusEndTime && !this.record.cusStartTime) {
+        data = {
+          ...data,
+          startTime: start, // 加班的开始时间
+          endTime: end
+        };
+      } else {
+        if (this.record.cusEndTime == end) {
+          data = {
+            ...data,
+            cusEndTime: this.record.cusEndTime,
+            cusStartTime: this.record.cusStartTime,
+            startTime: null
+          };
+        } else {
+          data = {
+            ...data,
+            cusEndTime: this.record.cusEndTime,
+            cusStartTime: this.record.cusStartTime,
+            startTime: this.record.cusEndTime, // 加班的开始时间,时分秒
+            endTime: end // 加班的结束时间
+          };
+        }
+      }
+
+      saveCustomServerTimeHttp(data).then(
+        res => {
+          this.closePanel();
+          this.getTimeInfo();
+        },
+        res => {
+          wx.showToast({
+            title: res.message,
+            duration: 1000,
+            icon: 'none'
+          });
+        }
+      );
+    },
+    workOff() {
+      saveCustomServerTimeHttp({
+        objectId: this.spaceId, // 空间ID
+        type: 3
+      }).then(
+        reuslt => {
+          if (reuslt.result === 'success') {
+            this.getTimeInfo();
+            wx.showToast({
+              title: '操作成功,将为您关闭空间内的设备',
+              duration: 3000,
+              icon: 'none'
+            });
+          } else {
+            wx.showToast({
+              title: reuslt.message,
+              duration: 3000,
+              icon: 'none'
+            });
+          }
+          this.showWorkOff = false;
+        },
+        res => {
+          wx.showToast({
+            title: reuslt.message,
+            duration: 3000,
+            icon: 'none'
+          });
+        }
+      );
+    },
+    doCancel() {
+      // todo 重新拉取数据设置为更改前的数据
+      this.getTimeInfo();
+      this.closePanel();
+    },
+    closePanel() {
+      this.show = false;
+    },
+    stopPragtion() {},
+    tipConfigCusTime() {
+      let { startList, endList } = this.generatListByNow();
+      this.tipText = '配置设备运行时间';
+      this.pickerTipText = startList[0].label + '-' + endList[0].label;
+      this.startList = [...startList];
+      this.endList = [...endList];
+      this.value = [0, 0];
+    },
+    generateListByTime(start) {
+      let endList = [];
+      for (var i = start; i < 24; i++) {
+        let time = parseInt(i) + 1;
+        time = time < 10 ? '0' + time : time;
+        endList.push({
+          label: time + ':00',
+          value: time
+        });
+      }
+      endList.push({
+        label: '通宵',
+        value: '通宵'
+      });
+      return endList;
+    },
+    generatListByNow() {
+      let now = new Date();
+      let hour = now.getHours();
+      let startList = [
+        {
+          label: utils.getTwoNum(hour) + ':00',
+          value: hour
+        }
+      ];
+      let endList = this.generateListByTime(hour);
+      return {
+        startList,
+        endList
+      };
+    },
+    showNoCusWithOverwork(endTime) {
+      let { startList, endList } = this.generatListByNow();
+      let tipText = '';
+      let endIndex = 0;
+      if (endTime === '通宵') {
+        tipText = '设备将通宵运行';
+        endIndex = endList.length - 1;
+      } else {
+        let endTimeValue = formatTimeStr(endTime);
+        let endTimeValues = endTimeValue.split(':');
+        let now = getNowTimeValue();
+        let endjuddgeTime = getStrTimeValue(endTimeValue);
+        if (now > endjuddgeTime) {
+          tipText = `配置设备运行时间`;
+          this.showWorkOff = false;
+        } else {
+          tipText = `设备将于${endTimeValue}关闭`;
+          endIndex = endList.findIndex(item => item.value == endTimeValues[0]);
+        }
+      }
+
+      this.tipText = tipText;
+      this.pickerTipTextst = startList[0].label + '-' + endList[endIndex].label;
+      this.startList = [...startList];
+      this.endList = [...endList];
+      this.value = [0, endIndex];
+    },
+    judgeShowWorkOff(cusendTime, endTime) {
+      // 如果没有加班时间,就不展示
+      if (!endTime) {
+        this.showWorkOff = false;
+        return;
+      }
+      // 如果没有定制时间,有加班时间
+      if (!cusendTime && endTime) {
+        this.showWorkOff = true;
+        return;
+      }
+
+      // 如有有加班时间,有定制时间,当前时间在定制结束时间以后,在加班时间以内都可以提前下班
+      let showWorkOff = false;
+      let nowHour = new Date().getHours();
+      let nowMinuts = new Date().getMinutes();
+      let nowValue = nowHour + nowMinuts / 60;
+
+      let cusendTimeValues = formatTimeStr(cusendTime).split(':');
+      let cusendTimeValue =
+        parseInt(cusendTimeValues[0]) + parseInt(cusendTimeValues[1]) / 60;
+
+      // 当前时间大于服务定制时间,小于加班时间
+      if (nowValue > cusendTimeValue) {
+        if (endTime.indexOf('通宵') !== -1) {
+          showWorkOff = true;
+        } else {
+          let endtimeValues = formatTimeStr(endTime).split(':');
+          let endtimeValue =
+            parseInt(endtimeValues[0]) + parseInt(endtimeValues[1]) / 60;
+          if (nowValue < endtimeValue) {
+            showWorkOff = true;
+          }
+        }
+      }
+      this.showWorkOff = showWorkOff;
+    },
+
+    showBeforeLimit(result) {
+      // 如果没有加班时间
+      let tipText = '';
+      let pickerTipText = '通宵运行';
+      let showWorkOff = false;
+      // 如果没有加班时间
+      if (!result.endTime || !result.startTime) {
+        tipText = '设备已关闭';
+      } else {
+        tipText = '设备将通宵运行';
+        showWorkOff = true;
+      }
+      let nowHour = new Date().getHours();
+
+      this.tipText = tipText;
+      this.pickerTipText = pickerTipText;
+      this.showWorkOff = showWorkOff;
+      this.startList = [
+        {
+          label: utils.getTwoNum(nowHour) + ':00',
+          value: nowHour
+        }
+      ];
+      this.endList = [
+        {
+          label: '通宵',
+          value: 8
+        }
+      ];
+    },
+    showWithCus(result) {
+      let end = result.endTime || result.cusEndTime;
+      let start = result.cusStartTime;
+      let now = getNowTimeValue();
+      let cusEndTimeStr = formatTimeStr(result.cusEndTime);
+      let cusEndTime = getStrTimeValue(cusEndTimeStr);
+      let endTimeStr = formatTimeStr(end);
+      let startTimeStr = formatTimeStr(start);
+      let startTime = getStrTimeValue(startTimeStr);
+      let endTime = getStrTimeValue(endTimeStr);
+      let startList = [
+        {
+          label: startTimeStr,
+          value: startTime
+        }
+      ];
+      let endList = [];
+      let tipText = '';
+      let pickerTipText = '';
+      let showWorkOff = false;
+      endTime =
+        endTime == '通宵'
+          ? '通宵'
+          : endTime > cusEndTime
+          ? endTime
+          : cusEndTime;
+      endTimeStr = getTimeValueStr(endTime);
+      let endJuddgeValue = endTime == '通宵' ? 25 : endTime;
+      pickerTipText = `${startTimeStr} - ${endTimeStr}`;
+      if (now < startTime) {
+        // before - 再服务时间开启之前
+        tipText = `设备将于${startTimeStr}开启`;
+        endList = this.generateListByTime(parseInt(cusEndTime - 1));
+      } else if (now < endJuddgeValue) {
+        // in-在服务开启到加班或服务结束以前
+        if (endTime == '通宵') {
+          tipText = `设备将通宵运行`;
+          pickerTipText = '通宵运行';
+          if (now < cusEndTime) {
+            endList = this.generateListByTime(parseInt(cusEndTime - 1));
+          } else {
+            let list = this.generatListByNow();
+            endList = list.endList;
+          }
+        } else {
+          tipText = `设备将于${endTimeStr}关闭`;
+          if (now > cusEndTime) {
+            let list = this.generatListByNow();
+            endList = list.endList;
+          } else {
+            endList = this.generateListByTime(parseInt(cusEndTime - 1));
+          }
+        }
+      } else {
+        // after-服务|加班结束范围之后
+        let list = this.generatListByNow();
+        endList = list.endList;
+        if (endTime == '通宵') {
+          tipText = `设备将通宵运行`;
+          pickerTipText = '通宵运行';
+        } else {
+          tipText = '设备已关闭';
+          pickerTipText = `${startTimeStr} - ${endList[0].label}`;
+        }
+      }
+
+      let endIndex = endList.findIndex(item => item.label == endTimeStr);
+      this.endIndex = endIndex == -1 ? 0 : endIndex;
+      this.tipText = tipText;
+      this.pickerTipText = pickerTipText;
+      this.endList = endList;
+      this.startList = startList;
+      this.value = [0, endIndex];
+    }
+  }
+});
+
+function formatTimeStr(str) {
+  if (!str) {
+    return '';
+  }
+  if (str.indexOf('通宵') !== -1) {
+    return '通宵';
+  }
+  let hour = str.substr(0, 2);
+  let minute = str.substr(2, 2);
+  return hour + ':' + minute;
+}
+
+function formatTimeStrToParamstr(str) {
+  str = str + '';
+  if (!str) {
+    return '';
+  }
+  if (str == '通宵') {
+    return '通宵';
+  }
+  let value = str.split('.');
+  let hour = utils.getTwoNum(parseInt(value[0]));
+  let minutes = '00';
+  if (value[1]) {
+    minutes = utils.getTwoNum(parseInt(value[1] * 60));
+  }
+  return [hour, minutes].join('') + '00';
+}
+
+function getStrTimeValue(str) {
+  if (!str) {
+    return '';
+  }
+  if (str == '通宵') {
+    return '通宵';
+  }
+  let values = str.split(':');
+  return parseInt(values[0]) + parseInt(values[1]) / 60;
+}
+
+function getNowTimeValue() {
+  let now = new Date();
+  let hour = parseInt(now.getHours());
+  let minuts = parseInt(now.getMinutes());
+  return hour + minuts / 60;
+}
+
+function getTimeValueStr(time) {
+  if (time == '通宵') {
+    return '通宵';
+  }
+  let values = (time + '').split('.');
+  let hour = parseInt(values[0]);
+  let minuts = 0;
+  if (values[1]) {
+    minuts = parseFloat('0.' + values[1]) * 60;
+  }
+  return utils.getTwoNum(hour) + ':' + utils.getTwoNum(minuts);
+}
+</script>

文件差異過大導致無法顯示
+ 830 - 0
src/packagesEnv/pages/intelligentControl/home.wpy


文件差異過大導致無法顯示
+ 924 - 0
src/packagesEnv/pages/intelligentControl/home2.wpy


+ 142 - 0
src/packagesEnv/pages/intelligentControl/meetingroomlist/meetingroomlist.wpy

@@ -0,0 +1,142 @@
+<style lang="less">
+.page-meetingroom-list {
+  height: 100%;
+  width: 100%;
+}
+.list {
+  height: 100%;
+  width: 100%;
+  padding: 0 48rpx;
+  box-sizing: border-box;
+}
+
+.list .item {
+  width: 100%;
+  height: 112rpx;
+  line-height: 112rpx;
+  font-size: 14px;
+  border-bottom: 1px solid rgba(2, 2, 82, 0.03);
+}
+
+.list .item .icon {
+  width: 48rpx;
+  height: 48rpx;
+  vertical-align: middle;
+}
+
+.list .item .value {
+  margin-left: 24rpx;
+  color: rgba(13, 13, 61, 0.86);
+}
+</style>
+
+<template>
+  <view class="page-meetingroom-list">
+    <view class="list">
+      <view class="item" v-for="(item, index) in list" bindtap="toMapPage" data-item="{{item}}">
+        <image class="icon" src="{{h5StaticPath+'icon_meetingroom.svg'}}" />
+        <label class="value">{{item.value}}</label>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import { checkHasUserInfo } from '@/service/user';
+
+import {
+  setLastSearchedSpace,
+  getHistorySearchedSpaceList,
+  setHistorySearchedSpaceList
+} from '../service/search';
+import { getMapInfoHttp } from '@/packagesEnv/api/intelligentControl';
+
+import config from '@/config';
+import { getCompanyMapData } from '@/api/home';
+
+let h5StaticPath = config.h5StaticPath + '/page-intelligent-control/';
+let pageQuery = {};
+let CompanyMapData;
+let combinedData = [];
+wepy.page({
+  data: {
+    list: [],
+    h5StaticPath,
+    companyId: ''
+  },
+  onLoad(query) {
+    pageQuery = query;
+    let meetingList = [];
+    checkHasUserInfo()
+      .then(userInfo => {
+        this.companyId = query.companyId || userInfo.companyId;
+        return getCompanyMapData(this.companyId);
+      }).then(res => {
+        CompanyMapData = JSON.parse(res.data);
+         return getMapInfoHttp();
+      })
+      .then(res => {
+        const result = JSON.parse(res.data);
+        const httpDdata = (result && result.content) || [];
+        combinedData = httpDdata.reduce((acc, cur) => {
+          const target = acc.find(e => e.id == cur.id);
+          if (target) {
+            Object.assign(target, { title: cur.localName });
+            // console.log('找到了', cur, index);
+          } else {
+            // console.log('data有没找到', cur.id);
+          }
+          return acc;
+        }, CompanyMapData.mapInfo);
+
+        combinedData.forEach(item => {
+          if (item.roomFuncType == 320) {
+            meetingList.push({
+              key: 'title',
+              spaceId: item.id,
+              value: item.title,
+              roomFuncType: item.roomFuncType
+            });
+          }
+        });
+        this.list = meetingList || [];
+      });
+  },
+  methods: {
+    toMapPage(e) {
+      let item = e.target.dataset.item;
+      setLastSearchedSpace(item);
+      getHistorySearchedSpaceList().then(historyList => {
+        setHistorySearchedSpaceList(historyList, item);
+        if (pageQuery.from == 'home') {
+          wx.navigateTo({
+            url: `/packagesEnv/pages/officehome/index?spaceId=${item.spaceId}&companyId=${this.companyId}`
+          });
+        } else if (pageQuery.from == 'officehome') {
+          wx.navigateBack({
+            delta: 2
+          });
+        } else {
+          wx.navigateBack({
+            delta: 3
+          });
+        }
+      });
+    }
+  }
+});
+</script>
+
+<config>
+{
+  
+    navigationBarTitleText: '所有会议室',
+    navigationBarTextStyle: 'black',
+    navigationBarBackgroundColor: '#FFFFFF',
+    "backgroundColor": "#FFFFFF",
+    usingComponents: {
+     "user-space-list": "../components/user-space-list/user-space-list"
+    }
+}
+</config>

+ 305 - 0
src/packagesEnv/pages/intelligentControl/search/search.wpy

@@ -0,0 +1,305 @@
+<style lang="less">
+.page-search {
+  position: relative;
+}
+
+.input-wrap {
+  display: flex;
+  position: relative;
+  margin: 16rpx auto 0;
+  width: 686rpx;
+  height: 96rpx;
+  line-height: 96rpx;
+  background: rgba(2, 2, 82, 0.03);
+  border-radius: 16rpx;
+}
+
+.input-wrap input,
+textarea {
+  width: 100%;
+  height: 100%;
+  padding: 0 88rpx;
+  box-sizing: border-box;
+  background-color: transparent;
+  font-size: 14px;
+}
+
+.input-wrap .icon {
+  width: 48rpx;
+  height: 48rpx;
+  display: block;
+}
+
+.input-wrap .icon-search-wrap {
+  position: absolute;
+  left: 16rpx;
+  top: 24rpx;
+}
+
+.input-wrap .icon-close-wrap {
+  position: absolute;
+  right: 32rpx;
+  top: 24rpx;
+}
+
+.btn-to-meetingroom {
+  font-size: 28rpx;
+  line-height: 32rpx;
+  text-align: right;
+  color: #4d94ff;
+  width: 100%;
+  text-align: center;
+  margin: 112rpx auto 0 auto;
+}
+
+.btn-to-meetingroom.has-history {
+  position: absolute;
+  top: 160rpx;
+  right: 48rpx;
+  text-align: right;
+  margin: 0;
+  font-size: 24rpx;
+}
+
+.user-space-list-wrap {
+  width: 100%;
+}
+
+.user-space-list-wrap .none-wrap {
+  margin-top: 112rpx;
+  font-family: PingFang SC;
+  font-style: normal;
+  font-weight: normal;
+  font-size: 14px;
+  line-height: 32rpx;
+  color: rgba(6, 6, 61, 0.3);
+  text-align: center;
+}
+</style>
+
+<template>
+  <view class="page-search">
+    <view class="input-wrap">
+      <label for="spaceSearchInputId" class="icon-search-wrap">
+        <image class="icon" src="{{h5StaticPath+ 'icon_search.svg'}}" />
+      </label>
+      <input
+        id="spaceSearchInputId"
+        value="{{inputValue}}"
+        bindinput="getInputValue"
+        placeholder="{{canSearchPerson?'输入人名/地点': '输入地点'}}"
+        focus="{{autoFocus}}"
+      />
+      <label for="spaceSearchInputId" class="icon-close-wrap">
+        <image
+          wx:if="{{inputValue}}"
+          bindtap="clearInput"
+          class="icon"
+          src="{{h5StaticPath+ 'icon_close.svg'}}"
+        />
+      </label>
+    </view>
+    <view
+      wx:if="{{!inputValue}}"
+      class="{{'btn-to-meetingroom ' + (historyList.length ? 'has-history' : '')}}"
+      bindtap="goToMeetingroomList"
+    >查看所有会议室</view>
+    <view class="user-space-list-wrap">
+      <block wx:if="{{!inputValue}}">
+        <user-space-list
+          wx:if="{{historyList.length}}"
+          title="最近查找"
+          list="{{historyList}}"
+          @component-user-space-list-click="toMapPage($event)"
+        ></user-space-list>
+      </block>
+      <block wx:else>
+        <block wx:if="{{searchResultList.length}}">
+          <user-space-list
+            title="匹配结果"
+            list="{{searchResultList}}"
+            @component-user-space-list-click="toMapPage($event)"
+          ></user-space-list>
+        </block>
+        <block wx:else>
+          <view class="none-wrap">
+            <label>没有匹配的结果</label>
+          </view>
+        </block>
+      </block>
+    </view>
+  </view>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import { checkHasUserInfo } from '@/service/user';
+import {
+  setLastSearchedSpace,
+  getHistorySearchedSpaceList,
+  setHistorySearchedSpaceList
+} from '../service/search';
+import config from '@/config';
+import {
+  getMapInfoHttp,
+  getUserSpaceList
+} from '@/packagesEnv/api/intelligentControl';
+import utils from '@/common/utils';
+import { getCompanyMapData } from '@/api/home';
+
+let h5StaticPath = config.h5StaticPath + '/page-intelligent-control/';
+let combinedData = [];
+let pageQuery;
+let CompanyMapData;
+wepy.page({
+  data: {
+    inputValue: '',
+    historyList: [],
+    searchResultList: [],
+    h5StaticPath,
+    canSearchPerson: true,
+    autoFocus: false,
+    userSpaceList: [],
+    companyId: ''
+  },
+  onLoad(query) {
+    this.canSearchPerson = query.canSearchPerson
+      ? JSON.parse(query.canSearchPerson)
+      : true; // 将字符串false/true 转为boolear
+    pageQuery = query;
+    checkHasUserInfo().then(userInfo => {
+      let companyId = query.companyId || userInfo.companyId;
+
+      this.companyId = companyId;
+      getCompanyMapData(companyId).then(res => {
+        CompanyMapData = JSON.parse(res.data);
+        getMapInfoHttp(companyId).then(res => {
+          const result = JSON.parse(res.data);
+          const httpDdata = (result && result.content) || [];
+          combinedData = httpDdata.reduce((acc, cur) => {
+            const target = acc.find(e => e.id == cur.id);
+            if (target) {
+              Object.assign(target, { title: cur.localName });
+              // console.log('找到了', cur, index);
+            } else {
+              // console.log('data有没找到', cur.id);
+            }
+            return acc;
+          }, CompanyMapData.mapInfo);
+        });
+      });
+
+      getHistorySearchedSpaceList().then(historyList => {
+        this.historyList = historyList;
+      });
+
+      getUserSpaceList(this.companyId).then(res => {
+        this.userSpaceList = res.data || [];
+      });
+    });
+  },
+  onReady() {
+    this.autoFocus = true;
+  },
+  onShow() {},
+  methods: {
+    getInputValue(e) {
+      let keyword = e.$wx.detail.value || '';
+      keyword = keyword.trim();
+      let searchResultList = [];
+      if (keyword) {
+        const regExp = utils.getSearchRegEx(keyword);
+        const searchedSpaceResult = search(regExp, combinedData, 'title');
+        searchResultList = searchResultList.concat(searchedSpaceResult);
+
+        if (this.canSearchPerson) {
+          let searchedUserResult = search(
+            regExp,
+            this.userSpaceList,
+            'userName'
+          );
+          searchedUserResult.forEach(user => {
+            combinedData.find(space => {
+              if (space.id == user.spaceId) {
+                user.spaceName = space.title;
+                return true;
+              }
+              return false;
+            });
+          });
+          searchResultList = searchResultList.concat(searchedUserResult);
+        }
+      }
+
+      this.inputValue = keyword;
+      this.searchResultList = searchResultList;
+    },
+    toMapPage(item) {
+      setLastSearchedSpace(item).then(() => {
+        var lastSearchedSpace = wx.getStorageSync({
+          key: 'lastSearchedSpace'
+        }); // 从搜索页面返回且搜索有效
+        let historyList = this.historyList;
+        let newHistoryList = setHistorySearchedSpaceList(historyList, item);
+        this.historyList = newHistoryList;
+        if (pageQuery.from == 'home') {
+          wx.navigateTo({
+            url: `/packagesEnv/pages/officehome/index?spaceId=${item.spaceId}`
+          });
+        } else if (pageQuery.from == 'officehome') {
+          wx.navigateBack();
+        } else {
+          wx.navigateBack({
+            delta: 2
+          });
+        }
+      });
+    },
+    clearInput() {
+      this.inputValue = '';
+      this.searchResultList = [];
+    },
+    goToMeetingroomList() {
+      if (pageQuery.from == 'home') {
+        wx.navigateTo({
+          url: `../meetingroomlist/meetingroomlist?from=home&companyId=${this.companyId}`
+        });
+      } else if (pageQuery.from == 'officehome') {
+        wx.navigateTo({
+          url: `../meetingroomlist/meetingroomlist?from=officehome&companyId=${this.companyId}`
+        });
+      } else {
+        wx.navigateTo({
+          url: `../meetingroomlist/meetingroomlist?companyId=${this.companyId}`
+        });
+      }
+    }
+  }
+});
+function search(regExp, list, key) {
+  let resultList = [];
+  list.forEach(item => {
+    const value = item[key];
+    if (value && regExp.exec(value)) {
+      resultList.push({
+        spaceId: item.id,
+        value: value,
+        key: key,
+        roomFuncType: item.roomFuncType
+      });
+    }
+  });
+  return resultList;
+}
+</script>
+
+<config>
+{
+    navigationBarTitleText: '',
+    navigationBarBackgroundColor: '#FFFFFF',
+    "backgroundColor": "#FFFFFF",
+    usingComponents: {
+     "user-space-list": "../components/user-space-list/user-space-list"
+    }
+}
+</config>

+ 168 - 0
src/packagesEnv/pages/intelligentControl/service/location.js

@@ -0,0 +1,168 @@
+import config from '@/config';
+import store from '@/store';
+
+export {
+  // 判断当前位置是否在指定坐标的一定范围内
+  isWithinLocation,
+  checkLoactionInDestance
+}
+
+function isWithinLocation() {
+  let targetLocationInfo, maxDistance;
+  // 是否有权限不限制距离 true-不限制,false-限制
+  let remoteControl = store.state.user.userInfo.remoteControl;
+  let companyConfig = store.state.company.companyConfig;
+
+  if (companyConfig.sagaCare && companyConfig.sagaCareLimit) {
+    maxDistance = companyConfig.sagaCareDistance;
+    let location = companyConfig.sagaCareCoords.split(',');
+    targetLocationInfo = {
+      longitude: location[0],
+      latitude: location[1]
+    }
+  };
+  maxDistance = maxDistance || 1;
+  let toastTip = '您好像不在公司';
+  if (remoteControl || (companyConfig.sagaCare && !companyConfig.sagaCareLimit)) {
+    return Promise.resolve();
+  } else if (config.isOpenLocationPermisson) {
+    return checkLoactionInDestance(targetLocationInfo, maxDistance).then((res) => {
+      return Promise.resolve(res);
+    }, (res) => {
+      if (res.reason == 'overDistance') {
+        setTimeout(() => {
+          wx.showToast({
+            title: toastTip,
+            duration: 2000,
+            icon: 'none'
+          })
+        }, 0);
+      }
+      return Promise.reject(res);
+    })
+  } else {
+    return Promise.resolve()
+  }
+}
+
+function checkLoactionInDestance(targetLocationInfo, maxDistance) {
+  return new Promise((resolve, reject) => {
+    let locationInfo = store.state.location.locationInfo;
+    if (locationInfo.getLastTime + 30e3 > new Date().getTime()) { // - 30秒内返回旧值
+      locationInfo.method === 'resolve' ? resolve() : reject(locationInfo.data);
+      return;
+    }
+
+    wx.getNetworkType({
+      success(res) {
+        const networkType = res.networkType;
+        if (networkType == 'none') {
+          wx.showToast({
+            title: '网络故障,请稍后重试',
+            duration: 2000,
+            icon: 'none'
+          })
+          reject({ reason: 'networkException' });
+        } else {
+          wx.authorize({
+            scope: 'scope.userLocation',
+            success: () => {
+              wx.getLocation({
+                success(res) {
+                  let distance = getDistance(res.latitude, res.longitude, targetLocationInfo.latitude, targetLocationInfo.longitude);
+               
+                  if (distance <= maxDistance) {
+                    store.commit('setLocationInfo', {
+                      getLastTime: new Date().getTime(),
+                      data: null,
+                      method: 'resolve'
+                    });
+                    resolve()
+                  } else {
+                    store.commit('locaitonInfo', {
+                      getLastTime: new Date().getTime(),
+                      data: { reason: 'overDistance' },
+                      method: 'reject'
+                    });
+                    reject({ reason: 'overDistance' });
+                  }
+                },
+                fail(res) {
+                  let msg = res.errMsg;
+                  switch (res.error) {
+                    case 11:
+                      msg = '无法使用位置服务';
+                      break;
+                    case 12:
+                      msg = '网络异常,请稍后再试';
+                      break;
+                    case 13:
+                      msg = '定位失败,请稍后再试';
+                      break;
+                    case 14:
+                      msg = '业务定位超时,请稍后再试';
+                      break;
+                    default:
+                      msg = '请前往系统设置开启微信定位权限';
+                  }
+                  store.commit('locaitonInfo', {
+                    getLastTime: new Date().getTime(),
+                    data: { reason: 'systemError' },
+                    method: 'reject'
+                  });
+                  reject({ reason: 'systemError' });
+                  setTimeout(() => {
+                    wx.showToast({
+                      title: msg,
+                      duration: 2000,
+                      icon: 'none'
+                    });
+                  }, 0);
+                }
+              })
+            },
+            fail: () => {
+              wx.showModal({
+                title: '提示',
+                content: '请授权位置信息',
+                success(res) {
+                  if (res.confirm) {
+                    wx.openSetting({
+                      success: () => {
+                      },
+                      fail: (res) => {
+                        console.log(res);
+                      }
+                    });
+                  }
+                }
+              })
+              store.commit('locaitonInfo', {
+                getLastTime: new Date().getTime(),
+                data: { reason: 'unAuthorized' },
+                method: 'reject'
+              });
+              reject({ reason: 'unAuthorized' });
+            }
+          })
+        }
+      }
+    })
+  })
+}
+
+/**
+* 计算两个经纬度的距离(千米)
+*/
+function getDistance(lat1, lng1, lat2, lng2) {
+  var radLat1 = lat1 * Math.PI / 180.0;
+  var radLat2 = lat2 * Math.PI / 180.0;
+  var a = radLat1 - radLat2;
+  var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
+  var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
+    Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
+  s = s * 6378.137;// EARTH_RADIUS;
+  s = Math.round(s * 10000) / 10000;
+  console.log('getDistance计算两个经纬度的距离(千米)', lat1, lng1, lat2, lng2, s)
+  return s;
+}

+ 68 - 0
src/packagesEnv/pages/intelligentControl/service/search.js

@@ -0,0 +1,68 @@
+function setLastSearchedSpace(item) {
+  if (!(item && item.spaceId)) {
+    return Promise.reject()
+  }
+  return new Promise((resolve, reject) => {
+    wx.setStorage({
+      key: "lastSearchedSpace",
+      data: {
+        spaceId: item.spaceId
+      },
+      success: () => {
+        resolve()
+      },
+      fail: () => {
+        reject()
+      }
+    });
+  })
+}
+
+
+function setHistorySearchedSpaceList(historyList, item) {
+  // 去重
+  historyList.find((history, index) => {
+    if (history.spaceId == item.spaceId && history.value == item.value) {
+      historyList.splice(index, 1);
+      return true;
+    }
+    return false;
+  });
+  historyList.unshift(item);
+  const maxLength = 10;
+  // 最多记录10条数据
+  if (historyList.length > maxLength) {
+    historyList.splice(maxLength, historyList.length - maxLength);
+  }
+
+  wx.setStorage({
+    key: "historySearchedSpaceList",
+    data: {
+      historyList: historyList,
+    },
+  });
+  return historyList;
+}
+
+function getHistorySearchedSpaceList() {
+  return new Promise((resolve, reject) => {
+    wx.getStorage({
+      key: "historySearchedSpaceList",
+      success: (res) => {
+        if (res && res.data) {
+          const historyList = res.data.historyList || [];
+          resolve(historyList);
+        }
+      },
+      fail: () => {
+        resolve([]);
+      }
+    });
+  })
+}
+
+export {
+  setLastSearchedSpace,
+  getHistorySearchedSpaceList,
+  setHistorySearchedSpaceList
+}

+ 476 - 0
src/packagesEnv/pages/officehome/Curtain/curtain-card.wpy

@@ -0,0 +1,476 @@
+<style lang="less" scoped>
+.curtain-card {
+    color: #c4c4c4;
+    margin-bottom: 30rpx;
+    border-radius: 25px;
+    background: #ffffff;
+    box-shadow: 0px 4px 10px 0px #0000000d, 0px 0px 2px 0px #00000012;
+    .curtain-card-box {
+        // height: 140px;
+        margin-bottom: 28rpx;
+        position: relative;
+    }
+    .curtain-content {
+        display: flex;
+        justify-content: space-between;
+        .curtain-cart-img {
+            height: 200rpx;
+            width: 330rpx;
+        }
+        .imgSt {
+            border-radius: 25px;
+        }
+    }
+    .curtain-title {
+        font-size: 32rpx;
+        font-weight: 600;
+        padding-top: 48rpx;
+        margin-left: 40rpx;
+        line-height: 44rpx;
+        color: rgba(77, 82, 98, 1);
+        font-family: PingFang SC;
+    }
+
+    .cutain-switch {
+        display: flex;
+        justify-content: flex-end;
+        margin-right: 40rpx;
+    }
+    .total-sw{
+        display: flex;
+        justify-content: flex-end;
+        border-top: 1px solid #E8ECF0;
+        padding-top: 32rpx;
+        padding-right: 40rpx;
+        padding-bottom: 32rpx;
+    }
+    .mr40 {
+        margin-right: 24rpx;
+    }
+    .mr40:last-child {
+        margin-right: 0rpx;
+    }
+    .line-center {
+        text-align: center;
+        color: #000;
+        height: 40rpx;
+        line-height: 40rpx;
+    }
+    .switch {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 80rpx;
+        width: 80rpx;
+        background: #F1F4F6;
+        border-radius: 50%;
+        &.yell {
+            background: #ffe823;
+        }
+    }
+    .style-hover{
+        background: #ffe823;
+    }
+    .arrow-size {
+        height: 32rpx;
+        width: 32rpx;
+    }
+    image {
+        width: 100%;
+        height: 100%;
+    }
+    .curtain-child {
+        .curtain-child-item {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding-left: 40rpx;
+            padding-bottom: 32rpx;
+            padding-top: 32rpx;
+            height: 144rpx;
+            border-top: 1px solid #e8ecf0;
+            box-sizing: border-box;
+            .curtain-child-item-text {
+                  
+            }
+  
+            .curtain-child-item-status {
+                font-family: PingFang SC;
+                font-size: 13px;
+                font-weight: 400;
+                line-height: 18px;
+            }
+        }
+    }
+}
+</style>
+<template>
+    <div class="curtain-card">
+        <!-- 单个设备 -->
+        <div
+            class="curtain-card-box"
+            v-if="curtainData.length == 1"
+        >
+            <div class="curtain-content">
+                <div class="curtain-title">
+                    <div>窗帘</div>
+                </div>
+                <div class="curtain-cart-img">
+                    <image
+                        class="imgSt"
+                        src="{{h5StaticPath}}curtain_vertical_3.svg"
+                    />
+                </div>
+            </div>
+
+            <div class="cutain-switch">
+                <div
+                    v-for="(btnItem,bi) in btnArr"
+                    :key="bi"
+                    class="mr40 switch"
+                    hover-class="style-hover"
+                    hover-style-time="300"
+                    :class="{'yell': btnItem.timeCount!=0} "
+                    @click="handleTotal(btnItem.setType,btnItem.type,bi,btnArr,curtainData,btnItem.value)"
+                >
+                    <div class="arrow-size">
+                        <div
+                            class="line-center"
+                            v-if="btnItem.timeCount!=0 && btnItem.setType!='EquipStop'"
+                        >{{btnItem.timeCount }}</div>
+                        <image
+                            v-else
+                            src="{{h5StaticPath}}{{btnItem.icon}}"
+                        />
+                    </div>
+                </div>
+            </div>
+
+        </div>
+        <!-- 多设备 -->
+        <div
+            class="curtain-card-box"
+            v-if="curtainData.length > 1"
+        >
+            <div class="curtain-content">
+                <div class="curtain-title">
+                    <div>窗帘</div>
+                </div>
+                <div class="curtain-cart-img">
+                    <image
+                        class="imgSt"
+                        src="{{h5StaticPath}}curtain_vertical_3.svg"
+                    />
+                </div>
+            </div>
+            <!--  展示按钮     -->
+            <div style="padding-bottom: 42rpx; margin-left: 40rpx;">
+                <SwitchLR @commponent-SwitchLR-change="handleSpread"></SwitchLR>
+                <span v-show="onlyOne">{{""}}</span>
+            </div>
+            <!-- 总控 -->
+            <div
+                class="total-sw"
+                v-if="showTotal"
+            >
+                <div
+                    v-for="(btnItem,bi) in btnArr"
+                    :key="bi"
+                    class="mr40 switch"
+                    hover-class="style-hover"
+                    hover-style-time="300"
+                    :class="{'yell': btnItem.timeCount!=0 } "
+                    @click="handleTotal(btnItem.setType,btnItem.type,bi,btnArr,curtainData,btnItem.value)"
+                >
+                    <div class="arrow-size">
+                        <div
+                            class="line-center"
+                            v-if="btnItem.timeCount!=0 && btnItem.setType!='EquipStop'"
+                        >{{btnItem.timeCount }}</div>
+                        <image
+                            v-else
+                            src="{{h5StaticPath}}{{btnItem.icon}}"
+                        />
+                    </div>
+                </div>
+            </div>
+             <!-- 展开 子设备 -->
+            <div
+                v-if="!showTotal"
+                class="curtain-child"
+                v-for="(item,idx) in curtainData"
+                :key="item.id"
+            >
+                <div class="curtain-child-item">
+                    <div class="curtain-child-item-text">
+                        <div class="curtain-title" style="padding-top: 0rpx; margin-left:0rpx;">{{item.name}}</div>
+                        <div class="curtain-child-item-status" v-if="item.isOpenSet">开度{{item.openValue}}%</div>
+                    </div>
+                    <div class="cutain-switch">
+                        <div
+                            v-for="(btnItem,bi) in item.btnArr"
+                            :key="bi"
+                            class="mr40 switch"
+                            hover-class="style-hover"
+                            hover-style-time="300"
+                            :class="{'yell': btnItem.timeCount!=0}"
+                            @click="handleChild(btnItem.setType,item.equipId,idx,bi,item.btnArr,item)"
+                        >
+                            <div class="arrow-size">
+                                <div
+                                    class="line-center"
+                                    v-if="btnItem.timeCount!=0 && btnItem.setType!='EquipStop'"
+                                >{{btnItem.timeCount }}</div>
+                                <image
+                                    v-else
+                                    src="{{h5StaticPath}}{{btnItem.icon}}"
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+import { changeManualTempHttp, getObjectDataEqpGroupHttp } from '@/packagesEnv/api/officehome.js'
+let prev = Date.now();
+const initBtnArr = [
+    { id: 0, show: false, type: 'all', setType: 'openPop', value: '', icon: 'curtain_icon_adjust_3.svg', size: '40', timeCount: '', text: '' },
+    { id: 1, show: false, type: 'updown', setType: 'EquipOffSet', value: 0, icon: 'curtain_icon_down_3.svg', size: '32', timeCount: 0, text: '下降', timer: null },
+    { id: 2, show: false, type: 'leftRit', setType: 'EquipOffSet', value: 0, icon: 'curtain_icon_close_3.svg', size: '32', timeCount: 0, text: '关闭', timer: null },
+    { id: 3, show: false, type: 'all', setType: 'EquipStop', value: 2, icon: 'curtain_icon_stop_3.svg', size: '32', timeCount: 0, text: '暂停', timer: null },
+    { id: 4, show: false, type: 'updown', setType: 'EquipOnSet', value: 1, icon: 'curtain_icon_up_3.svg', size: '32', timeCount: 0, text: '上升', timer: null },
+    { id: 5, show: false, type: 'leftRit', setType: 'EquipOnSet', value: 1, icon: 'curtain_icon_open_3.svg', size: '32', timeCount: 0, text: '打开', timer: null }
+]
+let setTotalTimer = null;
+let timerThree = null;
+wepy.component({
+  props: {
+    projectId: String,
+    spaceId: String,
+    curtainVisible: Boolean
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    showTotal: true, // 默认总控
+    suspendDownFlag: false, // 总向下暂停
+    suspendUpFlag: false, // 总向上暂停
+    count: 2, // 倒计时
+    countText: '',       // 倒计时文案
+    btnArr: [],
+    initData: [], // 初始值
+    curtainData: []
+  },
+  watch: {
+    curtainVisible(val) {
+      if (!val && this.spaceId) {
+        this.getData();
+      }
+    },
+    spaceId() {
+      this.getData();
+    }
+  },
+  computed: {
+
+  },
+  methods: {
+    getData() {  //  设备组 - 窗帘
+      const params = {
+        criteria: {
+          projectId: this.projectId,
+          spaceId: this.spaceId,   // 空间ID
+          type: 0      // 0.窗帘
+        },
+        orders: [
+          {
+            column: 'sortNum',
+            asc: true
+          }
+        ]
+      }
+      getObjectDataEqpGroupHttp(params).then(res => {
+        let resData = res.count ? res.content : [];
+        this.btnArr = [];
+        this.$emit('comments-curtain-len', resData.length)
+        if (!resData.length) return;
+        let type = '';
+        let isOpenSet = 0;
+        let stopSet = 0;
+        resData.forEach((item, idx) => {
+                        // equipmentType:1//开合方式:1-平开门式;2-左拉单开;3-右拉单开;4-上开;5-下开;99-其他
+          if (item.equipmentType === 4 || item.equipmentType === 5 || item.equipmentType === 99) {
+            type = 'updown';
+          } else {
+            type = 'leftRit';
+          }
+          type = 'leftRit'
+          item.childCountText = '';
+          item.btnArr = [];
+          isOpenSet = item.isOpenSet;
+          stopSet = item.stopSet;
+          this.itemBtn(item.btnArr, type, item.isOpenSet, item.stopSet);
+        })
+        this.itemBtn(this.btnArr, type, isOpenSet, stopSet);
+        this.initData = JSON.parse(JSON.stringify(resData));
+        this.showCurtainNum();
+      })
+    },
+    itemBtn(arr, type, isOpenSet, stopSet) {
+      initBtnArr.map(item => {
+        if (item.type === type || (isOpenSet && item.type === 'all') || (stopSet && item.type === 'all')) {
+          arr.push(item)
+        }
+      })
+    },
+    showCurtainNum() {
+        // 一个或者多个设备
+      let arr = [];
+      if (this.initData.length === 1) {
+        arr.push(this.initData[0])
+      } else {
+        this.initData.map(item => {
+          arr.push(item)
+        })
+      }
+      this.curtainData = JSON.parse(JSON.stringify(arr));
+    },
+        // 参数
+    setParams(arr, setType) {
+      arr.forEach(item => {
+        item.equipmentCategory = 'CFSSES';
+        item.infoCode = setType;
+        item.value = 1;
+      });
+      return arr;
+    },
+        // 总开关
+    handleTotal(setType, type, idx, btnArr, data, value) {
+      wx.uma.trackEvent(`officeHome_curtain_card_${setType}`, { key: setType === 'EquipOffSet' ? '卡片-下降' : '卡片-上升' });
+
+      if (setType === 'openPop') {
+        this.$emit('commponents-curtain-showPop', {btnArr, data, curtainName: '窗帘'});
+        return;
+      }
+    //   let now = Date.now();
+    //   if (now - prev <= 2000) return; // 请求间隔 2秒
+    //   prev = Date.now();
+
+      this.setSwitchStatus(setType, idx);
+      this.sendData(value, 'EquipSwitchSet', data)
+    },
+
+    setSwitchStatus(setType, idx) {
+      clearInterval(setTotalTimer);
+      setTotalTimer = null
+      this.btnArr.map(item => {
+        if (item.setType !== setType && item.timeCount) {
+          item.timeCount = 0;
+        }
+      })
+      //  暂停按钮 点击显示一下下
+      if (setType === 'EquipStop') {
+        this.btnArr[idx].timeCount = 1;
+        clearInterval(timerThree);
+        timerThree = null
+        timerThree = setTimeout(() => {
+          this.btnArr[idx].timeCount = 0;
+          clearInterval(timerThree);
+          timerThree = null
+        }, 300);
+      } else {
+        this.btnArr[idx].timeCount = 3;
+        setTotalTimer = setInterval(() => {
+          this.btnArr[idx].timeCount--;
+          if (this.btnArr[idx].timeCount === 0) {
+            clearInterval(setTotalTimer);
+            setTotalTimer = null
+          }
+        }, 1000)
+      }
+    },
+
+        // 子 开关
+    handleChild(setType, id, index, btnIdx, btnArr, data) {
+      const curtainName = this.curtainData[index].name
+      if (setType === 'openPop') {
+        this.$emit('commponents-curtain-showPop', {btnArr, data, curtainName});
+        return;
+      }
+      let currentBtnArr = this.curtainData[index].btnArr;
+      let childItem = this.curtainData[index].btnArr[btnIdx];
+      //   一组按钮只有一个显示倒计时
+      currentBtnArr.map((item, idx) => {
+        if (item.setType !== childItem.setType && item.timeCount) {
+          item.timeCount = 0;
+          clearInterval(item.timer);
+          item.timer = null;
+        }
+      })
+       //  暂停按钮 点击显示一下下
+      if (setType === 'EquipStop') {
+        childItem.timeCount = 1;
+        clearInterval(timerThree);
+        timerThree = null
+        timerThree = setTimeout(() => {
+          childItem.timeCount = 0;
+          clearInterval(timerThree);
+          timerThree = null
+        }, 300);
+      } else {
+        childItem.timeCount = 3;
+        clearInterval(childItem.timer);
+        childItem.timer = null
+        childItem.timer = setInterval(() => {
+          childItem.timeCount--;
+          if (childItem.timeCount === 0) {
+            clearInterval(childItem.timer);
+            childItem.timer = null
+          }
+        }, 1000);
+      }
+
+      this.sendData(childItem.value, 'EquipSwitchSet', data)
+    },
+    sendData(value, sendType, data) {
+      const propData = JSON.parse(JSON.stringify(data));
+      let paramsArr = [];
+      if (Array.isArray(propData)) {
+        propData.map(item => {
+          const obj = {
+            id: item.equipId,
+            code: sendType,
+            value: value
+          }
+          paramsArr.push(obj)
+        })
+      } else {
+        const obj = {
+          id: propData.equipId,
+          code: sendType,
+          value: value
+        }
+        paramsArr.push(obj)
+      }
+      changeManualTempHttp(paramsArr).then()
+    },
+
+        // 总子设备切换
+    handleSpread(val) {
+      this.showTotal = val;
+    }
+  }
+})
+</script>
+<config>
+    {
+        usingComponents: {
+            "SwitchLR":   "../comComponents/SwitchLR",
+        }
+    
+    }
+</config>

+ 247 - 0
src/packagesEnv/pages/officehome/Curtain/curtainPop.wpy

@@ -0,0 +1,247 @@
+<style lang="less" scoped>
+.title {
+    padding-top: 40rpx;
+    font-family: PingFang SC;
+    font-size: 44rpx;
+    font-weight: 500;
+    line-height: 62rpx;
+    color: #000000;
+}
+.text {
+    font-family: PingFang SC;
+    font-size: 32rpx;
+    font-weight: 600;
+    line-height: 44rpx;
+    color: #1b2129;
+}
+.dir {
+    border-right: 1px solid #e1e5eb;
+    height: 30rpx;
+    margin-left: 24rpx;
+    margin-right: 24rpx;
+}
+.set-value {
+    display: flex;
+    align-items: center;
+    padding-top: 96rpx;
+    padding-bottom: 34rpx;
+}
+.value {
+    font-family: Montserrat;
+    font-size: 32rpx;
+    font-weight: 400;
+    line-height: 38rpx;
+    color: #1B2129;
+}
+.silder-box {
+    margin-bottom: 140rpx;
+}
+.mr40 {
+    margin-right: 24rpx;
+}
+.line-center {
+    text-align: center;
+    color: #000;
+    height: 40rpx;
+    line-height: 40rpx;
+}
+.switch {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 112rpx;
+    width: 112rpx;
+    background: #c4c4c433;
+    border-radius: 50%;
+    &.yell {
+        background: #ffe823;
+    }
+}
+.arrow-size {
+    height: 48rpx;
+    width: 48rpx;
+}
+image {
+    width: 100%;
+    height: 100%;
+}
+.show_text {
+    font-family: PingFang SC;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 20px;
+    text-align: center;
+    color: #8b949e;
+    padding-bottom: 60rpx;
+}
+</style>
+
+<template>
+    <div>
+        <div class="title">{{curtainName}}</div>
+        <div class="set-value">
+            <div class="text">设定开度</div>
+            <div class="dir"></div>
+            <div class="value">{{percent}}%</div>
+        </div>
+        <div class="silder-box">
+            <curtainSlider
+                :typeValue="percent"
+                @silder-mouseEnd="changeSwitch($event)"
+            ></curtainSlider>
+        </div>
+        <div style="display: flex;justify-content: space-around;">
+            <div
+                v-for="(item,idx) in btnList"
+                :key="item.id"
+            >
+                <div
+                    class="mr40 switch"
+                    :class="{'yell': item.timeCount!= 0}"
+                    hover-class="style-hover"
+                    hover-style-time="300"
+                    @click="handleChild(item.setType,item.id,idx,item.value)"
+                >
+                    <div class="arrow-size">
+                        <div
+                            class="line-center"
+                            v-if="item.timeCount != 0 && item.setType != 'EquipStop'"
+                        >{{item.timeCount }}</div>
+                        <image
+                            v-else
+                            src="{{h5StaticPath}}{{item.icon}}"
+                        />
+                    </div>
+
+                </div>
+                <div
+                    class="show_text"
+                    style="color: #626C78;margin-right:24rpx;padding-top: 20rpx;"
+                >{{item.text}}</div>
+            </div>
+
+        </div>
+
+    </div>
+
+</template>
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+import { changeManualTempHttp } from '@/packagesEnv/api/officehome.js'
+let timerThree = null;
+wepy.component({
+  props: {
+    typeobj: Object,
+    curtainVisible: Boolean
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    defaultInactiveColor: 'rgba(196, 196, 196, 0.2)',
+    percent: 0,
+    changeWatchVal: true,
+    btnList: [],
+    timer: null,
+    curtainName: '窗帘'
+
+  },
+  computed: {
+
+  },
+  watch: {
+    curtainVisible: {
+      handler(val) {
+                // 进入弹窗 设定值默认为0
+        if (!val) {
+          this.percent = 0
+        }
+      },
+      immediate: true
+    },
+    typeobj: {
+      handler(val) {
+        if (val.btnArr.length) {
+          this.curtainName = val.curtainName;
+          const arr = JSON.parse(JSON.stringify(val.btnArr));
+          val.btnArr.map((item, idx) => {
+            item.timeCount = 0;
+            if (item.setType === 'openPop') {
+              arr.splice(idx, 1)
+            }
+          })
+          this.btnList = [...arr];
+        }
+      },
+      deep: true
+    }
+  },
+  methods: {
+    handleChild(setType, id, idx, value) {
+            // 清掉其他正在倒计时的
+      this.btnList.map(item => {
+        if (item.timeCount !== 0 && item.id !== id) {
+          item.timeCount = 0;
+        }
+      })
+      //  暂停按钮 点击显示一下下
+      if (setType === 'EquipStop') {
+        this.btnList[idx].timeCount = 1
+        clearInterval(timerThree);
+        timerThree = null
+        timerThree = setTimeout(() => {
+          this.btnArr[idx].timeCount = 0;
+          clearInterval(timerThree);
+          timerThree = null
+        }, 300);
+      } else {
+        this.btnList[idx].timeCount = 3
+        clearInterval(this.timer);
+        this.timer = null;
+        this.timer = setInterval(() => {
+          this.btnList[idx].timeCount--
+          if (this.btnList[idx].timeCount === 0) {
+            clearInterval(this.timer);
+            this.timer = null;
+          }
+        }, 1000);
+      }
+
+      this.sendData(value, 'EquipSwitchSet')
+    },
+    sendData(value, sendType) {
+      const propData = this.typeobj.data;
+      let paramsArr = [];
+      if (Array.isArray(propData)) {
+        propData.map(item => {
+          const obj = {
+            id: item.equipId,
+            code: sendType,
+            value: value
+          }
+          paramsArr.push(obj)
+        })
+      } else {
+        const obj = {
+          id: propData.equipId,
+          code: sendType,
+          value: value
+        }
+        paramsArr.push(obj)
+      }
+      changeManualTempHttp(paramsArr).then()
+    },
+    changeSwitch(val) {
+      this.percent = val.value
+      this.sendData(this.percent, 'EquipOpeningSet')
+    }
+  }
+});
+</script>
+<config>
+    {
+        usingComponents: {
+            "curtainSlider": "./curtainSlider"    
+        }
+    
+    }
+</config>

+ 176 - 0
src/packagesEnv/pages/officehome/Curtain/curtainSlider.wpy

@@ -0,0 +1,176 @@
+<style lang="less" scoped>
+.box {
+    position: relative;
+}
+.line {
+    position: absolute;
+    width: 335px;
+    height: 32px;
+    background: linear-gradient(
+        90deg,
+        #ffc079 0%,
+        #ffe7d1 43.1%,
+        #f1efff 74.36%,
+        #c8d6ff 100%
+    );
+    border-radius: 16px;
+}
+.line_bg {
+    background: rgba(232, 236, 240, 1);
+}
+.active_line {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 335px;
+    height: 32px;
+    border-radius: 16px 0 0 16px;
+    background: #FFE823;
+}
+.btn {
+    box-sizing: border-box;
+    position: absolute;
+    width: 38px;
+    height: 38px;
+    left: 0px;
+    top: -3px;
+    border-radius: 50%;
+    background: #f6f9fe;
+    border: 2px solid #ffffff;
+    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.06), 0px 0px 8px rgba(0, 0, 0, 0.1);
+}
+.text_box {
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 30rpx;
+    align-items: center;
+
+}
+.text{
+    font-family: PingFang SC;
+    font-size: 36rpx;
+    font-weight: 400;
+    line-height: 50rpx;
+    color: rgba(27, 33, 41, 1); 
+}
+.text_num{
+    font-family: Montserrat;
+    font-size: 40rpx;
+    font-weight: 400;
+    line-height: 48rpx;
+    color: rgba(27, 33, 41, 1);
+}
+.posi_text{
+    position: absolute;
+    left: 8px;
+    top: 0px;
+    color: #1B2129;
+    font-family: PingFang SC;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 32px;
+}
+.posi_text_right{
+    position: absolute;
+    right: 8px;
+    top: 0px;
+    color: #1B2129;
+    font-family: PingFang SC;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 32px;
+}
+</style>
+<template>
+    <div style="padding-top: 18rpx; width: 335px;">
+
+        <div class="box">
+            <div
+                v-if="onlyLine"
+                class="line"
+                bindtap="touchmove"
+            ></div>
+            <div
+                v-if="!onlyLine"
+                :class="{'line':true,'line_bg':true}"
+                bindtap="touchmove"
+            ></div>
+            <div
+                v-if="!onlyLine"
+                class="active_line"
+                bindtap="touchmove"
+                style="width:{{progress+19}}px;"
+            ></div>
+            <span class="posi_text">关</span>
+            <span class="posi_text_right">开</span>
+            <div
+                class="btn"
+                style="left:{{buttonLeft}}px;"
+                bind="mousedown"
+                bindtouchstart="touchstart"
+                bindtouchmove="touchmove"
+                bindtouchend="touchend"
+            ></div>
+        </div>
+    </div>
+
+</template>
+<script>
+import wepy from '@wepy/core';
+// import config from '@/config';
+const max = 335 - 38
+wepy.component({
+  name: 'Slider',
+  props: {
+    typeName: String,
+    type: String,
+    typeValue: Number,
+    onlyLine: {
+      type: Boolean,
+      defaulf: true
+    }
+  },
+  data: {
+    progress: 0,
+    buttonLeft: 0,
+    precent: 0,
+    min: 0,
+    max,
+    startPoint: 0
+        // h5StaticPath: config.h5StaticPath + '/page-officehome/',
+  },
+  watch: {
+    typeValue(val) {
+      this.buttonLeft = parseInt(val / 100 * this.max);
+      this.progress = this.buttonLeft;
+      this.precent = val;
+    }
+  },
+  methods: {
+    touchstart(ev) {
+      this.startPoint = ev.touches[0];
+    },
+    touchmove(e) {
+      var endPoint = e.touches[e.touches.length - 1]
+      var translateX = endPoint.clientX - this.startPoint.clientX
+      this.startPoint = endPoint;
+      var buttonLeft = this.buttonLeft + translateX;
+      if (buttonLeft > this.max) {
+                // 滑动位置大于进度条最大宽度的时候让它为最大宽度
+        buttonLeft = this.max
+      }
+      if (buttonLeft < this.min) {
+                // 滑动位置小于进度条最大宽度的时候让它为最小宽度
+        buttonLeft = this.min
+      }
+      this.buttonLeft = buttonLeft;
+      this.progress = buttonLeft;
+      this.precent = parseInt((buttonLeft / this.max) * 100);
+    //   this.$emit('silder-percent-value', {'value': this.precent, 'type': this.type})
+    },
+    touchend() {
+      this.$emit('silder-mouseEnd', {'value': this.precent, 'type': this.type})
+    }
+  }
+})
+</script>

+ 349 - 0
src/packagesEnv/pages/officehome/FloorHeating/floorHeating.wpy

@@ -0,0 +1,349 @@
+<style lang="less" scoped>
+.box {
+    margin-bottom: 30rpx;
+    position: relative;
+    box-sizing: border-box;
+    width: 662rpx;
+    background: #ffffff;
+    box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.07),
+        0px 4px 10px rgba(0, 0, 0, 0.05);
+    border-radius: 50rpx;
+    padding-top: 48rpx;
+    .card_total {
+        position: relative;
+        padding-left: 40rpx;
+    }
+    .divider {
+        position: absolute;
+        left: 0px;
+        top: 312rpx;
+        width: 100%;
+        border-top: 1px solid #e8ecf0;
+    }
+    .card_slider {
+        display: flex;
+        align-items: center;
+        height: 164rpx;
+        margin-left: 20px;
+        margin-right: 20px;
+    }
+    .img {
+        position: absolute;
+        right: 0px;
+        top: 0px;
+        width: 330rpx;
+        height: 200rpx;
+        border-radius: 50rpx;
+    }
+    .titleSt {
+        font-family: PingFang SC;
+        font-size: 32rpx;
+        font-weight: 600;
+        line-height: 44rpx;
+
+        color: #4d5262;
+    }
+    .temSt {
+        display: flex;
+        align-items: flex-start;
+        padding-top: 8rpx;
+    }
+    .num {
+        font-family: Montserrat;
+        font-size: 64rpx;
+        font-weight: 400;
+        line-height: 78rpx;
+        color: #1b2129;
+    }
+    .unit {
+        font-family: Mulish;
+        font-size: 24rpx;
+        font-weight: 400;
+        line-height: 30rpx;
+        color: #c4c4c4;
+        margin-left: 8rpx;
+    }
+    .btn_box {
+        padding-top: 34rpx;
+        padding-bottom: 44rpx;
+        height: 56rpx;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .rl {
+        }
+        .rt {
+            margin-right: 46rpx;
+        }
+    }
+}
+.box_feat {
+    display: flex;
+    flex-wrap: wrap;
+    padding-bottom: 8rpx;
+    padding-top: 24rpx;
+}
+.eachFeat {
+    margin-bottom: 24rpx;
+    position: relative;
+    height: 232rpx;
+    width: 296rpx;
+    margin-left: 24rpx;
+    background: #f7f9fa;
+    border-radius: 32rpx;
+    padding-left: 32rpx;
+    box-sizing: border-box;
+    padding-top: 26rpx;
+    padding-bottom: 24rpx;
+    padding-right: 24rpx;
+    .temp {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        font-family: Montserrat;
+        font-size: 44rpx;
+        font-weight: 400;
+        line-height: 44rpx;
+        color: #1b144e;
+    }
+    .unit {
+        font-family: Mulish;
+        font-size: 24rpx;
+        font-weight: 400;
+        line-height: 30rpx;
+        color: #c4c4c4;
+    }
+    .status {
+        padding-top: 8rpx;
+        padding-bottom: 52rpx;
+        font-family: PingFang SC;
+        font-size: 24rpx;
+        font-weight: 400;
+        line-height: 34rpx;
+        color: #c4c9cf;
+    }
+    .eqName {
+        font-family: PingFang SC;
+        font-size: 32rpx;
+        font-weight: 600;
+        line-height: 44rpx;
+        color: #4d5262;
+    }
+    .imgSt {
+        position: absolute;
+        right: 28rpx;
+        bottom: 30rpx;
+        height: 40rpx;
+        width: 40rpx;
+    }
+}
+</style>
+<template>
+    <div class="box">
+        <image
+                :src="[totalStatus ?  (h5StaticPath+'floor_heat_open.png') : (h5StaticPath+'floor_heat_close.png')]"
+                class="img"
+            />
+ 
+        <div class="card_total">
+            <div class="titleSt">地暖</div>
+            <div class="temSt">
+                <div class="num">{{ tempSet ? tempSet : '--'  }}</div>
+                <div class="unit">℃</div>
+            </div>
+            <div class="btn_box">
+                <div class="rl">
+                    <SwitchLR
+                        v-if="len>1"
+                        @commponent-SwitchLR-change="changeSwitchLR"
+                    ></SwitchLR>
+                    <span v-show="onlyOne">{{""}}</span>
+                </div>
+                <div class="rt">
+                    <Switch :checked="totalStatus" @component-switch-change="changeSwitch($event,'total')"></Switch>
+                </div>
+            </div>
+        </div>
+        <div
+            class="divider"
+            v-show="totalStatus || !IsAll"
+        ></div>
+        <!-- 调节 -->
+        <div
+            class="card_slider"
+            v-if="totalStatus && IsAll"
+        >
+            <CardSlider
+                :cardWidth="cardWidth"
+                :maxValue="maxValue"
+                :minValue="minValue"
+                :onlyLine="false"
+                :typeValue="tempSet"
+                typeName="地暖"
+                type="heating"
+                @silder-mouseEnd="changeSwitch($event,'temp')"
+            ></CardSlider>
+        </div>
+        <!-- 子设备 -->
+        <div
+            class="box_feat"
+            v-show="!IsAll"
+        >
+            <div class="eachFeat"  @click="openFloorFeatPop(item)"
+            v-for="item in equipList"
+            :key="item.id"
+            >
+                <div class="temp">
+                    <div style="display: flex;">{{item.tempSet ? item.tempSet :'--'}}<span class="unit">℃</span></div>
+                    <Switch
+                        :checked="item.runStatus"
+                        :height="32"
+                        :width="64"
+                        @component-switch-change="changeSwitch($event,'child',item.id,item.switchCode)"
+                    ></Switch>
+                </div>
+                <div class="status">
+                    {{ item.runStatus ? '已开启':'已关闭' }}
+                </div>
+                <div class="eqName">
+                    {{ item.localName }}
+                </div>
+               
+                <img
+                    :style="{'opacity':(item.runStatus ? '1' : '0.3;')}"
+                    class="imgSt"
+                    src="{{h5StaticPath}}icon_adjust.svg"
+                />
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+import { querySapceFloorHeating, setSocketHttp } from '@/packagesEnv/api/officehome.js'
+wepy.component({
+  props: {
+    cardWidth: Number, // 卡片宽度
+    isControl: Boolean, // 是否可控
+    roomHasScene: String,
+    roomType: String,
+    controlMode: Number,
+    openEquipList: Array,
+    spaceId: String // 空间id
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    onlyOne: false, // 只有一个设备
+    IsAll: true, // 总控制
+    totalStatus: false,
+    tempSet: 0,
+    maxValue: 0,
+    minValue: 0,
+    equipList: [],
+    timer: null
+  },
+  computed: {
+    arrowImg() {
+      let imgString = '';
+      if (!this.arrowType) {
+        imgString = 'down_Arrow.svg'
+      } else {
+        imgString = 'up_Arrow.svg'
+      }
+      return imgString;
+    }
+  },
+  watch: {
+    spaceId() {
+      this.getData();
+      this.intervalCall();
+    }
+  },
+  detached() {
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+  methods: {
+    // 定时调用
+    intervalCall() {
+      clearInterval(this.timer);
+      this.timer = null;
+      this.timer = setInterval(() => {
+        this.getData();
+      }, 1000 * 3);
+    },
+    getData() {
+      querySapceFloorHeating({ spaceId: this.spaceId }).then(res => {
+        const resData = res.data;
+        this.equipList = resData.equipList || [];
+        this.len = this.equipList.length;
+        this.tempSet = resData.tempSet;
+        this.maxValue = resData.maxTempSet > 35 ? 35 : resData.maxTempSet;
+        this.minValue = resData.minTempSet < 16 ? 16 : resData.minTempSet;
+        this.totalStatus = resData.runStatus;
+        let obj = {
+          maxValue: this.maxValue,
+          minValue: this.minValue,
+          len: !!this.len
+        }
+        this.$emit('component-floorHeat-data', obj)
+      })
+    },
+    setEquip(paramArr) {
+      setSocketHttp(paramArr).then(res => {
+        this.intervalCall()
+      })
+    },
+    changeSwitchLR(val) {
+      this.IsAll = val;
+    },
+    // 下发指令
+    changeSwitch(val, type, id, code) {
+      let paramArr = [];
+      if (type === 'total') {
+        this.equipList.map(item => {
+          paramArr.push({
+            value: val ? 1 : 0,
+            code: item.switchCode,
+            id: item.id
+          })
+        })
+      }
+      if (type === 'child') {
+        paramArr.push({
+          value: val ? 1 : 0,
+          code: code,
+          id: id
+        })
+      }
+      if (type === 'temp') {
+        this.equipList.map(item => {
+          paramArr.push({
+            value: val,
+            code: item.tempSetCode,
+            id: item.id
+          })
+        })
+      }
+    //   console.log(paramArr, '----paramArr');
+      this.setEquip(paramArr)
+    },
+    openFloorFeatPop(item) {
+      if (item.runStatus) {
+        this.$emit('component-open-floorFeat-pop', item)
+      }
+    }
+  }
+})
+</script>
+<config>
+    {
+        usingComponents: {
+            "Switch":     "../comComponents/Switch",    
+            "SwitchLR":   "../comComponents/SwitchLR",
+            "CardSlider": "../comComponents/CardSlider"    
+        }
+    
+    }
+</config>

+ 111 - 0
src/packagesEnv/pages/officehome/FloorHeating/floorPop.wpy

@@ -0,0 +1,111 @@
+<style lang="less" scoped>
+.title{
+    padding-top: 40rpx;
+    font-family: PingFang SC;
+    font-size: 44rpx;
+    font-weight: 500;
+    line-height: 62rpx;
+    color: #000000;
+}
+.text{
+    font-family: PingFang SC;
+    font-size: 32rpx;
+    font-weight: 600;
+    line-height: 44rpx;
+    color: #1B2129;
+}
+.dir{
+    border-right: 1px solid #E1E5EB;
+    height: 30rpx;
+    margin-left: 24rpx;
+    margin-right: 24rpx;
+}
+.set-value{
+    display: flex;
+    align-items: center;
+    padding-top: 96rpx;
+    padding-bottom: 34rpx
+}
+.value{
+    font-family: Montserrat;
+    font-size: 32rpx;
+    font-weight: 400;
+    line-height: 38rpx;
+    color: #626C78;
+}
+.silder-box{
+    margin-bottom: 190rpx;
+}
+</style>
+
+<template>
+    <div>
+        <div class="title">地暖</div>
+        <div class="set-value">
+            <div class="text">设定温度</div>
+            <div class="dir"></div>
+            <div class="value">{{temp}}℃</div>
+        </div>
+        <div class="silder-box">
+            <Floorfeat-Slider
+            :typeValue="floorFeatItem.tempSet"
+            :maxValue="maxValue"
+            :minValue="minValue"
+            @silder-mouseEnd="changeSwitch($event)"
+            ></Floorfeat-Slider>
+        </div>
+    </div>
+   
+</template>
+<script>
+import wepy from '@wepy/core';
+import { setSocketHttp } from '@/packagesEnv/api/officehome.js'
+wepy.component({
+  props: {
+    maxValue: Number,
+    minValue: Number,
+    floorFeatItem: Object,
+    floorfeatVisible: Boolean
+  },
+  data: {
+    defaultInactiveColor: 'rgba(196, 196, 196, 0.2)',
+    temp: '--',
+    changeWatchVal: true
+
+  },
+  computed: {
+
+  },
+  watch: {
+    floorFeatItem(val) {
+      if (this.changeWatchVal) {
+        this.temp = val.tempSet || '--';
+      }
+    }
+  },
+  methods: {
+    changeSwitch(val) {
+      this.changeWatchVal = false;
+      this.temp = val;
+      let obj = {
+        id: this.floorFeatItem.id,
+        value: val,
+        code: this.floorFeatItem.tempSetCode
+      }
+      let paramArr = [];
+      paramArr.push(obj)
+      console.log(paramArr, '----child下发指令');
+      setSocketHttp(paramArr).then(res => {
+      })
+    }
+  }
+});
+</script>
+<config>
+    {
+        usingComponents: {
+            "Floorfeat-Slider": "../comComponents/FloorfeatSlider"    
+        }
+    
+    }
+</config>

+ 178 - 0
src/packagesEnv/pages/officehome/comComponents/CardSlider.wpy

@@ -0,0 +1,178 @@
+<style lang="less" scoped>
+.card_silder{
+    padding-top: 18rpx;
+    width: 582rpx;
+}
+.box {
+    position: relative;
+    height: 22px;
+}
+.line {
+    position: absolute;
+    width: 582rpx;
+    height: 22px;
+    background: linear-gradient(
+        270deg, #FFBAB6 0%, #FFE7D1 29.05%, #F1EFFF 62%, #C8D6FF 100%
+    );
+    border-radius: 16px;
+    // 最大最小值
+    font-family: Montserrat;
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 14px;
+    letter-spacing: 0px;
+    text-align: left;
+    color: #626C78;
+    .min_l{
+        position: absolute;
+        left: 8px;
+        top:4px;
+    }
+    .max_r{
+        position: absolute;
+        right: 8px;
+        top:4px;
+    }
+}
+.btn {
+    box-sizing: border-box;
+    position: absolute;
+    height: 32px;
+    width: 32px;
+    left: 0px;
+    top: -5px;
+    border-radius: 50%;
+    background: #f6f9fe;
+    border: 2px solid #ffffff;
+    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.06), 0px 0px 8px rgba(0, 0, 0, 0.1);
+    .value{
+        position: absolute;
+        top:-36rpx;
+        left: 0;
+        font-family: Montserrat;
+        font-size: 12px;
+        font-weight: 400;
+        line-height: 14px;
+        color: #626C78;
+    }
+}
+.text_box {
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 30rpx;
+    align-items: center;
+
+}
+.text{
+    font-family: PingFang SC;
+    font-size: 36rpx;
+    font-weight: 400;
+    line-height: 50rpx;
+    color: rgba(27, 33, 41, 1); 
+}
+.text_num{
+    font-family: Montserrat;
+    font-size: 40rpx;
+    font-weight: 400;
+    line-height: 48rpx;
+    color: rgba(27, 33, 41, 1);
+}
+</style>
+<template>
+    <div class="card_silder" style="{{ 'width:'+cardWidth+'px' }}">
+        <div class="box" id="cardSilder">
+            <div
+                class="line"
+                bindtap="touchmove"
+                style="{{ 'width:'+cardWidth+'px' }}"
+            >
+               <div class="min_l">{{ minValue }}</div>
+               <div class="max_r">{{maxValue}}</div>
+            </div>
+            <div
+                class="btn"
+                style="left:{{buttonLeft}}px;"
+                bind="mousedown"
+                bindtouchstart="touchstart"
+                bindtouchmove="touchmove"
+                bindtouchend="touchend"
+            >
+            <div class="value">
+            {{precent}}℃
+            </div>
+            </div>
+        </div>
+    </div>
+
+</template>
+<script>
+import wepy from '@wepy/core';
+// import config from '@/config';
+wepy.component({
+  name: 'CardSlider',
+  props: {
+    cardWidth: Number,
+    typeName: String,
+    type: String,
+    typeValue: Number,
+    minValue: Number,
+    maxValue: Number
+  },
+  data: {
+    progress: 0,
+    buttonLeft: 0,
+    precent: 0,
+    min: 0,
+    max: 0,
+    startPoint: 0
+  },
+  watch: {
+    cardWidth: {
+      handler(val) {
+        if (val) {
+          const btnWidth = 32;
+          this.max = this.cardWidth - btnWidth;
+        }
+      },
+      immediate: true
+    },
+    typeValue: {
+      handler(val) {
+        if (!val) val = this.minValue;
+        this.buttonLeft = parseInt((val - this.minValue) / (this.maxValue - this.minValue) * this.max);
+        this.progress = this.buttonLeft;
+        this.precent = val;
+      },
+      immediate: true
+    }
+
+  },
+
+  methods: {
+
+    touchstart(ev) {
+      this.startPoint = ev.touches[0];
+    },
+    touchmove(e) {
+      var endPoint = e.touches[e.touches.length - 1]
+      var translateX = endPoint.clientX - this.startPoint.clientX
+      this.startPoint = endPoint;
+      var buttonLeft = this.buttonLeft + translateX;
+      if (buttonLeft > this.max) {
+                // 滑动位置大于进度条最大宽度的时候让它为最大宽度
+        buttonLeft = this.max
+      }
+      if (buttonLeft < this.min) {
+                // 滑动位置小于进度条最大宽度的时候让它为最小宽度
+        buttonLeft = this.min
+      }
+      this.buttonLeft = buttonLeft;
+      this.progress = buttonLeft;
+      this.precent = parseInt(buttonLeft * (this.maxValue - this.minValue) / this.max + this.minValue);
+    },
+    touchend() {
+      this.$emit('silder-mouseEnd', this.precent)
+    }
+  }
+})
+</script>

+ 161 - 0
src/packagesEnv/pages/officehome/comComponents/FloorfeatSlider.wpy

@@ -0,0 +1,161 @@
+<style lang="less" scoped>
+.card_silder{
+    padding-top: 18rpx;
+    width: 335px;
+}
+.box {
+    position: relative;
+    height: 32px;
+}
+.line {
+    position: absolute;
+    width: 335px;
+    height: 32px;
+    background: linear-gradient(
+        270deg, #FFBAB6 0%, #FFE7D1 29.05%, #F1EFFF 62%, #C8D6FF 100%
+    );
+    border-radius: 16px;
+    // 最大最小值
+    font-family: Montserrat;
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 14px;
+    letter-spacing: 0px;
+    text-align: left;
+    color: #626C78;
+    .min_l{
+        position: absolute;
+        left: 8px;
+        top: 8px;
+    }
+    .max_r{
+        position: absolute;
+        left: 314px;
+        top: 8px;
+    }
+}
+.btn {
+    box-sizing: border-box;
+    position: absolute;
+    height: 38px;
+    width: 38px;
+    left: 0px;
+    top: -4px;
+    border-radius: 50%;
+    background: #f6f9fe;
+    border: 2px solid #ffffff;
+    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.06), 0px 0px 8px rgba(0, 0, 0, 0.1);
+    .value{
+        position: absolute;
+        top:-18px;
+        left: 0;
+        font-family: Montserrat;
+        font-size: 12px;
+        font-weight: 400;
+        line-height: 14px;
+        color: #626C78;
+    }
+}
+.text_box {
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 30rpx;
+    align-items: center;
+
+}
+.text{
+    font-family: PingFang SC;
+    font-size: 36rpx;
+    font-weight: 400;
+    line-height: 50rpx;
+    color: rgba(27, 33, 41, 1); 
+}
+.text_num{
+    font-family: Montserrat;
+    font-size: 40rpx;
+    font-weight: 400;
+    line-height: 48rpx;
+    color: rgba(27, 33, 41, 1);
+}
+</style>
+<template>
+    <div class="card_silder">
+        <div class="box">
+            <div
+                class="line"
+                bindtap="touchmove"
+            >
+               <div class="min_l">{{minValue }}</div>
+               <div class="max_r">{{maxValue }}</div>
+            </div>
+            <div
+                class="btn"
+                style="left:{{buttonLeft}}px;"
+                bind="mousedown"
+                bindtouchstart="touchstart"
+                bindtouchmove="touchmove"
+                bindtouchend="touchend"
+            >
+            </div>
+        </div>
+    </div>
+
+</template>
+<script>
+import wepy from '@wepy/core';
+// import config from '@/config';
+const max = 335 - 38
+wepy.component({
+  name: 'CardSlider',
+  props: {
+    typeName: String,
+    type: String,
+    typeValue: Number,
+    maxValue: Number,
+    minValue: Number
+  },
+  data: {
+    progress: 0,
+    buttonLeft: 0,
+    precent: 0,
+    min: 0,
+    max,
+    startPoint: 0
+  },
+  watch: {
+    typeValue(val) {
+      if (!val) {
+        val = this.minValue;
+      }
+      this.buttonLeft = parseInt((val - this.minValue) / (this.maxValue - this.minValue) * this.max);
+      this.progress = this.buttonLeft;
+      this.precent = val;
+    }
+  },
+  methods: {
+    touchstart(ev) {
+      this.startPoint = ev.touches[0];
+    },
+    touchmove(e) {
+      var endPoint = e.touches[e.touches.length - 1]
+      var translateX = endPoint.clientX - this.startPoint.clientX
+      this.startPoint = endPoint;
+      var buttonLeft = this.buttonLeft + translateX;
+      if (buttonLeft > this.max) {
+                // 滑动位置大于进度条最大宽度的时候让它为最大宽度
+        buttonLeft = this.max
+      }
+      if (buttonLeft < this.min) {
+                // 滑动位置小于进度条最大宽度的时候让它为最小宽度
+        buttonLeft = this.min
+      }
+      this.buttonLeft = buttonLeft;
+      this.progress = buttonLeft;
+      this.precent = parseInt(buttonLeft * (this.maxValue - this.minValue) / this.max + this.minValue);
+    },
+    touchend() {
+      this.$emit('silder-mouseEnd', this.precent)
+    }
+  }
+})
+</script>

+ 158 - 0
src/packagesEnv/pages/officehome/comComponents/Slider.wpy

@@ -0,0 +1,158 @@
+<style lang="less" scoped>
+.box {
+    position: relative;
+}
+.line {
+    position: absolute;
+    width: 335px;
+    height: 32px;
+    background: linear-gradient(
+        90deg,
+        #ffc079 0%,
+        #ffe7d1 43.1%,
+        #f1efff 74.36%,
+        #c8d6ff 100%
+    );
+    border-radius: 16px;
+}
+.line_bg {
+    background: rgba(232, 236, 240, 1);
+}
+.active_line {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 335px;
+    height: 32px;
+    border-radius: 16px 0 0 16px;
+    background: rgba(255, 227, 153, 1);
+}
+.btn {
+    box-sizing: border-box;
+    position: absolute;
+    width: 38px;
+    height: 38px;
+    left: 0px;
+    top: -3px;
+    border-radius: 50%;
+    background: #f6f9fe;
+    border: 2px solid #ffffff;
+    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.06), 0px 0px 8px rgba(0, 0, 0, 0.1);
+}
+.text_box {
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 30rpx;
+    align-items: center;
+
+}
+.text{
+    font-family: PingFang SC;
+    font-size: 36rpx;
+    font-weight: 400;
+    line-height: 50rpx;
+    color: rgba(27, 33, 41, 1); 
+}
+.text_num{
+    font-family: Montserrat;
+    font-size: 40rpx;
+    font-weight: 400;
+    line-height: 48rpx;
+    color: rgba(27, 33, 41, 1);
+}
+</style>
+<template>
+    <div style="padding-top: 18rpx; width: 335px;">
+        <div class="text_box">
+            <div class="text">{{typeName}}</div>
+            <div class="text_num">{{precent}}%</div>
+        </div>
+        <div class="box">
+            <div
+                v-if="onlyLine"
+                class="line"
+                bindtap="touchmove"
+            ></div>
+            <div
+                v-if="!onlyLine"
+                :class="{'line':true,'line_bg':true}"
+                bindtap="touchmove"
+            ></div>
+            <div
+                v-if="!onlyLine"
+                class="active_line"
+                bindtap="touchmove"
+                style="width:{{progress+19}}px;"
+            ></div>
+            <div
+                class="btn"
+                style="left:{{buttonLeft}}px;"
+                bind="mousedown"
+                bindtouchstart="touchstart"
+                bindtouchmove="touchmove"
+                bindtouchend="touchend"
+            ></div>
+        </div>
+    </div>
+
+</template>
+<script>
+import wepy from '@wepy/core';
+// import config from '@/config';
+const max = 335 - 38
+wepy.component({
+  name: 'Slider',
+  props: {
+    typeName: String,
+    type: String,
+    typeValue: Number,
+    onlyLine: {
+      type: Boolean,
+      defaulf: true
+    }
+  },
+  data: {
+    progress: 0,
+    buttonLeft: 0,
+    precent: 0,
+    min: 0,
+    max,
+    startPoint: 0
+        // h5StaticPath: config.h5StaticPath + '/page-officehome/',
+  },
+  watch: {
+    typeValue(val) {
+      this.buttonLeft = parseInt(val / 100 * this.max);
+      this.progress = this.buttonLeft;
+      this.precent = val;
+    }
+  },
+  methods: {
+    touchstart(ev) {
+      this.startPoint = ev.touches[0];
+      this.$emit('silder-mouseStart')
+    },
+    touchmove(e) {
+      var endPoint = e.touches[e.touches.length - 1]
+      var translateX = endPoint.clientX - this.startPoint.clientX
+      this.startPoint = endPoint;
+      var buttonLeft = this.buttonLeft + translateX;
+      if (buttonLeft > this.max) {
+                // 滑动位置大于进度条最大宽度的时候让它为最大宽度
+        buttonLeft = this.max
+      }
+      if (buttonLeft < this.min) {
+                // 滑动位置小于进度条最大宽度的时候让它为最小宽度
+        buttonLeft = this.min
+      }
+      this.buttonLeft = buttonLeft;
+      this.progress = buttonLeft;
+      this.precent = parseInt((buttonLeft / this.max) * 100);
+      this.$emit('silder-percent-value', {'value': this.precent, 'type': this.type})
+    },
+    touchend() {
+      this.$emit('silder-mouseEnd', {'value': this.precent, 'type': this.type})
+    }
+  }
+})
+</script>

+ 112 - 0
src/packagesEnv/pages/officehome/comComponents/Switch.wpy

@@ -0,0 +1,112 @@
+<style lang="less">
+.component-switch-main {
+    // width: 96rpx;
+    // height: 48rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.component-switch {
+    background: #ffe823;
+    border-radius: 12px;
+    display: inline-block;
+    box-sizing: border-box;
+    position: relative;
+    cursor: pointer;
+    transition: background-color 0.3s;
+    &.disabled {
+        cursor: not-allowed;
+        opacity: 0.5;
+    }
+}
+
+.component-switch .checked-circle {
+    position: absolute;
+    top: 50%;
+    left: -1px;
+    transform: translate(0, -50%);
+    transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
+    border-radius: 50%;
+    background: #ffffff;
+    border: 1px solid rgba(196, 196, 196, 1);
+    box-sizing: border-box;
+    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.03);
+
+    &.circle-true {
+        left: 1px;
+    }
+}
+
+.component-switch.checked .checked-circle {
+    transform: translate(100%, -50%);
+}
+</style>
+
+<template>
+    <div
+        class="component-switch-main"
+        v-on:click.stop="changChecked"
+    >
+        <div
+            class="component-switch"
+            style="{{'background-color:'+(checked? defaultActiveColor: defaultInactiveColor)+';'+'width:'+width+'rpx;height:'+ height+'rpx;'}}"
+            :class="{'checked': checked,'disabled':disable}"
+        >
+            <div
+                class="checked-circle"
+                :class="{'circle-true': checked}"
+                style="{{ 'width:'+height+'rpx;height:'+ height+'rpx;' }}"
+            ></div>
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  data: {
+    defaultInactiveColor: 'rgba(196, 196, 196, 0.2)',
+    defaultActiveColor: 'rgba(255, 232, 35, 1)',
+    disable: false
+  },
+  props: {
+    height: {
+      type: Number,
+      default: 48
+    }, // 按钮的宽高
+    width: {
+      type: Number,
+      default: 96
+    }, // 圆圈宽高
+    checked: Boolean,
+    eqTitle: String
+  },
+  computed: {
+    confirmText() {
+      const text = this.checked ? '关闭' : '开启';
+      return text
+    }
+  },
+  watch: {
+    checked() {
+      this.disable = false;
+    }
+  },
+  methods: {
+    changChecked() {
+      this.vibrateShort();
+      this.$emit('component-switch-change', !this.checked);
+    },
+    vibrateShort() {
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+    }
+
+  }
+});
+</script>
+

+ 96 - 0
src/packagesEnv/pages/officehome/comComponents/SwitchLR.wpy

@@ -0,0 +1,96 @@
+<style lang="less">
+ .box{
+    position: relative;
+    height: 56rpx;
+    width: 232rpx;
+    border-radius: 42rpx;
+    background: #F1F4F6;
+    cursor: pointer;
+    transition: background-color 0.3s;
+    .bg_box{
+        display: flex;
+        justify-content: space-around;
+        align-items: center;
+        height: 100%;
+        font-family: PingFang SC;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 20px;
+        color: #626C78;
+    
+    }
+    .btn{
+        width:116rpx;
+        position: absolute;
+        height: 56rpx;
+        top: 0px;
+        border-radius: 42rpx;
+        padding: 8rpx 16rpx;
+        border: 2rpx solid #E1E5EB;
+        border-radius: 42rpx;
+        background: #FFFFFF;
+        box-sizing: border-box;
+        font-family: PingFang SC;
+        font-size: 28rpx;
+        font-weight: 400;
+        line-height: 40rpx;
+        color: #424C59;
+        transition: all .3s;
+        &.rt{
+            right: 0px;
+        }
+    }
+ }
+</style>
+
+<template>
+    <div class="box" @click="handleChange">
+        <div class="btn" style="{{'left:'+left+';'   }}">
+            <div style="width: 84rpx;">
+            {{value?'总控制':'子设备'}}
+        </div></div>
+        <div class="bg_box">
+            <span>总控制</span>
+            <span>子设备</span>
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  data: {
+    left: '0px',
+    value: true
+  },
+  props: {
+
+  },
+  computed: {
+
+  },
+  watch: {
+
+  },
+  methods: {
+    vibrateShort() {
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+    },
+    handleChange() {
+      this.value = !this.value;
+      this.vibrateShort();
+      if (this.value) {
+        this.left = '0px'
+      } else {
+        this.left = '50%'
+      }
+      this.$emit('commponent-SwitchLR-change', this.value)
+    }
+  }
+});
+</script>

文件差異過大導致無法顯示
+ 1219 - 0
src/packagesEnv/pages/officehome/components/air-adjust-hand.wpy


+ 644 - 0
src/packagesEnv/pages/officehome/components/air-adjust.wpy

@@ -0,0 +1,644 @@
+<template>
+    <div class="air-adjust office-card g-fl">
+        <div class="air-content">
+            <div class="air-adjust-env">
+                <div>
+                    <span class="envname">空调</span>
+                    <div
+                        class="envnum"
+                        @tap.stop="showDetail('Tdb,RH')"
+                    >{{temperature ? temperature :'--'}}<span>℃</span></div>
+                </div>
+                <div
+                    class="air-status"
+                    v-if="hasAir"
+                >
+                    <span>{{airData.isOpen ? '空调已开启' : '空调已关闭'}}</span>
+                </div>
+                <div
+                    class="room-air-status"
+                    v-if="airData.isOpen"
+                >
+                    <image
+                        v-if="airData.icon==1 || airData.icon==2 || airData.icon==3"
+                        class="room-air-img"
+                        src="{{h5StaticPath + airRunImg}}"
+                    />
+                    <span class="air-common-text air-status-text">{{airData.spaceStatus}}</span>
+                </div>
+                <div
+                    v-else
+                    style="height:52rpx"
+                >
+
+                </div>
+            </div>
+            <div class="air-adjust-btn">
+                <image
+                    :src="h5StaticPath+airImg"
+                    alt=""
+                    lazyload
+                />
+            </div>
+            <div class="air-switch">
+                <switch
+                    v-if="hasAir && !isACATVA"
+                    :isACATFC="isACATFC"
+                    :seasonType="seasonType"
+                    :isHand="'auto'"
+                    :showWorkTime="showWorkTime"
+                    :checked="airData.isOpen"
+                    :eqTitle="eqTitle"
+                    @component-switch-change="airChange($event)"
+                ></switch>
+            </div>
+        </div>
+        <!-- 空调 调节 -->
+        <div v-if="airData.isOpen">
+            <div class="air-control-item">
+                <div class="air-control-item-box">
+                    <!-- <div
+                        class="envnum-common"
+                        :class="[airTemSt]"
+                        v-show="glistenFlag"
+                    >{{airData.designTemperature}}<span>℃</span></div> -->
+                    <span class="air-common-text change-text">温度调节</span>
+                </div>
+                <div class="air-common-switch">
+                    <div
+                        class="switch"
+                        :class="{'yell': suspendUpFlag} "
+                        @click="changeAir(4, 'temp','toCold')"
+                    >
+                        <image src="{{h5StaticPath}}arrow_down_grey.png" />
+                    </div>
+                    <div
+                        class="switch"
+                        :class="{'yell': suspendUpFlag} "
+                        @click=" changeAir(2, 'temp','toWarm')"
+                    >
+                        <image src="{{h5StaticPath}}arrow_up_grey.png" />
+                    </div>
+                </div>
+            </div>
+            <!--Pj1101050038  ACATVA没有风量调节 和开关按钮 -->
+            <div
+                class="air-control-item"
+                v-if="!isACATVA"
+            >
+                <div class="air-control-item-box">
+                    <!-- <div class="envnum-common air-envnum-comon">
+                        <span
+                            class="envnum-common-wind"
+                            v-for="(item,index) in windGearArr"
+                            :key="index"
+                            :class="{'windBlack': item == windGear}"
+                        >{{item}}</span>
+                    </div> -->
+                    <span class="air-common-text change-text">风量调节</span>
+                </div>
+                <div class="air-common-switch">
+                    <div
+                        class="switch"
+                        :class="{'yell': suspendUpFlag} "
+                        @click="changeAir(5,'wind','toWindLow')"
+                    >
+                        <image src="{{h5StaticPath}}wind_small.png" />
+                    </div>
+                    <div
+                        class="switch"
+                        :class="{'yell': suspendUpFlag} "
+                        @click="changeAir(6,'wind','toWindUp')"
+                    >
+                        <image src="{{h5StaticPath}}wind_big.png" />
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import { changeTempHttp, getFeedbackDocumentsHttp, getTempRangeHttp, getSeasonType } from '@/packagesEnv/api/officehome.js'
+import config from '@/config';
+import { isWithinLocation } from '@/service/location';
+import moment from 'moment';
+import store from '@/store';
+import { mapState } from '@wepy/x';
+import { checkSpaceControl } from '@/packagesEnv/pages/common.js'
+
+let infoTimer;
+let feedbackTimer;
+
+wepy.component({
+  props: {
+    isControl: Boolean, // 是否可控
+    roomType: String, // 房间类型
+        // hasBlowing: Boolean, // 有无变风量
+    roomHasScene: Boolean, // 加班场景
+    temperature: Number,
+    objectId: String,
+    hasAir: {
+      type: Boolean,
+      default: true
+    }, // 有无空调
+    projectId: String,
+    isACATVA: Boolean,
+    isACATFC: Boolean,
+    airVolumes: Array, // 风量信息
+    airVisible: Boolean
+  },
+  store,
+  data: {
+    showWorkTime: false, // 展示是否需要预约时间
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    visible: false,
+    eqTitle: '空调',
+    airImg: 'air_close.png',
+    airTemSt: 'colorBlack',
+    glistenFlag: true,
+    windGear: 0, // 风量档位
+    windGearArr: [1, 2, 3], // 风量3/5个档位
+    airData: {
+      avg: '--',                // 算法调节温度(目标值)
+      icon: 1,                // 固定框文案ID 7 为关机,其它都是开机
+      spaceStatus: '',    // 固定框文案展示
+      notice: '',                // 弹框文案展示
+      isOpen: false // 空调开关 true 是开
+    },
+    watchObjectIdEnd: false, // 监听空间ID
+    tempMax: '--',
+    tempMin: '--',
+    seasonType: ''
+  },
+  watch: {
+    airVisible(val) {
+      if (!val) {
+        console.log('---关闭定时器');
+        clearInterval(feedbackTimer);
+      }
+    },
+    hasAir(val) {
+      if (!val) {
+        clearInterval(infoTimer);
+        this.airData.isOpen = false;
+        this.airImg = 'air_close.png';
+      }
+    },
+    objectId(val) {
+            //   this.glistenAirTemHttp();
+
+      if (val) {
+        clearInterval(infoTimer); // 定时器先清理 再设定
+        infoTimer = setInterval(() => {
+          this.getInfo();
+        }, 1000);
+      } else {
+        this.airData.isOpen = false;
+        this.airImg = 'air_close.png';
+      }
+    }
+
+  },
+  computed: {
+    ...mapState({isNowTime: state => state.officehome.isNowTime}),
+
+    airRunImg() {
+      let imgStr = '';
+      switch (this.airData.icon) {
+        case 1:
+          imgStr = 'temp-keep.png';
+          break;
+        case 2:
+          imgStr = 'temp_cold.svg';
+          break;
+        case 3:
+          imgStr = 'temp_sun.png';
+          break;
+        default:
+          imgStr = '';
+          break;
+      }
+      return imgStr
+    }
+  },
+  ready() {
+    this.querySeasonType();
+  },
+  detached() {
+    this.airData.isOpen = false;
+    this.airData.icon = 1;
+
+    clearInterval(feedbackTimer);
+    clearInterval(infoTimer);
+  },
+  methods: {
+    querySeasonType() {
+      const params = {
+        projectId: this.projectId,
+        date: moment().format('YYYYMMDD') // 当天日期
+      }
+      if (!this.seasonType) {
+        getSeasonType(params).then(res => {
+          this.seasonType = res.data || '';
+        }).catch(() => {
+        })
+      }
+    },
+    showDetail(funcid) {
+      this.$emit('showDetail', { funcid: funcid });
+      wx.uma.trackEvent('officeHome_changeAir_temperature', { key: 'roomTemperature' });
+    },
+    async getInfo() {
+      await this.getAirInfo('info');
+            //   this.judgeAirLevel();
+            //   this.glistenAirTem();
+    },
+
+        // 空调信息airTemp
+    getAirInfo(type, btnType, feedbackId) {
+      const paramObj = {
+        projectId: this.projectId, // 项目id
+        objectId: this.objectId // 空间id
+      }
+      if (type === 'feedback') {
+        paramObj.id = feedbackId;
+      }
+      getFeedbackDocumentsHttp(paramObj).then(res => {
+        if (type !== 'feedback') {
+          res.isOpen = false;
+          this.airData = res;
+          this.airData.designTemperature = res.designTemperature ? res.designTemperature.toFixed(1) : '--'
+
+          if (this.airData.icon === 7) {
+            this.airData.isOpen = false;
+            this.airImg = 'air_close.png';
+                        // 如果是过渡季 并且 是ATVA 空调 开启时则显示 过渡季节运行中...
+          } else if (this.airData.icon != 6) {
+            this.airData.isOpen = true;
+            this.airImg = 'openair.png';
+
+            if (this.isACATVA && this.seasonType == 'Transition') {
+              this.airData.spaceStatus = '过渡季节运行中...';
+              this.airData.icon = 1;
+            }
+          }
+        }
+                //   弹窗
+        if (type === 'feedback' && res.notice && btnType != 'sw') {
+          clearInterval(feedbackTimer); // 拿到反馈文案清除定时器
+          this.$emit('airTemp', {notice: res.notice, remark: res.remark});
+        }
+      })
+    },
+
+        // 空调温度 闪烁
+    glistenAirTemHttp() {
+      getTempRangeHttp().then(res => {
+        this.tempMax = res.tempMax;
+        this.tempMin = res.tempMin;
+      })
+    },
+        // glistenAirTem() {
+        //   clearInterval(timer);
+        //   if (this.airData.avg >= this.tempMax || this.airData.avg <= this.tempMin) {
+        //     this.airData.avg >= this.tempMax ? (this.airTemSt = 'colorRed') : (this.airTemSt = 'colorBlue');
+        //     const _this = this;
+        //     timer = setInterval(() => {
+        //       _this.glistenFlag = !_this.glistenFlag;
+        //     }, 500);
+        //   } else {
+        //     this.glistenFlag = true;
+        //     this.airTemSt = 'colorBlack';
+        //   }
+        // },
+
+        // judgeAirLevel() {
+        //         // - 空调风量判断 -
+        //   let blowObj = this.airVolumes ? this.airVolumes[0] : {};
+        //   let airLevel = 0;
+        //   if (blowObj.data === undefined || blowObj.data === 0 || !this.airData.isOpen) {
+        //     this.airLevel = 0;
+        //     return;
+        //   }
+        //   this.windGearArr = [1, 2, 3];
+        //   switch (blowObj.code) {
+        //     case 'AirSpeedGear':
+        //       this.windGearArr = [1, 2, 3, 4, 5]; // AirSpeedGear是五个挡位
+        //       airLevel = blowObj.data;
+        //       break;
+        //     case 'FanGear':
+        //       airLevel = blowObj.data;
+        //       break;
+        //     case 'SupplyAirFlow':
+        //       let avg = (blowObj.maxAirFlow - blowObj.minAirFlow) / 3;
+        //       if (blowObj.data > blowObj.minAirFlow && blowObj.data <= blowObj.minAirFlow + avg) {
+        //         airLevel = 1;
+        //       } else if (blowObj.data > blowObj.minAirFlow + avg && blowObj.data <= blowObj.minAirFlow + avg * 2) {
+        //         airLevel = 2;
+        //       } else {
+        //         airLevel = 3;
+        //       }
+        //       break;
+
+        //     default:
+        //       break;
+        //   }
+        //   this.windGear = airLevel;
+        // },
+
+    vibrateLong() {
+      if (wx.canIUse('vibrateLong')) {
+        wx.vibrateLong();
+      } else {
+        wx.vibrateShort();
+      }
+    },
+    vibrateShort() {
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+    },
+
+    airChange(value) {
+      if (!checkSpaceControl(this.isControl)) return;
+      const itemId = value ? 12 : 10; // 10关闭 12开启
+      console.log('itemId', itemId, value);
+      let btnTypeDetail = '';
+      if (itemId == 12) {
+        btnTypeDetail = 'openAir'
+      } else {
+        btnTypeDetail = 'closeAir'
+      }
+      this.changeAir(itemId, 'sw', btnTypeDetail)
+    },
+    onClose() {
+      this.visible = false;
+    },
+    popShow() {
+      this.visible = true;
+    },
+
+    checkAirCanOpen(itemId) { // 判断空调是否能开启
+      if (!this.roomHasScene && !this.isNowTime) {
+        wx.showToast({
+          title: '大厦该时段无空调提供',
+          icon: 'info',
+          duration: 1000
+        })
+        return false;
+      }
+      if (itemId === 12) { // 12表示开空调
+        console.log(this.isNowTime, 'this.isNowTime');
+        if (this.roomHasScene && !this.isNowTime) {
+          this.$emit('component-open-workTimePop', true);
+          return false;
+        }
+      }
+      return true;
+    },
+    changeAir(itemId, btnType, btnTypeDetail) {
+      if (!checkSpaceControl(this.isControl)) return;
+            // 如果是过渡季 并且ACATVA 空调  则不能调节温度 不能开
+    //   console.log('btnType,this.isACATVA,this.seasonType', btnType, this.isACATVA, this.isACATFC, this.seasonType);
+      if (btnType == 'temp' && this.isACATVA && this.seasonType == 'Transition') {
+        setTimeout(() => {
+          wx.showToast({
+            title: '目前为过渡季节,大厦无冷热水供应,空调无法调节',
+            duration: 2600,
+            icon: 'none'
+          })
+        }, 0);
+        return;
+      }
+      isWithinLocation().then(() => {
+        if (this.roomType === '开放') {
+          this.showWorkTime = this.checkAirCanOpen(itemId);
+          if (!this.showWorkTime) return;
+        }
+        wx.uma.trackEvent(`officeHome_changeAir_${btnTypeDetail}`, { key: btnTypeDetail });
+        this.vibrateShort();
+        const paramObj = {
+          objectId: this.objectId, // 空间id
+          valueType: 1, // 固定为1
+          itemId: itemId  //   1,有点冷  2,冷死了 3,有点热 4,热死了
+        }
+                // 如果不是开关空调
+        if (btnType != 'sw') {
+          this.$emit('airShowVisible', {
+            'toWhere': btnTypeDetail,
+            'designTemperature': 0 }
+                    );
+        }
+        console.log('空调下发指令');
+                //  下达指令
+        changeTempHttp(paramObj).then(res => {
+          if (btnType != 'sw') {
+            this.$emit('airShowVisible', {
+              'toWhere': btnTypeDetail,
+              'designTemperature': res.designTemperature }
+                        );
+            if (res.notice) {
+              this.$emit('airTemp', {notice: res.notice, remark: res.remark});
+            } else {
+              feedbackTimer = setInterval(() => {
+                this.getAirInfo('feedback', btnType, res.id); // 调节反馈文案
+              }, 1000);
+            }
+          }
+        }).catch(() => {
+        })
+      }).catch(() => {
+      })
+    }
+  }
+
+})
+</script>
+
+<style lang="less" scoped>
+.air-common-text {
+    padding-top: 2rpx;
+    font-family: PingFang SC;
+    font-size: 22rpx;
+    font-weight: 400;
+    line-height: 30rpx;
+    color: rgba(196, 196, 196, 1);
+    &.air-status-text {
+        width: 60%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        color: #827f7f;
+    }
+
+    &.change-text {
+        font-size: 28rpx;
+        line-height: 40rpx;
+    }
+}
+.office-card {
+    box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.07),
+        0px 4px 10px rgba(0, 0, 0, 0.05);
+    border-radius: 50rpx;
+    background: #ffffff;
+    margin-bottom: 30rpx;
+}
+
+.air-adjust {
+    position: relative;
+    // height: 280rpx;
+    .air-content {
+        padding: 48rpx 30rpx 0rpx 60rpx;
+        box-sizing: border-box;
+    }
+
+    .air-switch {
+        position: absolute;
+        top: 190rpx;
+        right: 30rpx;
+    }
+
+    .air-adjust-env {
+        .envname {
+            font-family: PingFang SC;
+            color: rgba(77, 82, 98, 1);
+            font-size: 32rpx;
+            font-weight: 600;
+            line-height: 44rpx;
+        }
+        .envnum {
+            font-family: Montserrat;
+            font-size: 64rpx;
+            font-weight: 400;
+            line-height: 64rpx;
+            padding-top: 10rpx;
+            color: #1b144e;
+            label {
+                font-family: Mulish;
+                font-weight: 800;
+                font-size: 24rpx;
+                margin-left: 6rpx;
+                line-height: 30rpx;
+                vertical-align: top;
+                color: #c4c4c4;
+            }
+        }
+    }
+    .air-status {
+        padding-top: 20rpx;
+        font-family: PingFang SC;
+        font-size: 28rpx;
+        font-weight: 400;
+        line-height: 40rpx;
+        color: rgba(196, 196, 196, 1);
+    }
+
+    .room-air-status {
+        padding-top: 6rpx;
+        padding-bottom: 20rpx;
+        display: flex;
+        align-items: center;
+        .room-air-img {
+            margin-right: 7rpx;
+            width: 28rpx;
+            height: 28rpx;
+        }
+    }
+
+    .air-adjust-btn {
+        flex: 1;
+        image {
+            position: absolute;
+            top: 0;
+            right: 0;
+            width: 314rpx;
+            height: 220rpx;
+        }
+    }
+}
+.air-control-item {
+    padding: 20rpx 28rpx 16rpx 56rpx;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    border-top: 1px solid rgba(196, 196, 196, 0.4);
+    .air-control-item-box {
+        display: flex;
+        align-items: center;
+    }
+    .envnum-common {
+        font-family: Montserrat;
+        font-size: 40rpx;
+        font-weight: 500;
+        line-height: 48rpx;
+        color: rgba(77, 82, 98, 1);
+        &.air-envnum-comon {
+            display: flex;
+            align-items: center;
+        }
+        .envnum-common-wind {
+            font-family: Montserrat;
+            font-size: 24rpx;
+            font-weight: 500;
+            line-height: 30rpx;
+            margin-right: 10rpx;
+            color: rgba(196, 196, 196, 1);
+            &.windBlack {
+                font-size: 40rpx;
+                line-height: 48rpx;
+                color: rgba(77, 82, 98, 1);
+            }
+        }
+        &.colorRed {
+            color: red;
+        }
+        &.colorBlue {
+            color: rgba(94, 139, 207, 1);
+        }
+        &.colorBlack {
+            color: rgba(77, 82, 98, 1);
+        }
+        label {
+            font-size: 11px;
+            line-height: 13px;
+            color: rgba(196, 196, 196, 1);
+            vertical-align: top;
+        }
+    }
+    .air-common-switch {
+        display: flex;
+    }
+    .switch:first-child {
+        margin-right: 40rpx;
+    }
+    .switch {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 80rpx;
+        width: 80rpx;
+        background: #c4c4c433;
+        border-radius: 50%;
+        image {
+            width: 40rpx;
+            height: 40rpx;
+        }
+        &.yell {
+            background: #ffe823;
+        }
+    }
+}
+</style>
+
+<config>
+{
+    usingComponents: {
+        "switch": "./switch"    
+    }
+
+}
+</config>

+ 258 - 0
src/packagesEnv/pages/officehome/components/air-feedback.wpy

@@ -0,0 +1,258 @@
+<template>
+    <div class="scene-popup air-popup">
+        <div class="scene-popup-head">
+            <div
+                class="head-imgbg grey"
+                :class="{'blue':airSetText.toWhere=='toCold', 'red':airSetText.toWhere=='toWarm'}"
+            >
+                <div class="air-head-imgbox">
+                    <image
+                        src="{{h5StaticPath+airPopImg}}"
+                        lazyload
+                    />
+                </div>
+            </div>
+        </div>
+        <div class="scene-popup-body air-popup-body">
+            <div class="body-item air-body-item">
+                <div
+                    class="air-body-item-num"
+                >
+                    <div
+                        v-if="airSetText.notice"
+                        class="air-temp-num"
+                    >{{airSetText.notice}}</div>
+                    <div
+                        v-else
+                        class="air-temp-num-load"
+                    >
+                        <image
+                            style="padding-top: 4px; height: 20px;width: 20px;"
+                            src="{{h5StaticPath}}loading.svg"
+                        ></image>
+                    </div>
+                </div>
+                <p
+                    v-if="airSetText.remark"
+                    class="air-notice"
+                    v-for="(item,idx) in airSetText.remark"
+                    :key="idx"
+                > <span>{{item}}</span></p>
+            </div>
+        </div>
+        <div class="btns">
+            <span
+                @click="closePop"
+                class="btn air_btn"
+            >确定</span>
+
+        </div>
+
+    </div>
+</template>
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+wepy.component({
+  props: {
+    airSetText: Object,
+    officeData: Object
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    airPopImg: ''
+  },
+  watch: {
+    airSetText() {
+      let nowImg = '';
+      if (this.airSetText.toWhere === 'toCold') {
+        nowImg = 'dialog_cooling.svg'
+      } else if (this.airSetText.toWhere === 'toWarm') {
+        nowImg = 'dialog_warmUp.svg'
+      } else {
+        nowImg = 'icon_wind.svg';
+      }
+      this.airPopImg = nowImg
+    }
+  },
+  methods: {
+    closePop() {
+      this.$emit('component-open-sceneShowpop')
+    }
+  }
+})
+</script>
+<style lang="less" scoped>
+.scene-popup {
+    width: 520rpx;
+    height: 860rpx;
+    background: linear-gradient(0deg, #fff 720rpx, #f3f3f3 140rpx);
+    &.air-popup {
+        width: 560rpx;
+        height: 720rpx;
+        background: linear-gradient(
+            0deg,
+            #fff 580rpx,
+            rgba(243, 243, 243, 1) 140rpx
+        );
+    }
+    .scene-popup-head {
+        padding-top: 30rpx;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        font-size: 38rpx;
+        .head-imgbg {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            width: 140rpx;
+            height: 140rpx;
+            background: #f0da21;
+            border-radius: 50%;
+            margin-bottom: 30rpx;
+            &.grey {
+                background: rgba(196, 196, 196, 1);
+            }
+            &.blue {
+                background: rgba(94, 139, 207, 1);
+            }
+            &.red {
+                background: rgba(229, 87, 79, 1);
+            }
+            .air-head-imgbox {
+                width: 72rpx;
+                height: 72rpx;
+                image {
+                    width: 100%;
+                    height: 100%;
+                }
+            }
+            .head-imgbox {
+                width: 40rpx;
+                height: 40rpx;
+                image {
+                    width: 100%;
+                    height: 100%;
+                }
+            }
+        }
+    }
+    .scene-popup-body {
+        display: flex;
+        flex-direction: column;
+        margin-left: 72rpx;
+        margin-top: 50rpx;
+        margin-right: 20rpx;
+        &.air-popup-body {
+            flex-direction: row;
+            margin: 0;
+            justify-content: center;
+        }
+        .body-item {
+            padding-top: 60rpx;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 30rpx;
+            line-height: 42rpx;
+            color: #c4c4c4;
+            .text-status {
+                width: 16rpx;
+                height: 16rpx;
+                background: #f0da21;
+                border-radius: 100%;
+                display: inline-block;
+                margin-right: 16rpx;
+                &.grey {
+                    background: #f3f3f3;
+                }
+            }
+            &.air-body-item {
+                width: 366rpx;
+                .air-body-item-num {
+                    display: flex;
+                    justify-content: center;
+                }
+            }
+            .air-temp-num {
+                padding-bottom: 54rpx;
+                font-family: Montserrat;
+                font-size: 22px;
+                font-weight: 400;
+                line-height: 29px;
+                color: rgba(20, 0, 80, 1);
+                &.arrow-cooling {
+                    color: #5e8bcf;
+                    margin: 0 30rpx;
+                    margin-bottom: 70rpx;
+                }
+                &.arrow-warn {
+                    color: rgba(229, 87, 79, 1);
+                    margin: 0 30rpx;
+                    margin-bottom: 70rpx;
+                }
+            }
+            .air-temp-load {
+                font-size: 26px;
+                display: flex;
+                justify-content: center;
+            }
+            .air-notice {
+                text-align: center;
+                display: flex;
+                justify-content: center;
+                font-family: PingFang SC;
+                font-size: 15px;
+                font-weight: 400;
+                line-height: 21px;
+                color: rgba(77, 82, 98, 1);
+            }
+        }
+        .order-item {
+            font-style: normal;
+            font-weight: 500;
+            font-size: 32rpx;
+            line-height: 52rpx;
+            color: #c4c4c4;
+            .time-span {
+                color: #000000;
+                font-size: 38rpx;
+            }
+        }
+    }
+}
+.btns {
+    display: flex;
+    position: absolute;
+    bottom: 40rpx;
+    left: 50%;
+    transform: translateX(-50%);
+    .btn {
+        width: 240rpx;
+        height: 80rpx;
+        line-height: 80rpx;
+        text-align: center;
+        font-size: 28rpx;
+        font-weight: 500;
+        color: #c4c4c4;
+        &.air_btn {
+            height: 100rpx;
+            width: 176rpx;
+            border-radius: 60rpx;
+            padding: 34rpx, 60rpx;
+            border: 1px solid rgba(196, 196, 196, 1);
+            line-height: 100rpx;
+            font-family: PingFang SC;
+            font-size: 28rpx;
+            font-weight: 400;
+            color: rgba(77, 82, 98, 1);
+        }
+    }
+    .btn_com {
+        border: 1px solid #c4c4c4;
+        border-radius: 60rpx;
+    }
+}
+</style>

+ 174 - 0
src/packagesEnv/pages/officehome/components/all-close.wpy

@@ -0,0 +1,174 @@
+<style lang="less">
+.all-close {
+    height: 64px;
+    border-radius: 25px;
+    background: #0f0c24;
+    margin-bottom: 15px;
+    display: flex;
+    padding: 0 60rpx;
+    box-sizing: border-box;
+    align-items: center;
+
+    .cont {
+        font-size: 27rpx;
+        color: #fff;
+        // line-height: 64px;
+        // text-align: center;
+        flex: 1;
+    }
+    .check {
+        width: 38px;
+        height: 38px;
+        // display: flex;
+        // background: #ffe823;
+        // align-items: center;
+        // justify-content: center;
+        // font-size: 46rpx;
+        image {
+            width: 100%;
+            height: 100%;
+            border-radius: 38px;
+        }
+    }
+}
+</style>
+
+<template>
+    <div
+        class="all-close"
+        v-on:click="showPopup"
+    >
+        <div class="cont">办公室已经没人了,我是最后一个走的</div>
+        <div class="check">
+            <image src="{{h5StaticPath}}icon_done_btn.svg" />
+        </div>
+
+    </div>
+
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import { specialLightSetHttp, queryEquipStatistics, changeAllAirHttp } from '@/packagesEnv/api/officehome.js'
+import config from '@/config';
+wepy.component({
+    data: {
+        h5StaticPath: config.h5StaticPath + '/',
+        allSpaceList: [],
+        lightSpaceList: [],
+        airSpaceList: [],
+    },
+    props: {
+        floorInfo: Array,
+        projectId: String,
+    },
+    computed: {
+
+    },
+    watch: {
+
+    },
+    ready() {
+        this.getEquipStatistics();
+    },
+    methods: {
+        changeLight(status) {
+            const setType = status === '打开' ? true : false; // 10关闭 12开启
+            var paramsArr = [];
+            this.allSpaceList.forEach((citem) => {
+                (citem.equipList || []).forEach((item) => {
+                    if (item.equipType == 'light') {
+                        paramsArr.push({
+                            id: item.id,
+                            switch: setType,
+                        });
+                    }
+                });
+            });
+            specialLightSetHttp(paramsArr).then((res) => {
+
+            });
+        },
+        //开关全部空调 
+        changeAllAir(status) {
+            let paramsArr = [];
+            this.allSpaceList.forEach((sitem) => {
+                const itemId = status === '打开' ? 12 : 10; // 10关闭 12开启
+                paramsArr.push({
+                    projectId: this.projectId,
+                    objectId: sitem.id, // 空间id
+                    valueType: 1, // 固定为1 1
+                    itemId: itemId,
+                });
+            });
+            //console.log("paramsArr2", paramsArr);
+            changeAllAirHttp(paramsArr).then((res) => {
+                if (res.result == 'success') {
+                } else {
+                }
+            });
+        },
+        getEquipStatistics() {
+            var params1 = {
+                floorId: this.floorInfo[0] && this.floorInfo[0].floorId,
+                projectId: this.projectId
+            };
+            var params2 = {
+                floorId: this.floorInfo[1] && this.floorInfo[1].floorId,
+                projectId: this.projectId
+            };
+            Promise.all([queryEquipStatistics(params1), queryEquipStatistics(params2)]).then((res) => {
+
+                var allSpaceList = [];
+                (res || []).forEach((ritem) => {
+                    var spaceList = (ritem.data && ritem.data.spaceList) || [];
+                    allSpaceList.push(...spaceList)
+                });
+                //|| item.localName == '上格云休闲区'
+                var filterSpaceArr = allSpaceList.filter((item) => {
+                    return item.localName == '图书休闲区';
+                });
+                //没用
+                this.airSpaceList = allSpaceList.filter((item) => {
+                    var airStatus = (item.equipStatusList || []).filter((eitem) => {
+                        return eitem.equipType == 'airConditioner'
+                    })
+
+                    var status = airStatus[0] && airStatus[0].status;
+                    //if(status !== 0){ console.log('this.airSpaceList-name', item.localName,status);}
+                    return status !== 0;
+                });
+                //没用
+                this.lightSpaceList = allSpaceList.filter((item) => {
+                    var lightStatus = (item.equipStatusList || []).filter((eitem) => {
+                        return eitem.equipType == 'light'
+                    })
+
+                    var status = lightStatus[0] && lightStatus[0].status;
+                    // if(status !== 0){ console.log('this.lightSpaceList-name', item.localName,status);}
+                    return status !== 0;
+                });
+                //console.log('this.lightSpaceList', this.lightSpaceList);
+                this.allSpaceList = allSpaceList;
+
+            });
+        },
+        controlEquipStatistics() {
+            this.changeLight('关闭');
+            this.changeAllAir('关闭');
+        },
+        showPopup() {
+            this.$emit("showAllClose");
+
+        }
+    }
+});
+</script>
+
+<config>
+{
+    usingComponents: {
+        
+    }
+}
+</config>

+ 221 - 0
src/packagesEnv/pages/officehome/components/book-equipments.wpy

@@ -0,0 +1,221 @@
+<template>
+    <div class="book-equipments">
+        <div class="content">
+            <div class="line"></div>
+            <div class="text">预约开启设备</div>
+            <div class="line"></div>
+        </div>
+
+        <scroll-view
+            :scroll-y="true"
+            style="height: 240px;"
+        >
+        <div style="padding-bottom:100rpx">
+            <div v-for="(item,prIndx) in equipData" class="contain">
+                <div class="title">{{item.name}}</div>
+                <div class="box">
+                    <div
+                        :class="{'item-box':true,'item-box-fullwidth':item.eqlist.length===1 }"
+                        v-for="(child,idx) in item.eqlist"
+                        :key="idx"
+                    >
+                        <span :class="{'child-name':true, 'def':true}">{{child.localName}}</span>
+                        <div @click="change(child.status,idx,prIndx)">
+                            <image
+                                v-if="child.status"
+                                style="height: 18px;width: 18px;"
+                                :src="h5StaticPath+'book-act-btn.svg'"
+                            />
+                            <image
+                                v-if="!child.status"
+                                style="height: 18px;width: 18px;"
+                                :src="h5StaticPath+'book-dis-btn.svg'"
+                            />
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div
+                class="child-name"
+                style="color: #8d9399;padding-bottom:40rpx;padding-top:12rpx"
+            >
+                <div>计费方式:</div>
+                <div class="pdtop">*空调延时采用后付费方式,系统将根据您申请的时间产生延时费用。预约时间下您可自由开启空间下的设备。</div>
+                <div class="pdtop">
+                    收费标准按开机设备对应空间的面积计费,空调加时费用按0.4元/m²·h进行计费,照明设备延时使用不计费。
+                </div>
+                <div class="pdtop">
+                    如您提前结束工作,请点击“我要离开”按钮,系统会自动调整设备关闭时间,感谢与您一同共创可持续的办公环境。
+                </div>
+            </div>
+        </div>
+           
+        </scroll-view>
+
+    </div>
+</template>
+
+<script>
+import store from '@/store';
+import wepy from '@wepy/core';
+import config from '@/config';
+import { mapActions } from '@wepy/x';
+
+wepy.component({
+  props: {
+    spaceEquipList: [], // 空间id
+    projectId: String,
+    bookEquipList: String // 显示的设备类型
+
+  },
+  store,
+  data: {
+    equipData: [],
+    h5StaticPath: config.h5StaticPath + '/page-officehome/'
+  },
+  computed: {
+  },
+  watch: {
+    spaceEquipList(val) {
+      this.setequipData(val)
+    }
+  },
+  detached() {
+
+  },
+  methods: {
+    ...mapActions([
+      'setHandPopHight'
+    ]),
+    change(status, idx, prIndx) {
+      this.equipData[prIndx].eqlist[idx].status = !status
+    },
+    setequipData(val) {
+      this.equipData = [];
+      const eqArr = JSON.parse(JSON.stringify(val));
+      let lampObj = { eqlist: [] }
+      let AirObj = { eqlist: [] }
+      eqArr.length && eqArr.map(item => {
+        item.status = 0;
+        if (this.bookEquipList === item.id || this.bookEquipList === 'all') {
+          item.status = 1;
+        }
+
+        if ((this.bookEquipList === 'allLamp' && item.equipmentCategory === 'SELTLT') || (item.equipmentCategory !== 'SELTLT' && this.bookEquipList === 'air') || (this.bookEquipList === 'allsocket' && item.equipmentCategory === 'CFIDSC')) {
+          item.status = 1;
+        }
+        // 根据点击的设备类型 展示不同的设备列表
+        if (item.equipmentCategory === 'SELTLT') {
+          lampObj.name = '照明'
+          lampObj.eqlist.push(item)
+        }
+        if (item.equipmentCategory !== 'SELTLT') {
+          AirObj.name = '空调';
+          AirObj.eqlist.push(item)
+        }
+      })
+      //  根据设备情况 设置弹窗高度
+      if (AirObj.eqlist.length) {
+        this.equipData.push(AirObj);
+      }
+      if (lampObj.eqlist.length) {
+        this.equipData.push(lampObj)
+      }
+      if (lampObj.eqlist.length || AirObj.eqlist.length) {
+        this.setHandPopHight(75)
+      }
+      if (!this.equipData.length) {
+        this.setHandPopHight(50)
+      }
+    }
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.book-equipments {
+    font-family: PingFang SC;
+    .content {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        padding: 40rpx 42rpx;
+        .line {
+            height: 0;
+            border-top: 1px solid #eee;
+            flex: 1;
+        }
+        .text {
+            height: 34rpx;
+            width: 144rpx;
+            font-size: 24rpx;
+            font-weight: 400;
+            line-height: 34rpx;
+            color: #8d9399;
+            margin: 0rpx 20rpx;
+        }
+    }
+    .contain{
+        padding-top:24rpx; 
+    }
+    .contain:first-child{
+        padding-top:0rpx; 
+    }
+    .title {
+        font-family: 'PingFang SC';
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 18px;
+        color: #000000;
+    }
+  
+    .box {
+        display: flex;
+        justify-content: space-around;
+        flex-wrap: wrap;
+        padding-top:12rpx;
+        .item-box {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            width: 48%;
+            height: 76rpx;
+            border-radius: 8rpx;
+            padding: 20rpx 16rpx 20rpx 16rpx;
+            box-sizing: border-box;
+            background: #f7f9fa;
+            margin-right: 10rpx;
+            margin-bottom: 12rpx;
+        }
+        .item-box-fullwidth {
+            width: 100%;
+        }
+    }
+    .act-icon {
+        color: #00dc23;
+    }
+    .icon {
+        color: linear-gradient(0deg, #c4c4c4, #c4c4c4),
+            linear-gradient(0deg, #ffffff, #ffffff);
+    }
+    .child-name {
+        font-family: 'PingFang SC';
+        font-size: 24rpx;
+        font-weight: 400;
+        line-height: 36rpx;
+    }
+    .def {
+            color: #1b2129;
+    }
+    .pdtop{
+        padding-top: 12rpx;
+    }
+}
+</style>
+<config>
+    {
+      usingComponents: {
+      "van-icon": "module:@vant/weapp/dist/icon",
+      }
+    }
+</config>

+ 160 - 0
src/packagesEnv/pages/officehome/components/date-selector.wpy

@@ -0,0 +1,160 @@
+<style lang="less">
+.day-selecotr {
+  text-align: center;
+  margin-top: 32rpx;
+  width: 100%;
+  box-sizing: border-box;
+  overflow-x: hidden;
+  white-space: nowrap;
+}
+
+.day-selecotr .day-selecotr-item {
+  width: 96rpx;
+  margin-right: 4rpx;
+  text-align: center;
+  padding-top: 20rpx;
+  padding-bottom: 16rpx;
+  display: inline-block;
+  &.cross-month {
+    .weekday {
+      color: #140050;
+      font-weight: 600;
+    }
+  }
+}
+
+.day-selecotr .day-selecotr-item.selected {
+  background: #ffe823;
+  border-radius: 30rpx;
+  color: #140050;
+  font-weight: 600px;
+  .weekday {
+    color: #140050;
+    font-weight: 600;
+  }
+  .date {
+    font-weight: 500;
+    color: #140050;
+  }
+}
+
+.day-selecotr .day-selecotr-item .weekday {
+  font-size: 10px;
+  line-height: 14px;
+  color: #9fb7cd;
+  margin-bottom: 2rpx;
+}
+
+.day-selecotr .day-selecotr-item .date {
+  color: #4d5262;
+  font-family: Montserrat;
+  font-size: 22px;
+  line-height: 27px;
+}
+</style>
+
+<template>
+  <scroll-view scroll-x="true" class="day-selecotr">
+    <div
+      class="{{'day-selecotr-item ' + (selectDaySelectorsIndex==index?'selected': '') + (item.selectorIscrossMonth? ' cross-month': '')}}"
+      v-for="(item, index) in daySelectors"
+      :key="index"
+      v-on:click.stop="selectDaySelectors(index)"
+      data-index="{{index}}"
+    >
+      <div class="weekday">{{item.weekday}}</div>
+      <div class="date">{{item.date}}</div>
+    </div>
+  </scroll-view>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+import {
+  getNowDate,
+  getDate,
+  getWeekDay,
+  getMonthText
+} from '@/service/meetingTimes';
+// import {
+//     getNowDate,
+//    getDate
+//   } from '@/packagesEnv/pages/common.js'
+const selectorsNumber = 7; // 总共展示多个选择项;
+wepy.component({
+  data: {
+    daySelectors: [],
+    selectDaySelectorsIndex: 0
+  },
+  props: {
+    selectValue: {
+      type: String,
+      default: getNowDate()
+    }
+  },
+  created() {
+    this.daySelectors = getDaysSelctors();
+  },
+  ready() {
+    let { selectValue } = this;
+    if (selectValue) {
+      let selectDaySelectorsIndex = this.daySelectors.findIndex(
+        selecotr => selecotr.value == selectValue
+      );
+      this.selectDaySelectorsIndex = selectDaySelectorsIndex;
+    }
+  },
+  watch: {
+    selectValue: function(newSelectValue, oldSelectValue) {
+      if (newSelectValue !== oldSelectValue) {
+        let selectDaySelectorsIndex = this.daySelectors.findIndex(
+          selecotr => selecotr.value == newSelectValue
+        );
+        this.selectDaySelectorsIndex = selectDaySelectorsIndex;
+      }
+    }
+  },
+  methods: {
+    selectDaySelectors(index) {
+      this.selectDaySelectorsIndex = index;
+      this.emmitSelect(index);
+    },
+    emmitSelect(index) {
+      let selector = this.daySelectors[index];
+      this.$emit('component-date-selector-select', selector.value);
+    }
+  }
+});
+
+function getDaysSelctors() {
+  let now = new Date();
+  let nowDate = now.getDate();
+  let selectors = [
+    {
+      weekday: '今天',
+      date: nowDate,
+      value: getNowDate()
+    }
+  ];
+  let newTime = now.getTime();
+  for (let i = 0; i < selectorsNumber - 1; i++) {
+    newTime = newTime + 24 * 60 * 60 * 1000;
+    let date = new Date(newTime);
+    let weekday = getWeekDay(date);
+    let selectorIscrossMonth = false;
+    if (now.getMonth() !== date.getMonth() && date.getDate() == '1') {
+      weekday = getMonthText(date);
+      selectorIscrossMonth = true;
+    }
+
+    selectors.push({
+      weekday,
+      date: date.getDate(),
+      value: getDate(date),
+      selectorIscrossMonth
+    });
+  }
+  return selectors;
+}
+</script>

+ 66 - 0
src/packagesEnv/pages/officehome/components/has-person.wpy

@@ -0,0 +1,66 @@
+<style lang="less" scoped>
+.has-person {
+    margin-left: 24rpx;
+    height: 40rpx;
+    width: 110rpx;
+    border-radius: 20rpx;
+    background: #edf1f5;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .img-box {
+        height: 32rpx;
+        width: 32rpx;
+    }
+    .text-box {
+        margin-left: 4rpx;
+        font-family: Inter;
+        font-size: 24rpx;
+        font-weight: 400;
+        line-height: 32rpx;
+        color: #626c78;
+        .notHas {
+            color: #8b949e;
+        }
+    }
+}
+</style>
+
+<template>
+    <div class="has-person">
+        <image
+            lazyload
+            class="img-box"
+            :src="imgType"
+        />
+        <div
+            class="text-box"
+            :class="{'notHas': !isPassengerPass}"
+        >
+            <span>{{isPassengerPass?'有人':'无人'}} </span>
+        </div>
+    </div>
+</template>
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+
+wepy.component({
+  props: {
+    isPassengerPass: Boolean
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    imgType: ''
+  },
+  watch: {
+    isPassengerPass: {
+      handler(val) {
+        this.imgType = val ? `${this.h5StaticPath}has_person.svg` : `${this.h5StaticPath}not_has_person.svg`
+      },
+      immediate: true
+    }
+
+  }
+})
+</script>

+ 567 - 0
src/packagesEnv/pages/officehome/components/lamp-adjust.wpy

@@ -0,0 +1,567 @@
+<style lang="less" scoped>
+.office-card {
+    box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.07),
+        0px 4px 10px rgba(156, 125, 125, 0.05);
+    border-radius: 50rpx;
+    background: #ffffff;
+    margin-bottom: 30rpx;
+}
+.lamp-basic-name {
+    font-weight: 600;
+    font-size: 32rpx;
+    line-height: 44rpx;
+    color: rgba(77, 82, 98, 1);
+}
+.lamp-status {
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 40rpx;
+    color: rgba(196, 196, 196, 1);
+    &.lamp-status-padding {
+        padding-top: 20rpx;
+    }
+}
+.lamp-basic-one {
+    padding: 48rpx 40rpx 12rpx 40rpx;
+    font-family: PingFang SC;
+    .lamp-basic-num {
+        font-family: Montserrat;
+        font-size: 64rpx;
+        line-height: 78rpx;
+        color: #1b144e;
+        label {
+            font-weight: 800;
+            font-size: 24rpx;
+            margin-left: 8rpx;
+            line-height: 54rpx;
+            vertical-align: top;
+            color: #c4c4c4;
+        }
+    }
+}
+.lamp-adjust {
+    box-sizing: border-box;
+    position: relative;
+    .lamp-pic {
+        height: 140px;
+        width: 165px;
+        position: absolute;
+        top: 0;
+        left: 166px;
+        image {
+            width: 100%;
+            height: 100%;
+        }
+    }
+    .lamp-basic {
+        font-family: PingFang SC;
+        padding-top: 48rpx;
+        padding-bottom: 40rpx;
+        .lamp-basic-num {
+            font-family: Montserrat;
+            font-size: 64rpx;
+            line-height: 78rpx;
+            color: #1b144e;
+            label {
+                font-weight: 800;
+                font-size: 24rpx;
+                margin-left: 8rpx;
+                line-height: 54rpx;
+                vertical-align: top;
+                color: #c4c4c4;
+            }
+        }
+    }
+}
+.lamp-item {
+    display: flex;
+    flex-wrap: wrap;
+    padding-top: 40rpx;
+    padding-left: 24rpx;
+    padding-right: 24rpx;
+    .item-box {
+        position: relative;
+        height: 232rpx;
+        width: 296rpx;
+        border-radius: 32rpx;
+        margin-top: 24rpx;
+        background: #f7f9fa;
+    }
+    .item-box:nth-child(2n-1) {
+        margin-right: 22rpx;
+    }
+}
+.adjust-box {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 72rpx;
+    width: 72rpx;
+    border-radius: 50%;
+}
+.bg {
+    background: #f1f4f6;
+}
+.icon_adjust {
+    width: 40rpx;
+    height: 40rpx;
+}
+.dplCenter {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+.all-btn {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-left: 24rpx;
+    height: 64rpx;
+    width: 120rpx;
+    border-radius: 42rpx;
+    background: #f1f4f6;
+    .btn-text {
+        font-family: PingFang SC;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 40rpx;
+        color: #2e3742;
+    }
+}
+
+.small-img-card {
+    .small-lamp-img {
+        width: 80px;
+        height: 70px;
+    }
+}
+.small-sw-card {
+    position: absolute;
+    top: 0rpx;
+    right: 0rpx;
+}
+
+.lamp-spread {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding-top: 25rpx;
+    .lamp-basic-allow {
+        width: 50rpx;
+        height: 36rpx;
+    }
+}
+.cellhoverstyle {
+    background: #e8ecf0;
+}
+</style>
+<template>
+    <div class="lamp-adjust office-card">
+        <!-- 一个灯 -->
+        <div
+            class="lamp-pic"
+            v-if="lampList.length===1"
+        >
+            <image
+                :src="[lampSw ?  (h5StaticPath+'lamp_open_v2.png') : (h5StaticPath+'lamp_close_v2.png')]"
+                alt=""
+            />
+        </div>
+        <div
+            class="lamp-basic-one"
+            v-if="lampList.length===1"
+            @click="openColourPop(lampList[0])"
+        >
+            <span class="lamp-basic-name">光照</span>
+            <div class="lamp-status lamp-status-padding">{{lampSw? '照明已开启':'照明已关闭'}}</div>
+            <div
+                class="dplCenter"
+                style="margin-top: 20rpx;"
+            >
+                <div :class="{'adjust-box':true,'bg':(lampList[0].bright || lampList[0].colorTemperature)}">
+                    <div
+                        v-if="lampList[0].bright || lampList[0].colorTemperature"
+                        :style="{'opacity':(!lampList[0].switch?'0.3':'1')+';width:40rpx;height:40rpx'}"
+                    >
+                        <image
+                            class="icon_adjust"
+                            src="/static/icon_adjust.svg"
+                        />
+                    </div>
+                </div>
+                <div>
+                    <switch
+                        class="lamp-switch"
+                        :checked="lampList[0].switch"
+                        :showWorkTime="showWorkTime"
+                        :eqTitle="lampList[0].localName"
+                        @component-switch-change="eqChange($event,lampList[0].id)"
+                    ></switch>
+                </div>
+            </div>
+        </div>
+        <!-- 多个灯 -->
+        <div
+            class="lamp-basic"
+            style="{{ lampList.length>2 ? 'padding-bottom: 20rpx;' :'' }}"
+            v-if="lampList.length>1"
+        >
+            <div
+                class="dplCenter"
+                style="margin-left:40rpx;margin-right: 40rpx;"
+            >
+                <div>
+                    <span class="lamp-basic-name">光照</span>
+                    <div class="lamp-status lamp-status-padding">{{lampSw? '照明已开启':'照明已关闭'}}</div>
+                </div>
+                <div class="dplCenter">
+                    <div
+                        class="all-btn"
+                        hover-class="cellhoverstyle"
+                        @click="eqChange(true,'allLamp')"
+                    >
+                        <span class="btn-text">全开</span>
+                    </div>
+                    <div
+                        class="all-btn"
+                        hover-class="cellhoverstyle"
+                        @click="eqChange(false,'allLamp')"
+                    >
+                        <span class="btn-text">全关</span>
+                    </div>
+                </div>
+            </div>
+            <div class="lamp-item">
+                <div
+                    class="item-box"
+                    v-for="(item,index) in lampList"
+                    :data-index="item.id"
+                    :key="item.id"
+                    @click="openColourPop(item)"
+                >
+                    <div class="small-img-card">
+                        <image
+                            class="small-lamp-img"
+                            src="{{h5StaticPath}}{{item.lampImg}}"
+                            :key="item.id"
+                        />
+                    </div>
+                    <div class="small-sw-card">
+                        <switch-small
+                            class="lamp-switch"
+                            :checked="item.switch"
+                            :eqTitle="item.name"
+                            :showWorkTime="showWorkTime"
+                            @component-switch-change="eqChange($event,item.id,index)"
+                        ></switch-small>
+                    </div>
+
+                    <div
+                        class="dplCenter"
+                        style="padding-right:28rpx;padding-left:32rpx"
+                    >
+                        <div class="lamp-basic-name">
+                            <span>{{item.localName}}</span>
+                        </div>
+                        <div
+                            :style="{'opacity':(!item.switch?'0.3;':'1')}"
+                            v-if="item.bright || item.colorTemperature"
+                        >
+                            <image
+                                class="icon_adjust"
+                                src="{{h5StaticPath}}icon_adjust.svg"
+                                :key="item.id"
+                            />
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div
+                class="lamp-spread"
+                v-if="total>2"
+                @click="handleShow"
+            >
+                <image
+                    class="lamp-basic-allow"
+                    src="{{h5StaticPath}}{{allowSvg}}"
+                />
+                <span class="lamp-status">{{showLamps?'收起':'展开'}}更多</span>
+            </div>
+
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import { setallLampHttp, getLampHttp, getStatusHttp } from '@/packagesEnv/api/officehome.js'
+import { isWithinLocation } from '@/service/location';
+import { checkSpaceControl } from '@/packagesEnv/pages/common.js'
+import config from '@/config';
+import store from '@/store';
+import { mapState } from '@wepy/x';
+let fifteenTimer = null;
+wepy.component({
+  props: {
+    isControl: Boolean, // 是否可控
+    equipConfig: Array, // 设备配置情况
+    controlMode: Number, // 手动模式
+    roomType: String, // 房间类型
+    roomHasScene: Boolean, // 加班场景
+    spaceId: String, // 空间id
+    lampHide: Boolean
+  },
+  store,
+  data: {
+    showWorkTime: false,  // 展示预约时间弹框
+    lightsStatusTimer: null,
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    lampSw: null, // 主灯开关
+    showLamps: false,
+    allowSvg: 'down_Arrow.svg',
+    startX: 0,
+    sliderWidth: 0,
+    total: 0,
+    lampList: [],
+    initData: [] // 灯原始数据
+
+  },
+  computed: {
+    ...mapState({ isNowTime: state => state.officehome.isNowTime })
+
+  },
+  watch: {
+    spaceId() {
+      this.showLamps = false; // 恢复初始化值
+      this.allowSvg = 'down_Arrow.svg';
+
+      this.getLampList();
+      this.getTwoTimerLoop();
+    },
+    lampHide(nval) {
+      if (this.showLamps) {
+        this.handleShow()
+      }
+    }
+
+  },
+  detached() {
+    clearInterval(this.lightsStatusTimer);
+    this.lightsStatusTimer = null;
+    clearTimeout(fifteenTimer);
+    fifteenTimer = null;
+  },
+  methods: {
+    openColourPop(obj) {
+      if (!checkSpaceControl(this.isControl)) return;
+            //  灯开时 才能调节色温
+      if (obj.runStatus && (obj.bright || obj.colorTemperature)) {
+        this.$emit('component-open-colourPop', obj)
+      }
+    },
+    // 设置灯对应的图片
+    setLampItem(item) {
+      if (item.lightType === 1) {
+        if (item.switch) {
+          item.lampImg = 'lamp_small_open.png';
+        } else {
+          item.lampImg = 'lamp_small_close.png';
+        }
+      } else {
+        if (item.switch) {
+          item.lampImg = 'atmLamp_small_open.png';
+        } else {
+          item.lampImg = 'atmLamp_small_close.png';
+        }
+      }
+    },
+    // 大于三个灯时,展示的数量
+    showLampCount() {
+      let arr = [];
+      this.initData.map((item, idx) => {
+        this.setLampItem(item);
+        if (this.initData.length < 3 || !this.showLamps) {
+          if (idx < 2) {
+            arr.push(item);
+          }
+        } else {
+          arr = [...this.initData];
+        }
+      })
+      this.lampList = JSON.parse(JSON.stringify(arr))
+    },
+    getLampList() {
+      getLampHttp({ spaceId: this.spaceId }).then(res => {
+        const content = res.content || [];
+        if (!content.length) return;
+        let flag = false;
+        for (let index = 0; index < content.length; index++) {
+          let item = content[index];
+          item.switch = !!item.runStatus;
+          if (item.runStatus) {
+            flag = true;
+          }
+        }
+        this.lampSw = flag;
+        this.initData = JSON.parse(JSON.stringify(res.content));//  灯的数据
+        this.total = this.initData.length;
+        this.showLampCount()
+      })
+    },
+    vibrateShort() {
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+    },
+        // 手动模式下设备是否可控
+    checkControlMode(id) {
+      let flag = false;
+      if (!this.controlMode) {
+        return flag
+      }
+
+            //  总开关
+      if (id === 'allLamp') {
+        let canOpen = true;
+        for (let i = 0; i < this.equipConfig.length; i++) {
+          const ele = this.equipConfig[i];
+          for (let j = 0; j < this.lampList.length; j++) {
+            const jele = this.lampList[j];
+                        // 有可控设备
+            if (ele.equipmentId === jele.id && ele.isExeSpaceTime) {
+              canOpen = false;
+              return;
+            }
+          }
+        }
+        flag = canOpen;
+      } else {
+        this.equipConfig.map(item => {
+                    //  设备不受控制 可直接开灯
+          if (item.equipmentId === id && !item.isExeSpaceTime) {
+            flag = true;
+          }
+                    // 设备受控 且有当前工作时间 可直接开灯
+          if (item.equipmentId === id && this.isNowTime && item.isExeSpaceTime) {
+            flag = true;
+          }
+        })
+      }
+
+      return flag
+    },
+    checkLampCanOpen(value, id) { // 判断灯是否能开启
+      if (value) {
+                // 手动模式下设备是否可控
+        let canControl = this.checkControlMode(id);
+                // 有场景无时间 得预约
+        if (this.roomHasScene && !this.isNowTime && !canControl) {
+                    // 手动模式下
+          this.controlMode && this.$emit('component-EquipType-workTimePop', id);
+          this.$emit('component-open-workTimePop', true);
+          return false
+        }
+      }
+      return true
+    },
+    eqChange(value, id, index) {
+      if (!checkSpaceControl(this.isControl)) return;
+      isWithinLocation().then(() => {
+        if (this.roomType === '开放') {
+          this.showWorkTime = this.checkLampCanOpen(value, id);
+          if (!this.showWorkTime) return;
+        }
+        this.fifteenTimeout();
+        let params = [];
+        if (id === 'allLamp') {
+          wx.uma.trackEvent(`officeHome_lamp_card_Switch_${value}`, { key: value ? '卡片-开灯' : '卡片-关灯' });
+          params = this.initData;
+          params.forEach(item => {
+            item.switch = value
+          })
+         // 前端现更改灯的状态,15s后获取设备状态
+          this.lampList.map(item => {
+            item.switch = value;
+            this.setLampItem(item)
+          })
+          this.initData.map(item => {
+            item.switch = value;
+            this.setLampItem(item)
+          })
+          this.lampSw = value;
+        } else {
+          wx.uma.trackEvent(`officeHome_lamp_list_Switch_${value}`, { key: value ? '列表-开灯' : '列表-关灯' });
+          params = [{ id: id, switch: value }];
+          //  前端现更改灯的状态,15s后获取设备状态
+          if (this.lampList.length === 1) {
+            this.lampSw = value;
+          }
+          this.lampList.map(item => {
+            if (item.id === id) {
+              item.switch = value;
+              this.setLampItem(item)
+            }
+          })
+          this.initData.map(item => {
+            if (item.id === id) {
+              item.switch = value;
+              this.setLampItem(item)
+            }
+          })
+        }
+        this.fifteenTimeout();
+        setallLampHttp(params).then(res => {});
+        this.vibrateShort();
+      })
+    },
+    // 15s 之后再请求状态
+    fifteenTimeout() {
+      clearInterval(this.lightsStatusTimer);
+      this.lightsStatusTimer = null;
+      clearTimeout(fifteenTimer);
+      fifteenTimer = null;
+      fifteenTimer = setTimeout(() => {
+        this.getTwoTimerLoop();
+      }, 1000 * 15);
+    },
+    getTwoTimerLoop() {
+      clearInterval(this.lightsStatusTimer);
+      this.lightsStatusTimer = null;
+      this.lightsStatusTimer = setInterval(() => {
+        this.getLampList();
+      }, 2 * 1000); // 2秒描刷接口
+    },
+
+        // 灯列表
+    handleShow() {
+      if (!checkSpaceControl(this.isControl)) return;
+      if (!this.lampList.length) return;
+      this.showLamps = !this.showLamps
+      if (!this.showLamps) {
+        this.allowSvg = 'down_Arrow.svg'
+      } else {
+        this.allowSvg = 'up_Arrow.svg'
+      }
+      this.setLampList();
+      wx.uma.trackEvent('officeHome_lamp_show', { key: this.showLamps ? '展开' : '收起' });
+    },
+    setLampList() {
+      this.showLampCount()
+    }
+  }
+})
+</script>
+
+
+
+<config>
+{
+    usingComponents: {
+        "switch": "./switch",
+        "switch-small": "./switch-small",
+        "van-popup": "module:@vant/weapp/dist/popup"
+    }
+}
+</config>

+ 153 - 0
src/packagesEnv/pages/officehome/components/lamp-colour.wpy

@@ -0,0 +1,153 @@
+<style lang="less" scoped>
+.box {
+    padding-top: 40rpx;
+}
+.box_sw {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.text_box {
+    width: 335px;
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 30rpx;
+    align-items: center;
+}
+.title {
+    font-family: PingFang SC;
+    font-size: 44rpx;
+    font-weight: 500;
+    line-height: 62rpx;
+    color: rgba(0, 0, 0, 1);
+}
+</style>
+<template>
+    <div class="box">
+        <div class="box_sw">
+            <div class="text_box">
+                <div class="title">{{colourItem.localName}}</div>
+            </div>
+        </div>
+        <div v-if="colourObj.bright">
+            <Slider
+                :onlyLine="false"
+                :typeValue="colourObj.brightPercent"
+                typeName="亮度"
+                type="brightSet"
+                @silder-percent-value="silderVal"
+                @silder-mouseEnd="mouseEnd"
+                @silder-mouseStart="mouseStart"
+            ></Slider>
+        </div>
+        <div
+            style="padding-top:100rpx;"
+            v-if="colourObj.colorTemperature"
+        >
+            <Slider
+                :onlyLine="true"
+                :typeValue="colourObj.tempPercent"
+                typeName="色温"
+                type="colorTemperatureSet"
+                @silder-percent-value="silderVal"
+                @silder-mouseEnd="mouseEnd"
+                @silder-mouseStart="mouseStart"
+            ></Slider>
+        </div>
+    </div>
+
+</template>
+<script>
+import wepy from '@wepy/core';
+import { changeManualTempHttp } from '@/packagesEnv/api/officehome.js'
+wepy.component({
+  props: {
+    colourObj: Object
+  },
+  data: {
+    timerSetVal: null,
+    startTime: 0,
+    colourItem: {},
+    renew: true
+  },
+  watch: {
+    colourObj(val) {
+            // 开始滑动了 就不再更新父级数据
+      if (this.renew && val) {
+        if (val.brightValue === val.brightMinValue) {
+          val.brightValue = 0;
+        } else {
+          const brightPercent = val.brightValue / (val.brightMaxValue - val.brightMinValue) * 100;
+          val.brightPercent = Math.floor(brightPercent);
+        }
+        if (val.colorTempValue === val.colorTempMinValue) {
+          val.colorTempValue = 0;
+        } else {
+          const tempPercent = val.colorTempValue / (val.colorTempMaxValue - val.colorTempMinValue) * 100;
+          val.tempPercent = Math.floor(tempPercent);
+        }
+        this.colourItem = JSON.parse(JSON.stringify(val))
+      }
+    }
+  },
+  methods: {
+        // 下发色温指令
+    setColour(obj) {
+      let paramsObj = JSON.parse(JSON.stringify(obj));
+      paramsObj.id = this.colourItem.id;
+      paramsObj.code = obj.type;
+      delete paramsObj.type
+      const arr = [paramsObj]
+      changeManualTempHttp(arr)
+    },
+
+    countVal(obj) {
+      let dxValue = 0;
+      if (obj.type === 'brightSet') {
+        if (obj.value < this.colourItem.brightMinValue) {
+          obj.value = this.colourItem.brightMinValue
+          return obj;
+        }
+        dxValue = this.colourItem.brightMaxValue - this.colourItem.brightMinValue;
+      } else {
+        if (obj.value < this.colourItem.colorTempMinValue) {
+          obj.value = this.colourItem.brightMinValue
+          return obj;
+        }
+        dxValue = this.colourItem.colorTempMaxValue - this.colourItem.colorTempMinValue;
+      }
+      obj.value = Math.floor(obj.value / 100 * (dxValue));
+      return obj
+    },
+        // 滑动
+    silderVal(obj) {
+            // 值  obj.value  色温还是亮度 obj.type
+      obj = this.countVal(obj);
+      const endTime = Date.parse(new Date());
+      const dx = endTime - this.startTime;
+      if (dx === 1000) {
+        this.startTime = endTime;
+        this.setColour(obj);
+      }
+    },
+        // 滑动开始
+    mouseStart() {
+      this.startTime = Date.parse(new Date());
+      this.renew = false;
+    },
+        // 滑动停止
+    mouseEnd(obj) {
+      obj = this.countVal(obj);
+      this.setColour(obj);
+    }
+  }
+})
+</script>
+
+<config>
+{
+    usingComponents: {
+        "Slider": "../comComponents/Slider",
+    }
+}
+</config>

文件差異過大導致無法顯示
+ 1063 - 0
src/packagesEnv/pages/officehome/components/period-time-slider-hand.wpy


+ 895 - 0
src/packagesEnv/pages/officehome/components/period-time-slider.wpy

@@ -0,0 +1,895 @@
+<style lang="less">
+.componet-period-time-slider {
+    width: 100%;
+    height: 182rpx;
+    position: relative;
+    z-index: 2;
+    overflow-x: hidden;
+}
+
+.componet-period-time-slider .peroid-content-wrapper {
+    width: 658rpx;
+    position: absolute;
+    // left: 48rpx;
+    // right: 48rpx;
+    height: 182rpx;
+}
+
+.componet-period-time-slider .period-time-scroller {
+    position: absolute;
+    left: 32rpx;
+    right: 32rpx;
+    overflow: hidden;
+}
+
+.componet-period-time-slider .sliders-scroller-wrapper {
+    display: inline-block;
+    position: relative;
+    // border-right: 1px solid #c4c4c4;
+    height: 100%;
+}
+.componet-period-time-slider .sliders-list {
+    height: 100%;
+    width: 100%;
+}
+
+.componet-period-time-slider .sliders-wrapper {
+    width: 40px;
+    height: 100%;
+    display: inline-block;
+    vertical-align: bottom;
+    box-sizing: border-box;
+    position: relative;
+    z-index: 10;
+    &:last-child {
+        height: 154rpx;
+        border-right: 1px solid #c4c4c4;
+    }
+}
+
+.componet-period-time-slider .disabled-block {
+    position: absolute;
+    height: 120rpx;
+    // line-height: 120rpx;
+    left: 0;
+    right: 0;
+    background-repeat: repeat;
+}
+
+.componet-period-time-slider .sliders {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    position: relative;
+}
+
+.componet-period-time-slider .sliders .label {
+    width: 100%;
+    box-sizing: border-box;
+    height: 62rpx;
+    position: relative;
+    .split {
+        position: absolute;
+        height: 34rpx;
+        width: 1px;
+        left: 0;
+        bottom: 0;
+        background: #c4c4c4;
+    }
+    .text {
+        position: absolute;
+        left: 14rpx;
+        top: 0;
+        font-weight: 500;
+        font-size: 26rpx;
+        line-height: 32rpx;
+        color: #656872;
+        font-family: Montserrat;
+    }
+}
+
+.componet-period-time-slider .sliders .slider-block {
+    width: 100%;
+    height: 120rpx;
+
+    box-sizing: border-box;
+    background-repeat: repeat;
+    &.hasBorder {
+        border-left: 1px solid #ccc;
+    }
+    &.is-booked-selected-slider {
+        background-color: rgba(229, 87, 79, 0.6);
+    }
+    &.is-can-selected-slider {
+        background-color: rgba(0, 220, 35, 0.2);
+    }
+}
+
+.componet-period-time-slider .selected-area {
+    height: 120rpx;
+    position: absolute;
+    bottom: 0;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    z-index: 12;
+    &.showBg {
+        background: rgba(0, 220, 35, 0.2);
+    }
+}
+
+.componet-period-time-slider .selected-area.disabled {
+    // background-color: rgba(255, 77, 94, 0.26);
+}
+
+.componet-period-time-slider .booked-area {
+    height: 100%;
+    position: absolute;
+    bottom: 0;
+    font-size: 24rpx;
+    line-height: 112rpx;
+    text-align: center;
+    color: rgba(6, 6, 61, 0.3);
+    z-index: 11;
+    .bg {
+        position: absolute;
+        bottom: 0;
+        height: 120rpx;
+        width: 100%;
+        background-repeat: repeat;
+    }
+    &.selected {
+        z-index: 12;
+        .bg {
+            background-color: rgba(255, 232, 35, 0.2);
+        }
+    }
+    .booked-user-info {
+        padding: 18rpx;
+        background: #ffffff;
+        box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.08);
+        border-radius: 54rpx;
+        position: absolute;
+        bottom: 66rpx;
+        left: 50%;
+        transform: translateX(-50%);
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        z-index: 13;
+        text-align: left;
+        .avatar {
+            width: 72rpx;
+            height: 72rpx;
+            display: block;
+            border-radius: 50%;
+        }
+        .info {
+            width: 156rpx;
+            margin-left: 22rpx;
+            .name {
+                font-weight: 500;
+                font-size: 28rpx;
+                line-height: 40rpx;
+                color: #000000;
+                margin-top: 2rpx;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+                overflow: hidden;
+            }
+            .time {
+                color: #656872;
+                font-weight: 500;
+                font-size: 20rpx;
+                line-height: 24rpx;
+                font-family: Montserrat;
+            }
+        }
+    }
+}
+
+.componet-period-time-slider .selected-area .start,
+.componet-period-time-slider .selected-area .end {
+    position: absolute;
+    height: 100%;
+    width: 50rpx;
+    background: transparent;
+    /* display: flex;
+  flex-direction: row;
+  justify-content: center; */
+}
+
+.componet-period-time-slider .selected-area .start {
+    left: 0;
+    transform: translateX(-50%);
+}
+
+.componet-period-time-slider .selected-area .end {
+    right: 0;
+    transform: translateX(50%);
+}
+
+.componet-period-time-slider .selected-area .start-axis {
+    /* right:-20%; */
+    // margin-left: 20rpx;
+    left: 50%;
+    transform: translateX(-50%);
+}
+
+.componet-period-time-slider .selected-area .end-axis {
+    // margin-left: 30rpx;
+    right: 50%;
+    transform: translateX(50%);
+}
+
+.componet-period-time-slider .selected-area .axis {
+    height: 100%;
+    width: 3px;
+    background-color: #00dc23;
+    position: absolute;
+}
+
+.componet-period-time-slider .selected-area .axis.disabled {
+    background-color: #e5574f;
+    &::before {
+        border-color: #e5574f;
+    }
+}
+
+.componet-period-time-slider .selected-area .end-axis:before {
+    content: ' ';
+    position: absolute;
+    height: 24rpx;
+    width: 24rpx;
+    background-color: #fff;
+    border: 3px solid #00dc23;
+    box-sizing: border-box;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 13;
+    border-radius: 50%;
+}
+</style>
+
+<template>
+    <div
+        class="componet-period-time-slider"
+        v-on:touchend.stop="stopPragation"
+    >
+        <div class="peroid-content-wrapper">
+            <scroller-x
+                @componet-scroller-x-scroll="scroll"
+                containerId="peroid-content-wrapper"
+                canMove="{{!isSelectPeriodIng}}"
+                scrollLeft="{{scrollLeft}}"
+            >
+                <div class="sliders-scroller-wrapper">
+                    <div class="sliders-list">
+                        <div
+                            class="sliders-wrapper"
+                            v-for="(item,index) in sliders"
+                            data-index="{{index}}"
+                        >
+                            <div class="sliders">
+                                <div
+                                    class="label"
+                                    v-if="index%2==0"
+                                >
+                                    <div class="split"></div>
+                                    <div class="text">{{item.label}}</div>
+                                </div>
+                                <!-- slider-block class判断逻辑:如果选中了, 则需判断该块的状态来增加对应的背景色 -->
+                                <!-- slider-block style判断逻辑: 
+                一: 如果是过去的状态下: 1-需要展示过去状态需要红斜线(该块不是预定状态下(因为预定的斜线在booked-area里合并展示) 2- 是在选择区域,选择区域在过去状态需要红斜线)展示红斜线 
+                二: 如果展示未运营时间范围内且disabledUnopened 则需要展示 红斜线-->
+                                <div
+                                    class="{{'slider-block' + (item.isInSelectArea ? (item.isBooked || item.status=='unOpened' || item.status=='past')?' is-booked-selected-slider':' is-can-selected-slider' :'') + (index%2==0? ' hasBorder':'')}}"
+                                    v-on:click="selectPeriod"
+                                    data-index="{{index}}"
+                                    style="{{((item.status=='past'&& (showPastDisabled || item.isInSelectArea)) ||  (disabledUnopened && item.status=='unOpened'))? 'background-color:'+'#e3e3e3' : ''+';'+(item.isBooked?'background-image:url('+h5StaticPath+'/page-meeting-room/bg_disabled_red_v2.svg)':'')}}"
+                                ></div>
+                            </div>
+                        </div>
+                    </div>
+                    <div
+                        @click="deleteSelectedBookArea"
+                        class="{{'selected-area'+(canBookSelectArea?'': 'disabled') + (isSelectPeriodIng? ' showBg': '')}}"
+                        v-if="startIndex!==null"
+                        style="{{'left:' +selectArealeft + 'px;right:'+ selectAreaRight+ 'px;' }}"
+                    >
+                        <div
+                            class="start"
+                            data-from="start"
+                        >
+                            <div class="{{'start-axis axis'+ (!firstSelectSliderCanBook?' disabled': '')}}"></div>
+                        </div>
+                        <div
+                            class="end"
+                            v-on:touchmove.stop="changeEndTimeMove"
+                            v-on:touchstart.stop="changeEndTimeStart"
+                            v-on:touchend.stop="changeEndTimeEnd"
+                        >
+                            <div
+                                class="{{'end-axis axis'+ (!endSelectSliderCanBook ? 'disabled': '')}}"
+                                data-from="end"
+                            ></div>
+                        </div>
+                    </div>
+
+                    <!-- <div
+                        class="{{'booked-area' + (index === selectedBookAreaIndex? ' selected': '')}}"
+                        v-for="(item,index) in formattedBookList"
+                        :key="index"
+                        style="{{'left:' +item.left + 'px;right:'+ (item.right)+ 'px;'}}"
+                        bindlongtap="{{deleteSelectedBookArea(index)}}"
+                    >
+                        <div
+                            class="bg"
+                            style="{{'background-image:url('+h5StaticPath+'/page-meeting-room/bg_disabled_red_v2.svg)'}}"
+                        ></div>
+                    </div> -->
+                </div>
+            </scroller-x>
+        </div>
+    </div>
+</template>
+
+<script>
+import {
+    generateTimeValuesByArea,
+    SliderUnit
+} from '@/service/meetingTimes';
+import { generateTimeSliders,TimeStart,TimeEnd } from '@/packagesEnv/pages/common.js'
+let SliderWidth = 40; // ('单位为px');
+let startX = 0;
+let scrollerMarginLeft = 16; // ('单位为px');
+let axiosWidth = 56; // ('单位为px');
+let componentWrapInfo = {};
+let maxScrollLeft = 0;
+let addTimer = null;
+let reduceTimer = null;
+let overRightMoveX = 0;
+let reduceLeftMoveX = 0;
+let SlidersWrapperTotaLength = ((TimeEnd - TimeStart) / SliderUnit) * SliderWidth;
+let ShowAreaLength = 0;
+import wepy from '@wepy/core';
+import config from '@/config';
+wepy.component({
+    data: {
+        sliders: [],
+        bookedTimeValues: [], //这个用来判断当前slider是否是占用的
+        formattedBookList: [], //这个用来展示占用人名字居中的
+        startIndex: null,
+        duration: 0,
+        selectArealeft: 0,
+        selectAreaRight: '100%',
+        sliderFrom: '', //end,触发结束轴,start触发增加轴,
+        scrollLeft: 0,
+        isSelectPeriodIng: false, //用来标记是否在滑动选时间中
+        componentWrapWidth: 375,
+        h5StaticPath: config.h5StaticPath,
+        scrollLeftByTouch: 0, //用来记录自身滚动值,与设置的值区分开,防止多次设置组件内部滚动值会出现抖动
+        featuresVersion: config.featuresVersion,
+        selectedBookAreaIndex: null
+    },
+    props: {
+        selectValue: Object,
+        disabledPast: {
+            type: Boolean,
+            default: false
+        },
+        defaultSelectedPeriod: Object,
+        bookedList: Array,
+        hiddenBookedUserName: {
+            type: Boolean,
+            default: false
+        },
+        openStartTime: {
+            type: Number
+        },
+        openEndTime: {
+            type: Number
+        },
+        disabledUnopened: {
+            type: Boolean,
+            default: false
+        },
+        showPastDisabled: {
+            type: Boolean,
+            default: false
+        }
+    },
+    ready() {
+        this.getComponentWrapInfo().then(({ area }) => {
+            ShowAreaLength = area;
+            maxScrollLeft = ShowAreaLength - SlidersWrapperTotaLength;
+            const { selectValue } = this;
+            if (selectValue && selectValue.start && selectValue.end) {
+                // 防止此时选择时间列表还未全部渲染完毕
+                setTimeout(() => {
+                    this.setValeByStartAndValue(selectValue.start, selectValue.end);
+                }, 500);
+            }
+        });
+
+    },
+    computed: {
+        sliders() {
+            let sliders = generateTimeSliders(
+                this.openStartTime,
+                this.openEndTime,
+                this.disabledPast
+            );
+            // 找出已经booked, 并标记isBooked
+            if (this.formattedBookList && this.formattedBookList.length !== 0) {
+                sliders = sliders.map(slider => {
+                    let item = { ...slider };
+                    let sliderHasBooked = this.bookedTimeValues.find(
+                        time => slider.value == time
+                    );
+                    if (typeof sliderHasBooked === 'number') {
+                        item.isBooked = true;
+                    }
+                    return { ...item };
+                });
+            }
+            
+            // 找出在selectesArea里面的, 并标记isInSelectArea
+            if (this.startIndex !== null && this.duration) {
+                let start = sliders[this.startIndex].value;
+                let end = start + this.duration * SliderUnit;
+                sliders = sliders.map((slider, index) => {
+                    let isInSelectArea = false;
+                    if (slider.value >= start && slider.value < end) {
+                        isInSelectArea = true;
+                    }
+                    return { ...slider, isInSelectArea };
+                });
+            }
+            return sliders;
+        },
+        formattedBookList() {
+            let { formattedBookList } = this.formattedPeriodBookList(this.bookedList);
+            return formattedBookList;
+
+        },
+        bookedTimeValues() {
+            let {
+                formattedBookList,
+                bookedTimeValues
+            } = this.formattedPeriodBookList(this.bookedList);
+            return bookedTimeValues;
+
+        },
+        canBookSelectArea() {
+            let { canBooked } = this.selectAareaCanBook();
+            return canBooked;
+        },
+        firstSelectSliderCanBook() {
+            let { firstCanBook } = this.selectAareaCanBook();
+            return firstCanBook;
+        },
+        endSelectSliderCanBook() {
+            let { endCanBook } = this.selectAareaCanBook();
+            return endCanBook;
+        }
+    },
+    watch: {
+        selectValue: function (newSelectValue, oldSelectValue) {
+            if (JSON.stringify(oldSelectValue) !== JSON.stringify(newSelectValue)) {
+                if (newSelectValue.from !== 'outer') {
+                    return;
+                }
+                if (newSelectValue && newSelectValue.start && newSelectValue.end) {
+                    this.setValeByStartAndValue(newSelectValue.start, newSelectValue.end);
+                } else {
+                    this.reset();
+                }
+            }
+        },
+        bookedList: function (newBookedList, oldBooledList) {
+            if (JSON.stringify(oldBooledList) !== JSON.stringify(newBookedList)) {
+                // this.changedSlidersWithBookList();
+                const { selectValue } = this;
+                if (selectValue && selectValue.start && selectValue.end) {
+                    this.setValeByStartAndValue(selectValue.start, selectValue.end);
+                }
+            }
+        },
+        disabledPast: function (newDisabledPast, oldDisabledPast) {
+            const { selectValue } = this;
+            if (selectValue && selectValue.start && selectValue.end) {
+                this.setValeByStartAndValue(selectValue.start, selectValue.end);
+            }
+        }
+    },
+    methods: {
+        formattedPeriodBookList(bookedList) {
+            let formattedBookList = [],
+                bookedTimeValues = [];
+            if (!bookedList || (bookedList && bookedList.length == 0)) {
+                return { formattedBookList, bookedTimeValues };
+            }
+            formattedBookList = bookedList.map(booked => {
+                //todo
+                let { start, end } = booked;
+                let duration = (end - start) / SliderUnit;
+                let list = generateTimeValuesByArea(start, end);
+                bookedTimeValues = bookedTimeValues.concat(list);
+                let left = ((start - TimeStart) / SliderUnit) * SliderWidth; 
+
+                let right = SlidersWrapperTotaLength - left - duration * SliderWidth;
+                return { ...booked, duration, left, right };
+            });
+            return { formattedBookList, bookedTimeValues };
+        },
+        reset() {
+            this.startIndex = null;
+            this.duration = 0;
+            this.scrollLeft = 0;
+            this.selectArealeft = 0;
+            this.selectAreaRight = 0;
+        },
+        setValeByStartAndValue(start, end) {
+            let duration = (end - start) / SliderUnit;
+            let startIndex = this.sliders.findIndex(item => item.value == start);
+            let scrollLeft = -(startIndex * SliderWidth) + 50;
+            if (scrollLeft > 0) {
+                scrollLeft = 0;
+            } else if (scrollLeft < maxScrollLeft) {
+                scrollLeft = maxScrollLeft;
+            }
+            this.startIndex = startIndex;
+            this.duration = duration;
+            this.scrollLeftByTouch = scrollLeft; //同时也要记录;
+            setTimeout(() => {
+                this.scrollLeft = scrollLeft;
+            }, 100); // 防止初始是,时间轴列表还未渲染完,此时设置滚动不生效
+            const { canBookSelectArea } = this;
+            this.$emit(
+                'component-period-time-slider-judgecanselectedend',
+                canBookSelectArea
+            );
+            this.setSelectArea(startIndex, duration);
+        },
+        selectAareaCanBook() {
+            const { startIndex, duration, sliders, disabledPast } = this;
+
+            let canBooked = true;
+            let firstCanBook = true;
+            let endCanBook = true;
+            // 只有当有值才去判断
+            if (sliders && sliders.length && duration && !isNaN(startIndex)) {
+                let start = sliders[startIndex].value;
+                let selectedSliders = [];
+                for (let i = 0; i < duration; i++) {
+                    selectedSliders.push(start + i * SliderUnit);
+                }
+                selectedSliders.forEach((selectedItem, index) => {
+                    let selectSlider = sliders.find(item => item.value == selectedItem);
+                    if (
+                        (selectSlider && disabledPast && selectSlider.status == 'past') ||
+                        (selectSlider && selectSlider.status === 'unOpened')
+                    ) {
+                        canBooked = false;
+                        // 在不能预约的块里面是否有第一个,
+                        if (index == 0) {
+                            firstCanBook = false;
+                        }
+                        //再不能最后一个
+                        if (index == selectedSliders.length - 1) {
+                            endCanBook = false;
+                        }
+                    }
+                });
+            }
+
+            return { canBooked, firstCanBook, endCanBook };
+        },
+        scroll(scrollLeft) {
+            this.scrollLeftByTouch = scrollLeft;
+        },
+        selectPeriod(e) {
+            let index = e.$wx.target.dataset.index;
+            // if (this.index === index) {
+            //   this.cancelSelect();
+            // } else {
+            //   this.selectSlider(index)
+            // }
+            this.resetCanBook();
+            this.selectSlider(index);
+            this.onEndSelectFun();
+        },
+        resetCanBook() {
+            this.canBookSelectArea = true;
+        },
+        cancelSelect() {
+            this.startIndex = null;
+            this.duration = 0;
+            this.selectArealeft = 0;
+            this.selectAreaRight = 0; //默认保留一个宽度;
+        },
+        selectSlider(index) {
+            this.startIndex = index;
+            this.duration = 1;
+            this.setSelectArea(index, 1);
+            this.selectedBookAreaIndex = null;
+        },
+        setSelectArea(startIndex, duration) {
+            // console.log('设置偏移值',left, right);
+            let left = SliderWidth * startIndex;
+            let right = SlidersWrapperTotaLength - left - duration * SliderWidth;
+            this.selectArealeft = parseInt(left);
+            this.selectAreaRight = parseInt(right);
+        },
+        changeEndTimeStart(e) {
+            startX = Number(e.touches[0].pageX);
+            this.isSelectPeriodIng = true;
+        },
+        changeEndTimeMove(e) {
+            let {
+                scrollLeftByTouch,
+                startIndex,
+                duration,
+                selectArealeft,
+                selectAreaRight
+            } = this;
+            let touch = e.touches[0],
+                x = Number(touch.pageX);
+            let from = e.target.dataset.from;
+            if (x === startX) {
+                // 没有实质性的移动,不做处理,提高性能
+                return;
+            }
+            let touchLength = parseInt(x - startX);
+            startX = x;
+            //左边最起始x位置
+            let leftLimit = scrollerMarginLeft;
+            //因为要保证最少有一格,故滑动start轴最右边是当前结束位置向前移一格
+            let startLimit =
+                scrollerMarginLeft +
+                SliderWidth * (startIndex + duration - 1) +
+                scrollLeftByTouch;
+            //右边最大结束x位置
+            let rightLimit = scrollerMarginLeft + componentWrapInfo.width;
+            //因为要保证最少有一格,故滑动end轴结束位置为当前开始位置加一格
+            let endLimit =
+                scrollerMarginLeft + SliderWidth * (startIndex + 1) + scrollLeftByTouch;
+            //end 轴往前滑
+
+            if (touchLength >= 0 && x >= ShowAreaLength) {
+                this.clerReduceTimer();
+                // console.log('超出右边界,增加右边');
+                this.scrollToRightEdgBySelectArea(touchLength);
+                return;
+            }
+
+            // if (touchLength <= 0 && (selectArealeft + scrollLeftByTouch) < 0) {
+            //   this.clearAddTimer();
+            //   console.log('左边看不见,可以减少左边');
+            //   this.reduceSelectAreaRight();
+            //   return
+            // }
+
+            if (
+                touchLength <= 0 &&
+                SlidersWrapperTotaLength -
+                (selectAreaRight - touchLength) -
+                selectArealeft <=
+                SliderWidth
+            ) {
+                //保证一格,不让滑了
+                this.selectAreaRight =
+                    SlidersWrapperTotaLength - selectArealeft - SliderWidth;
+                return;
+            }
+
+            this.clearAddTimer();
+            this.clerReduceTimer();
+            // console.log('正常滑动增加或减少');
+            this.selectAreaRight = selectAreaRight - touchLength;
+        },
+        scrollToRightEdgBySelectArea() {
+            let { scrollLeftByTouch, selectAreaRight } = this;
+            if (addTimer) {
+                return;
+            }
+
+            addTimer = setInterval(() => {
+                // console.log('进入增加定时器');
+                overRightMoveX = overRightMoveX + 1;
+                let translateX = scrollLeftByTouch - overRightMoveX;
+                let newSelectAreaRight = selectAreaRight - overRightMoveX; //同时selectAreaRight也要变小
+                if (translateX <= maxScrollLeft) {
+                    //如果超出边界值,就不在滑动了
+                    this.clearAddTimer();
+                    this.scrollLeftByTouch = maxScrollLeft; //同时也要记录;
+                    this.scrollLeft = maxScrollLeft;
+                    this.selectAreaRight = 0;
+                    return;
+                }
+                this.scrollLeftByTouch = translateX; //同时也要记录;
+                this.scrollLeft = translateX;
+                this.selectAreaRight = newSelectAreaRight;
+            }, 10);
+        },
+
+        reduceSelectAreaRight() {
+            let { scrollLeftByTouch, selectArealeft, selectAreaRight } = this;
+            if (addTimer) {
+                this.clearAddTimer();
+            }
+            if (reduceTimer) {
+                return;
+            }
+
+            reduceTimer = setInterval(() => {
+                // console.log('进入减少定时器');
+                let toMinScrollLeft = false;
+                reduceLeftMoveX = reduceLeftMoveX + 1;
+                let translateX = scrollLeftByTouch + reduceLeftMoveX; //scroll增加,滚动减小
+                let right = selectAreaRight + reduceLeftMoveX; //right增加,选择区域减小
+
+                let minScrollLeft = -selectArealeft;
+                if (translateX >= minScrollLeft) {
+                    //当滚动到当前选项结束开始位置位于屏幕左测开始位置
+                    toMinScrollLeft = true;
+                }
+
+                this.scrollLeftByTouch = toMinScrollLeft ? minScrollLeft : translateX; //同时记录
+                this.scrollLeft = toMinScrollLeft ? minScrollLeft : translateX;
+                this.selectAreaRight = right;
+
+                if (SlidersWrapperTotaLength - right - selectArealeft <= SliderWidth) {
+                    //如果right 减小到与left 只剩一格,则不再减少,并且设置为1格的间距
+                    this.clerReduceTimer(); //就不再滑动了
+                    // console.log('保证一格了,不再减少了');
+                    this.scrollLeftByTouch = toMinScrollLeft ? minScrollLeft : translateX; //同时记录
+                    this.scrollLeft = toMinScrollLeft ? minScrollLeft : translateX;
+                    this.selectAreaRight =
+                        SlidersWrapperTotaLength - selectArealeft - SliderWidth;
+                }
+            }, 10);
+        },
+
+        clearAddTimer() {
+            if (addTimer) {
+                // console.log('清楚增加定时器');
+                overRightMoveX = 0;
+                addTimer = clearInterval(addTimer);
+            }
+        },
+        clerReduceTimer() {
+            if (reduceTimer) {
+                // console.log('清楚减少定时器');
+                reduceLeftMoveX = 0;
+                reduceTimer = clearInterval(reduceTimer);
+            }
+        },
+        moveLeftToScreenLeft() {
+            const { scrollLeftByTouch, selectArealeft } = this;
+            let distance = selectArealeft + scrollLeftByTouch;
+            if (distance <= 0) {
+                this.scrollLeft = scrollLeftByTouch - distance;
+            }
+        },
+
+        moveRihtToScreenRight() {
+            const { scrollLeftByTouch, selectAreaRight } = this;
+            let distance =
+                SlidersWrapperTotaLength -
+                selectAreaRight +
+                scrollLeftByTouch -
+                ShowAreaLength;
+            if (distance > 0) {
+                this.scrollLeft = scrollLeftByTouch - distance;
+            }
+        },
+        changeEndTimeEnd() {
+            this.clerReduceTimer();
+            this.clearAddTimer();
+            this.isSelectPeriodIng = false;
+            let { selectAreaRight, selectArealeft, startIndex } = this;
+            let numbers;
+            let remainder;
+            let selectAreaX =
+                SlidersWrapperTotaLength - selectAreaRight - selectArealeft;
+            // console.log('touchEnd', selectAreaX)
+            let moveNumbers = (Math.abs(selectAreaX) / SliderWidth)
+                .toString()
+                .split('.');
+            numbers = parseInt(moveNumbers[0]);
+            remainder = parseFloat('0.' + moveNumbers[1]);
+            if (Math.abs(remainder) >= 0.5) {
+                ++numbers;
+                //如果进一的时候,屏幕看不到了,则多滚一点
+                this.moveRihtToScreenRight();
+            }
+            // console.log('滑动结束', startIndex, duration);
+            this.duration = numbers;
+            this.startIndex = startIndex;
+            this.setSelectArea(startIndex, numbers);
+            startX = 0;
+            this.onEndSelectFun();
+            // 钉钉短震动(仅在 iPhone 7 / 7 Plus 以上及 Android 机型生效)
+            if (wx.canIUse('vibrateShort')) {
+                wx.vibrateShort();
+            }
+        },
+        getComponentWrapInfo() {
+
+            if (SlidersWrapperTotaLength && ShowAreaLength) {
+                return Promise.resolve({
+                    total: SlidersWrapperTotaLength,
+                    area: ShowAreaLength
+                });
+            }
+            return new Promise((resolve, reject) => {
+                setTimeout(() => {
+                    this.$wx
+                        .createSelectorQuery()
+                        .select('.peroid-content-wrapper')
+                        .boundingClientRect()
+                        .exec(ret => {
+                            if (!ret[0].width) {
+                                reject();
+                                return;
+                            }
+                            return resolve({
+                                area: ret[0].width
+                            });
+                        });
+                }, 300);
+
+            });
+        },
+        onEndSelectFun() {
+            const { startIndex, duration, sliders } = this;
+            let start = sliders[startIndex].value;
+            let end = start + duration * SliderUnit;
+            let hasUuBooked = this.hasUuBooked();
+            // console.log(start,'start!!!!!');
+            // console.log(end,'end!!!!!');
+            this.$emit('component-period-time-slider-select', { start, end,hasUuBooked });
+
+            this.$emit(
+                'component-period-time-slider-judgecanselectedend',
+                this.canBookSelectArea
+            );
+        },
+        stopPragation() { },
+        hasUuBooked(){ // 选区有新的增加
+            let hasUuBooked = false;
+            this.sliders.forEach((item,idx) => {
+                if (item.isInSelectArea && !item.isBooked) {
+                    hasUuBooked = true;
+                }
+            });
+            return hasUuBooked
+        },
+        deleteSelectedBookArea() { // 点击 删除
+            // console.log('1111');
+            let hasUuBooked = this.hasUuBooked();
+            if (hasUuBooked)return
+            // console.log('222');
+            this.$emit('component-bookList-delete')
+            // this.selectedBookAreaIndex = idx;
+        }
+    }
+});
+</script>
+
+<config>
+{
+  "usingComponents":{
+    "scroller-x": "~@/components/common/scroller-x"
+  },
+  componentPlaceholder: {
+    'scroller-x': 'view'
+  }
+}
+</config

+ 321 - 0
src/packagesEnv/pages/officehome/components/socket-card.wpy

@@ -0,0 +1,321 @@
+<template>
+    <div class="socket-adjust office-card">
+        <!-- 插座的基本信息 -->
+        <div class="socket-pic">
+            <image
+                :src="[socketSw ?  (h5StaticPath+'socket_open.png') : (h5StaticPath+'socket_close.png')]"
+                alt=""
+            />
+        </div>
+        <div class="socket-basic">
+            <span class="socket-basic-name">智能插座</span>
+            <div class="socket-status socket-status-padding">{{socketSw? '插座已开启':'插座已关闭'}}</div>
+            <div
+                class="socket-basic-text"
+                :class="{'justOne': !socketLength}"
+            >
+                <div v-if="socketLength">
+                    <SwitchLR @commponent-SwitchLR-change="changeSwitchLR"></SwitchLR> 
+                </div>
+              
+                <div v-if="!showsockets">
+                    <switch
+                        class="socket-switch"
+                        :checked="socketSw"
+                        eqTitle="所有插座"
+                        @component-switch-change="eqChange($event,'allsocket')"
+                    ></switch>
+                </div>
+            </div>
+        </div>
+        <!-- 插座列表 -->
+        <div
+            class="socket-list"
+            v-if="showsockets"
+        >
+            <div
+                class="socket-item"
+                v-for="(item,index) in socketList"
+                :data-index="item.id"
+                :key="index"
+            >
+                <div class="socket-basic-name">
+                    <span>{{item.localName}}</span>
+                </div>
+
+                <switch
+                    class="socket-switch"
+                    :checked="item.runStatus"
+                    :eqTitle="item.localName"
+                    :showWorkTime="showWorkTime"
+                    @component-switch-change="eqChange($event,item.id,item)"
+                ></switch>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import { setSocketHttp, getSocketHttp } from '@/packagesEnv/api/officehome.js'
+import { isWithinLocation } from '@/service/location';
+import config from '@/config';
+import store from '@/store';
+import { checkSpaceControl } from '@/packagesEnv/pages/common.js'
+import { mapState, mapActions } from '@wepy/x';
+let timer = null;
+wepy.component({
+  props: {
+    isControl: Boolean, // 是否可控
+    roomHasScene: String,
+    roomType: String,
+    controlMode: Number,
+    openEquipList: Array,
+    spaceId: String, // 空间id
+    projectId: String
+  },
+  store,
+  data: {
+    onlyOne: true,
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    socketSw: null, // 总开关
+    showsockets: false,
+    allowSvg: 'down_Arrow.svg',
+    socketList: [
+      {
+        localName: '饮水机插座'
+      },
+      {
+        localName: '加湿器插座'
+      },
+      {
+        localName: '微波炉插座'
+      }
+    ]
+  },
+  computed: {
+    ...mapState({ isNowTime: state => state.officehome.isNowTime }),
+    socketLength() {
+            // 灯的个数 大于 1 为true,
+      let length = false;
+      if (this.socketList.length > 1) {
+        length = true;
+      } else {
+        length = false;
+      }
+      return length
+    }
+  },
+  watch: {
+    spaceId() {
+      this.showsockets = false; // 恢复初始化值
+      this.allowSvg = 'down_Arrow.svg';
+      this.onlyOne = true; // 切换空间重新触发
+      this.handlerTimer();
+    },
+    openEquipList(newVal) {
+      if (newVal.length) {
+        let arr = []
+        newVal.map(item => {
+          if (item.type === 'CFIDSC') {
+            item.switch = true;
+            item.id = item.objectId
+            item.switchCode = 'EquipSwitchSet'
+            item.value = 1
+            arr.push(item)
+          }
+        })
+        if (arr.length) {
+          setSocketHttp(arr).then(res => {
+            this.handlerTimer();
+          })
+        }
+      }
+    }
+  },
+  detached() {
+    clearInterval(timer);
+    timer = null;
+  },
+  methods: {
+     // 判断窗帘是否能开启
+    checkSocketCanOpen(value, id) {
+      if (value) {
+        if (this.roomHasScene && !this.isNowTime) { // 有场景无时间 得预约
+          this.controlMode && this.$emit('component-EquipType-workTimePop', id);
+          this.$emit('component-open-workTimePop', true);
+
+          return false
+        }
+      }
+      return true
+    },
+        // 开关所有灯
+    allsocket() {
+      this.socketSw = !this.socketSw
+      this.socketList.forEach(item => {
+        item.value = this.socketSw ? 1 : 0;
+      });
+      return this.socketList;
+    },
+
+        // 控制插座
+    eqChange(value, id, item) {
+      if (!checkSpaceControl(this.isControl)) return;
+      isWithinLocation().then(() => {
+      /* 插座功能暂时没有加班功能  if (this.roomType === '开放') {
+          this.showWorkTime = this.checkSocketCanOpen(value, id);
+          if (!this.showWorkTime) return;
+        } */
+        let paramsArr = [];
+        if (id === 'allsocket') {
+          paramsArr = this.allsocket();
+        } else {
+          item.value = value ? 1 : 0;
+          paramsArr.push(item)
+        }
+        setSocketHttp(paramsArr).then(() => {
+          this.handlerTimer();
+        })
+        this.vibrateShort();
+      })
+    },
+    // 定时调
+    handlerTimer() {
+      this.getsocketList();
+      clearInterval(timer);
+      timer = null;
+      timer = setInterval(() => {
+        this.getsocketList();
+      }, 1000 * 8);
+    },
+    getsocketList() {
+      getSocketHttp({ spaceId: this.spaceId }).then(res => {
+        this.socketSw = res.data.runStatus;
+        this.socketList = res.data.equipList || []
+        this.onlyOne && this.$emit('component-showSocket', this.socketList.length);
+        this.onlyOne = false;
+        for (let index = 0; index < this.socketList.length; index++) {
+          const element = this.socketList[index];
+          element.runStatus = !!element.runStatus;
+          element.code = element.switchCode
+        }
+      })
+    },
+    vibrateShort() {
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+    },
+
+        // 灯列表
+    changeSwitchLR(val) {
+      if (!checkSpaceControl(this.isControl)) return;
+      if (!this.socketLength) return;
+      this.showsockets = !val;
+    }
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.office-card {
+    box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.07),
+        0px 4px 10px rgba(156, 125, 125, 0.05);
+    border-radius: 50rpx;
+    background: #ffffff;
+    margin-bottom: 30rpx;
+}
+.socket-basic-name {
+    font-weight: 600;
+    font-size: 32rpx;
+    line-height: 44rpx;
+    color: rgba(77, 82, 98, 1);
+}
+.socket-status {
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 40rpx;
+    color: rgba(196, 196, 196, 1);
+    &.socket-status-padding {
+        padding-top: 20rpx;
+    }
+}
+.socket-basic {
+    padding: 48rpx 30rpx 0px 60rpx;
+}
+.socket-adjust {
+    box-sizing: border-box;
+    position: relative;
+    .socket-pic {
+        height: 100px;
+        width: 165px;
+        position: absolute;
+        top: 0px;
+        right: 0px;
+        image {
+            width: 100%;
+            height: 100%;
+        }
+    }
+    .socket-basic {
+        font-family: PingFang SC;
+        padding-bottom: 32rpx;
+        .socket-basic-num {
+            font-family: Montserrat;
+            font-size: 64rpx;
+            line-height: 78rpx;
+            color: #1b144e;
+            label {
+                font-weight: 800;
+                font-size: 24rpx;
+                margin-left: 8rpx;
+                line-height: 54rpx;
+                vertical-align: top;
+                color: #c4c4c4;
+            }
+        }
+        .socket-basic-text {
+            margin-top: 50rpx;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            &.justOne {
+                justify-content: flex-end;
+            }
+            .socket-spread {
+                display: flex;
+                align-items: center;
+            }
+            .socket-basic-allow {
+                width: 50rpx;
+                height: 36rpx;
+            }
+        }
+    }
+}
+.socket-list {
+    .socket-item {
+        height: 108rpx;
+        display: flex;
+        justify-content: space-between;
+        padding-left: 60rpx;
+        padding-right: 30rpx;
+        align-items: center;
+        border-top: 1px solid rgba(196, 196, 196, 0.4);
+        box-sizing: border-box;
+    }
+}
+</style>
+
+<config>
+{
+    usingComponents: {
+        "switch": "./switch",
+        "van-popup": "module:@vant/weapp/dist/popup",
+        "SwitchLR":"../comComponents/SwitchLR.wpy"
+    }
+}
+</config>

+ 139 - 0
src/packagesEnv/pages/officehome/components/switch-small.wpy

@@ -0,0 +1,139 @@
+<style lang="less">
+.component-switch-main {
+    width: 114rpx;
+    height: 96rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.component-switch {
+    height: 32rpx;
+    width: 64rpx;
+    background: #ffe823;
+    border-radius: 24rpx;
+    display: inline-block;
+    box-sizing: border-box;
+    position: relative;
+    cursor: pointer;
+    transition: background-color 0.3s;
+    &.disabled {
+        cursor: not-allowed;
+        opacity: 0.5;
+    }
+}
+
+.component-switch .checked-circle {
+    position: absolute;
+    top: 49.8%;
+    left: -1px;
+    width: 32rpx;
+    height: 32rpx;
+    transform: translate(0, -50%);
+    transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
+    border-radius: 50%;
+    background: #ffffff;
+    border: 1px solid rgba(196, 196, 196, 1);
+    box-sizing: border-box;
+    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.03);
+
+    &.circle-true {
+        left: 1px;
+    }
+}
+
+.component-switch.checked .checked-circle {
+    transform: translate(100%, -50%);
+}
+</style>
+
+<template>
+    <div
+        class="component-switch-main"
+        v-on:click.stop="changChecked"
+    >
+        <!-- <div
+      class="component-switch"
+      :class="{'checked': checked}"
+      style="{{'background-color:'+(checked? activeColor||defaultActiveColor: inactiveColor||defaultInactiveColor)+';' }}"
+    > -->
+        <div
+            class="component-switch"
+            style="{{'background-color:'+(checked? activeColor||defaultActiveColor: inactiveColor||defaultInactiveColor)+';' }}"
+            :class="{'checked': checked,'disabled':disable}"
+        >
+            <div
+                class="checked-circle"
+                :class="{'circle-true': checked}"
+            ></div>
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  data: {
+    defaultInactiveColor: 'rgba(196, 196, 196, 0.2)',
+    defaultActiveColor: 'rgba(255, 232, 35, 1)',
+    disable: false
+  },
+  props: {
+    isHand: String,
+    isACATFC: Boolean,
+    checked: Boolean,
+    activeColor: String,
+    inactiveColor: String,
+    eqTitle: String,
+    seasonType: String,
+    showWorkTime: Boolean // 展示预约时间弹框
+  },
+  computed: {
+    confirmText() {
+      const text = this.checked ? '关闭' : '开启';
+      return text
+    }
+  },
+  watch: {
+    checked: {
+      handler() {
+        this.disable = false;
+      },
+      immediate: true
+    }
+
+  },
+  methods: {
+    changChecked() {
+      const that = this;
+      if (this.disable) return;
+      if (this.isHand == 'auto') {
+        wx.showModal({
+                    // title: '提示',
+          content: `要${that.confirmText}${that.eqTitle}吗?`,
+          confirmText: that.confirmText,
+          success: function (res) {
+            if (res.confirm) {
+              that.emitCheck(that); // 开关状态提交
+            }
+          }
+        });
+        return;
+      }
+      that.emitCheck(that); // 开关状态提交
+    },
+
+    emitCheck(that) {
+      let checked = that.checked;
+
+      if (!this.showWorkTime) {
+        this.disable = false;
+      } else {
+        this.disable = true;
+      }
+      that.$emit('component-switch-change', !checked);
+    }
+
+  }
+});
+</script>

+ 150 - 0
src/packagesEnv/pages/officehome/components/switch.wpy

@@ -0,0 +1,150 @@
+<style lang="less">
+.component-switch-main {
+    width: 96rpx;
+    height: 88rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.component-switch {
+    height: 48rpx;
+    width: 96rpx;
+    background: #ffe823;
+    border-radius: 12px;
+    display: inline-block;
+    box-sizing: border-box;
+    position: relative;
+    cursor: pointer;
+    transition: background-color 0.3s;
+    &.disabled {
+        cursor: not-allowed;
+        opacity: 0.5;
+    }
+}
+
+.component-switch .checked-circle {
+    position: absolute;
+    top: 50%;
+    left: -1px;
+    width: 48rpx;
+    height: 48rpx;
+    transform: translate(0, -50%);
+    transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
+    border-radius: 50%;
+    background: #ffffff;
+    border: 1px solid rgba(196, 196, 196, 1);
+    box-sizing: border-box;
+    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.03);
+
+    &.circle-true {
+        left: 1px;
+    }
+}
+
+.component-switch.checked .checked-circle {
+    transform: translate(100%, -50%);
+}
+</style>
+
+<template>
+    <div
+        class="component-switch-main"
+        v-on:click.stop="changChecked"
+    >
+        <!-- <div
+      class="component-switch"
+      :class="{'checked': checked}"
+      style="{{'background-color:'+(checked? activeColor||defaultActiveColor: inactiveColor||defaultInactiveColor)+';' }}"
+    > -->
+        <div
+            class="component-switch"
+            style="{{'background-color:'+(checked? activeColor||defaultActiveColor: inactiveColor||defaultInactiveColor)+';' }}"
+            :class="{'checked': checked,'disabled':disable}"
+        >
+            <div
+                class="checked-circle"
+                :class="{'circle-true': checked}"
+            ></div>
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+
+wepy.component({
+  data: {
+    defaultInactiveColor: 'rgba(196, 196, 196, 0.2)',
+    defaultActiveColor: 'rgba(255, 232, 35, 1)',
+    disable: false
+  },
+  props: {
+    isHand: String,
+    isACATFC: Boolean,
+    checked: Boolean,
+    activeColor: String,
+    inactiveColor: String,
+    eqTitle: String,
+    seasonType: String,
+    showWorkTime: Boolean // 展示预约时间弹框
+  },
+  computed: {
+    confirmText() {
+      const text = this.checked ? '关闭' : '开启';
+      return text
+    }
+  },
+  watch: {
+    checked() {
+      this.disable = false;
+    }
+  },
+  methods: {
+    changChecked() {
+      const that = this;
+      if (this.disable) return;
+            // console.log('this.isHand,isAirSwitch', this.isHand);
+            // 如果是过渡季 并且是ATFC 空调 则不能开启
+      if (this.isHand == 'auto' && this.isACATFC && !this.checked && this.seasonType == 'Transition') {
+        setTimeout(() => {
+          wx.showToast({
+            title: '目前为过渡季节,大厦无冷热水,空调无法供冷或供热',
+            duration: 2600,
+            icon: 'none'
+          })
+        }, 0);
+        return;
+      }
+
+      if (this.isHand == 'auto') {
+        wx.showModal({
+                    // title: '提示',
+          content: `要${that.confirmText}${that.eqTitle}吗?`,
+          confirmText: that.confirmText,
+          success: function (res) {
+            if (res.confirm) {
+              that.emitCheck(that); // 开关状态提交
+            }
+          }
+        });
+        return;
+      }
+      that.emitCheck(that); // 开关状态提交
+    },
+
+    emitCheck(that) {
+      let checked = that.checked;
+
+      if (!this.showWorkTime) {
+        this.disable = false;
+      } else {
+        this.disable = true;
+      }
+      console.log(this.showWorkTime, 'this.showWorkTimeSW');
+      console.log(this.disable, 'this.disableSw');
+      that.$emit('component-switch-change', !checked);
+    }
+
+  }
+});
+</script>

+ 215 - 0
src/packagesEnv/pages/officehome/components/voice-adjust.wpy

@@ -0,0 +1,215 @@
+<template>
+    <div class="voice-adjust office-card">
+        <div class="lamp-basic">
+            <span class="lamp-basic-name">噪声</span>
+            <div v-if="!replayFlag">
+                <div class="lamp-basic-num">{{dbs}}<span>dB</span></div>
+                <div class="lamp-basic-text">
+                    <span class="lamp-basic-level"> {{dbText}}
+                    </span>
+                    <van-icon
+                        v-if="iconFlag"
+                        name="smile-o"
+                        style="color:#f0da21"
+                    />
+                </div>
+
+            </div>
+            <div
+                v-else
+                class="basic-loading"
+            >
+                <image
+                    class="voice-basic-loading"
+                    src="{{h5StaticPath}}loading.svg"
+                />
+                <span class="voice-basic-testing">检测中</span>
+            </div>
+        </div>
+        <div class="voice-img-box">
+            <image
+                class="voice-img"
+                src="{{h5StaticPath}}voice.png"
+            />
+        </div>
+
+        <div
+            class="voice-replay"
+            @click.stop="handleReplay"
+        >
+            <image
+                class="voice-replay-img"
+                src="{{h5StaticPath}}vioce-replay.svg"
+            />
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import config from '@/config';
+let RecorderManager = wx.getRecorderManager()
+wepy.component({
+  externalClasses: ['my-class'],
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    replayFlag: false,
+    dbs: 45, // 默认值是45
+    iconFlag: false
+  },
+  computed: {
+    dbText() {
+      let dbText = '适宜办公';
+      if (this.dbs <= 60) {
+        dbText = '适宜办公';
+        this.iconFlag = false;
+      } else {
+        dbText = '闭目养神2分钟';
+        this.iconFlag = true;
+      }
+      return dbText;
+    }
+  },
+  attached() {
+    RecorderManager.onFrameRecorded((res) => {
+      console.log('onFrameRecorded', res)
+      const myTypedArray = new Int16Array(res.frameBuffer);
+      var sum = 0;
+      for (var i = 0; i < myTypedArray.length; i++) {
+        sum += Math.abs(myTypedArray[i]);
+      }
+      var mean = sum / myTypedArray.length;
+      let value = Math.abs(parseFloat(mean));
+            // 公式
+      let db = parseInt(20 * Math.log10(value));
+      this.dbs = db ? parseInt(db) : 45;
+    })
+  },
+  methods: {
+    vibrateShort() {
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+    },
+    handleReplay() {
+      this.vibrateShort();
+      this.replayFlag = true
+      RecorderManager.start({
+                // duration:3000, // 录音时长
+        frameSize: 4, // 指定帧大小,单位 KB
+        sampleRate: 16000, // 采样率
+        encodeBitRate: 48000, // 编码码率
+        numberOfChannels: 1,  // 录音通道数
+        format: 'pcm'  // 音频格式
+      })
+
+      setTimeout(() => {
+        this.replayFlag = false;
+        RecorderManager.stop();
+      }, 2000);
+      wx.uma.trackEvent('officeHome_checkVoice', {key: '噪声'});
+    }
+  }
+})
+</script>
+
+<style lang="less">
+.office-card {
+    height: 274rpx;
+    box-sizing: border-box;
+    position: relative;
+    box-shadow: 0px 0px 4rpx rgba(0, 0, 0, 0.07),
+        0px 8rpx 20rpx rgba(0, 0, 0, 0.05);
+    border-radius: 50rpx;
+    padding: 48rpx 40rpx 60rpx 60rpx;
+    background: #ffffff;
+    margin-bottom: 30rpx;
+}
+.lamp-basic {
+    font-family: PingFang SC;
+    color: #c4c4c4;
+    .lamp-basic-name {
+        font-weight: 600;
+        font-size: 32rpx;
+        line-height: 44rpx;
+        margin-bottom: 20rpx;
+        color: #4d5262;
+    }
+    .lamp-basic-num {
+        font-family: Montserrat;
+        font-size: 64rpx;
+        line-height: 78rpx;
+        color: #1b144e;
+        label {
+            font-weight: 800;
+            font-size: 24rpx;
+            margin-left: 8rpx;
+            line-height: 54rpx;
+            vertical-align: top;
+            color: #c4c4c4;
+        }
+    }
+    .lamp-basic-text {
+        display: flex;
+    }
+    .lamp-basic-level {
+        font-size: 28rpx;
+        line-height: 40rpx;
+        margin-right: 8rpx;
+    }
+    .basic-loading {
+        display: flex;
+        align-items: center;
+        margin-top: 20rpx;
+
+        .voice-basic-loading {
+            margin-right: 20rpx;
+            width: 36rpx;
+            height: 36rpx;
+        }
+        .voice-basic-testing {
+            font-size: 32rpx;
+            font-style: normal;
+            font-weight: 600;
+            line-height: 44rpx;
+        }
+    }
+}
+.voice-img-box {
+    position: absolute;
+    right: 50rpx;
+    top: 0px;
+    width: 258rpx;
+    height: 150rpx;
+    .voice-img {
+        width: 100%;
+        height: 100%;
+    }
+}
+.voice-replay {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    width: 148rpx;
+    height: 148rpx;
+    height: 148rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: #c4c4c4;
+    .voice-replay-img {
+        width: 36rpx;
+        height: 36rpx;
+    }
+}
+</style>
+
+<config>
+{
+    usingComponents: {
+        "van-icon": "module:@vant/weapp/dist/icon"
+    }
+}
+</config>

+ 219 - 0
src/packagesEnv/pages/officehome/components/work-off.wpy

@@ -0,0 +1,219 @@
+<style lang="less" scoped>
+.scene-popup {
+    width: 520rpx;
+    height: 860rpx;
+    background: linear-gradient(0deg, #fff 720rpx, #f3f3f3 140rpx);
+    &.air-popup {
+        width: 560rpx;
+        height: 720rpx;
+        background: linear-gradient(
+            0deg,
+            #fff 580rpx,
+            rgba(243, 243, 243, 1) 140rpx
+        );
+    }
+    .scene-popup-head {
+        padding-top: 30rpx;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        font-size: 38rpx;
+        .head-imgbg {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            width: 140rpx;
+            height: 140rpx;
+            background: #FFE823;
+            border-radius: 50%;
+            margin-bottom: 30rpx;
+            &.grey {
+                background: rgba(196, 196, 196, 1);
+            }
+            &.blue {
+                background: rgba(94, 139, 207, 1);
+            }
+            &.red {
+                background: rgba(229, 87, 79, 1);
+            }
+            .air-head-imgbox {
+                width: 72rpx;
+                height: 72rpx;
+                image {
+                    width: 100%;
+                    height: 100%;
+                }
+            }
+            .head-imgbox {
+                width: 40rpx;
+                height: 40rpx;
+                image {
+                    width: 100%;
+                    height: 100%;
+                }
+            }
+        }
+    }
+    .scene-popup-body {
+        display: flex;
+        flex-direction: column;
+        margin-left: 72rpx;
+        margin-top: 50rpx;
+        margin-right: 20rpx;
+        &.air-popup-body {
+            margin: 0;
+            justify-content: center;
+        }
+        .body-item {
+            padding-top: 20rpx;
+            margin-left: 70rpx;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 30rpx;
+            line-height: 42rpx;
+            color: #c4c4c4;
+            font-family: PingFang SC;
+            .text-status {
+                width: 32rpx;
+                height: 32rpx;
+                background: #FFE823;
+                border-radius: 100%;
+                display: inline-block;
+            }
+            .grey {
+                color: #4d5262;
+            }
+        }
+    }
+}
+.btns {
+    display: flex;
+    position: absolute;
+    bottom: 40rpx;
+    left: 50%;
+    transform: translateX(-50%);
+    .btn {
+        width: 240rpx;
+        height: 80rpx;
+        line-height: 80rpx;
+        text-align: center;
+        font-size: 28rpx;
+        font-weight: 500;
+        color: #c4c4c4;
+        &.air_btn {
+            height: 100rpx;
+            width: 176rpx;
+            border-radius: 60rpx;
+            padding: 34rpx, 60rpx;
+            border: 1px solid rgba(196, 196, 196, 1);
+            line-height: 100rpx;
+            font-family: PingFang SC;
+            font-size: 28rpx;
+            font-weight: 400;
+            color: rgba(77, 82, 98, 1);
+        }
+    }
+    .btn_com {
+        border: 1px solid #c4c4c4;
+        border-radius: 60rpx;
+    }
+}
+</style>
+
+<template>
+    <div class="scene-popup air-popup">
+        <div class="scene-popup-head">
+            <div class="head-imgbg">
+                <div class="air-head-imgbox">
+                    <image
+                        src="{{h5StaticPath+'icon-handshake.svg'}}"
+                        lazyload
+                    />
+                </div>
+            </div>
+        </div>
+        <div class="scene-popup-body air-popup-body">
+            <div style="text-align: center;font-weight: 500;padding-bottom: 42rpx;">工作辛苦了!</div>
+            <div
+                class="body-item"
+                v-for="(item,idx) in textObj"
+                :key="idx"
+            >
+                <div>
+                    <div v-if="item.status === 'pending'" class="text-status"> 
+                    </div>
+                    <img
+                        v-else
+                        style="width:32rpx;height:32rpx"
+                        src="{{h5StaticPath+ 'icon-check-mark.svg'}}"
+                        lazyload
+                        alt=""
+                    />
+                    <span style="margin-left:18rpx" :class="{'grey':item.status === 'pending'}">{{item.text}}</span>
+                </div>
+            </div>
+        </div>
+        <div
+            class="btns"
+            @click.stop="hideScene"
+        >
+            <span class="btn air_btn">隐藏</span>
+        </div>
+    </div>
+</template>
+<script>
+
+import wepy from '@wepy/core';
+import config from '@/config';
+let textData = [
+  {
+    text: '已为您取消当前的预约时间',
+    status: 'pending'
+  },
+  {
+    text: '系统将自动调整设备状态',
+    status: 'pending'
+  },
+  {
+    text: '感谢您的预约使用',
+    status: 'pending'
+  }
+]
+wepy.component({
+  props: {
+    sceneShow: Boolean
+  },
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    textObj: []
+  },
+  watch: {
+    sceneShow(newVal) {
+      if (newVal) {
+        let timer = null;
+        let statusTimer = null;
+        let idx = 0;
+        timer = setInterval(() => {
+          if (idx > 2) {
+            clearInterval(timer);
+            clearTimeout(statusTimer);
+            return;
+          }
+          this.textObj.push(textData[idx])
+          this.textObj[idx].status = 'ok'
+          idx++
+        }, 600);
+      } else {
+        this.textObj = []
+      }
+    }
+  },
+  methods: {
+    hideScene() {
+      this.$emit('component-open-sceneShowpop', 'hideWorkOff')
+    }
+  }
+})
+
+</script>

+ 440 - 0
src/packagesEnv/pages/officehome/components/work-scene-hand.wpy

@@ -0,0 +1,440 @@
+<style lang="less" scoped>
+.work-scene {
+    border-top: 1px solid #c4c4c466;
+    // height: 184rpx;
+    // padding: 28rpx 40rpx 40rpx 40rpx;
+    height: 92px;
+    display: flex;
+    padding: 0 40rpx;
+    justify-content: space-around;
+    align-items: center;
+}
+.off {
+    display: flex;
+    padding: 26rpx 24rpx;
+    text-align: center;
+    box-sizing: border-box;
+    align-items: center;
+    width: 156rpx;
+    height: 116rpx;
+    background: rgba(159, 183, 205, 0.15);
+    border-radius: 32rpx;
+    margin-left: 18rpx;
+    .text-font {
+        padding-left: 16rpx;
+        width: 52rpx;
+        height: 64rpx;
+        height: 64rpx;
+        font-family: 'Montserrat';
+        font-style: normal;
+        font-weight: 500;
+        font-size: 26rpx;
+        color: #4d5262;
+    }
+}
+.imgSt {
+    width: 40rpx;
+    height: 40rpx;
+}
+.book {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+    height: 116rpx;
+    background: rgba(159, 183, 205, 0.15);
+    &.hasWorkTime {
+        background: #ffe723;
+    }
+    border-radius: 16px;
+    padding-left: 24rpx;
+    .work-box {
+        // margin-left: 16rpx;
+        display: flex;
+        align-items: center;
+        // width: 220rpx;
+        .fontSi {
+            padding-top: 8rpx;
+            font-family: 'Montserrat';
+            font-style: normal;
+            font-weight: 500;
+            font-size: 30rpx;
+            line-height: 36rpx;
+            color: #1f2429;
+        }
+        .text-font {
+            font-family: 'Montserrat';
+            font-style: normal;
+            font-weight: 500;
+            font-size: 24rpx;
+            line-height: 30rpx;
+            color: #4d5262;
+        }
+    }
+
+    .book-box {
+        margin-right: 8rpx;
+        width: 96rpx;
+        height: 100rpx;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        padding: 9px;
+        box-sizing: border-box;
+        background: #ffffff;
+        box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.1),
+            0px 4px 10px rgba(0, 0, 0, 0.07);
+        border-radius: 32rpx;
+        &.bigWidth {
+            width: 196rpx;
+        }
+        .book-font {
+            // width: 52rpx;
+            // height: 64rpx;
+            font-family: 'PingFang SC';
+            font-style: normal;
+            font-weight: 400;
+            font-size: 26rpx;
+            line-height: 32rpx;
+            text-align: center;
+            color: #000000;
+        }
+    }
+}
+</style>
+<template>
+    <div class="work-scene">
+        <div
+            class="book"
+            :class="{'hasWorkTime': timeText === '工作时间' && hasWorkTime}"
+            @click="changeWorkStatus"
+        >
+            <div
+                class="work-box"
+                v-if="hasWorkTime"
+            >
+                <div class="imgSt">
+                    <img
+                        style="width:100%;height:100%"
+                        src="{{h5StaticPath+'icon-work.svg'}}"
+                        alt=""
+                        lazyload
+                    >
+                </div>
+                <div style="margin-left:20rpx">
+                    <div class="text-font">{{timeText}}</div>
+                    <div class="fontSi">{{timeRange}}</div>
+                </div>
+            </div>
+            <div
+                class="work-box"
+                v-else
+            >
+                <div class="imgSt">
+                    <img
+                        style="width:100%;height:100%"
+                        src="{{h5StaticPath+'icon-work.svg'}}"
+                        alt=""
+                        lazyload
+                    >
+                </div>
+                <div style="margin-left:20rpx">
+                    <div class="text-font">请选择工作时间段</div>
+                    <div
+                        class="text-font"
+                        style="padding-top: 8rpx;"
+                    >将自动开启办公服务</div>
+                </div>
+
+            </div>
+            <div
+                class="book-box"
+                :class="{'bigWidth':!(isWorkOff &&  !inCusRangeTime)}"
+            >
+                <div class="book-font">预约时间</div>
+            </div>
+
+        </div>
+
+        <div
+            class="off"
+            v-if="isWorkOff && !inCusRangeTime"
+            @click="handleWorkOff"
+        >
+            <div class="imgSt">
+                <img
+                    style="width:100%;height:100%"
+                    src="{{h5StaticPath+'icon-work-over.svg'}}"
+                    alt=""
+                    lazyload
+                >
+            </div>
+            <div class="text-font">我要离开</div>
+        </div>
+    </div>
+</template>
+<script>
+
+import wepy from '@wepy/core';
+import moment from 'moment';
+import store from '@/store';
+import config from '@/config';
+import { mapActions } from '@wepy/x';
+import { saveBatchCustomScene } from '@/packagesEnv/api/officehome.js'
+import { checkSpaceControl } from '@/packagesEnv/pages/common.js'
+function HHmm(time, num) {
+  const HH = time.slice(num, num + 2);
+  const mm = time.slice(num + 2, num + 4);
+  return `${HH}:${mm}`
+}
+let timer = null;
+let isClickTimer = null;
+wepy.component({
+
+  props: {
+    workDayTime: Object,
+    isControl: Boolean // 是否可控
+  },
+  store,
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    isWorkOff: false, // 到下班时间
+    hasWorkTime: true, // 有工作时间
+    workTimeList: [], // 工作时间列表
+    currentTimeRange: [], // 当前工作区间
+    timeRange: '', // 工作时间区间
+    timeText: '--', // 工作时间文案
+    inCusRangeTime: false // 在服务时间内
+  },
+  computed: {
+
+  },
+  watch: {
+    workDayTime(newVal) {
+      if (newVal.data) {
+        clearInterval(timer);
+        timer = null;
+        this.getData()
+        timer = setInterval(() => {
+          this.getData();
+        }, 1000 * 10);
+      }
+    }
+  },
+  detached() {
+    clearInterval(timer);
+    timer = null;
+  },
+  methods: {
+    ...mapActions([
+      'setCusTime',
+      'setIsNowTime'
+    ]),
+
+    isHasWorkTime() { // 有工作时间
+      let flag = false;
+      this.workTimeList.forEach((item, idx) => {
+        if (item.length !== 0) {
+          flag = true
+        }
+      })
+      if (this.timeRange) { // 有时间段
+        flag = true
+      }
+      return flag
+    },
+    concatText(today, val) {
+      let text = '';
+      if (today === val) {
+        text = '今日'
+      }
+      if (Number(val) === (Number(today) + 1)) {
+        text = '次日'
+      }
+      if (Number(val) > (Number(today) + 1)) {
+        const weekDay = moment(val).weekday();
+        const weekArr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六' ];
+        text = weekArr[weekDay];
+      }
+      return text
+    },
+    bookDayText(val, cusFlag) { // 预约显示日期
+      if (cusFlag || (this.isWorkOff && !this.inCusRangeTime)) {
+        return '工作时间'
+      }
+      if (!val.length) return ''
+      const startDate = val[0].slice(0, 8);
+      const endDate = val[1].slice(0, 8);
+      const today = moment().format('YYYYMMDD');
+      let startText = this.concatText(today, startDate);
+      let endText = this.concatText(today, endDate);
+      if (startText === endText) {
+        return `已预约-${endText}`
+      } else {
+        return `已预约(${startText}-${endText})`
+      }
+    },
+    showTimeText(val, cusFlag) {  // 显示时间文案
+      const nowTime = moment().format('YYYYMMDDHHmmss');
+      let nowtimeFlag = false;
+      val.forEach((item, idx) => {
+        const start = item[0];
+        const end = item[1];
+        if (nowTime >= end) {
+          this.workTimeList.splice(idx, 1, []);  // 已经过去的从列表删除
+        }
+        if (start <= nowTime && nowTime <= end) { // 当前在这个区间内
+          this.timeText = '工作时间'
+          this.setIsNowTime(true)
+          this.isWorkOff = true; // 过服务定制时间
+          this.currentTimeRange = item;
+          nowtimeFlag = true;
+        }
+      })
+      if (!this.hasWorkTime) return;
+      if (!nowtimeFlag) {
+        this.currentTimeRange = this.workTimeList.find(item => item.length !== 0);
+      }
+      const startTime = HHmm(this.currentTimeRange[0], 8);
+      const endTime = HHmm(this.currentTimeRange[1], 8);
+      this.timeRange = `${startTime} ~ ${endTime}`
+      this.setCusTime({ startTime: startTime, endTime: endTime })
+      this.timeText = this.bookDayText(this.currentTimeRange, cusFlag); // 预约显示日期
+    },
+    arrTrans(num, arr) { // 一维数组转换为二维数组
+      const iconsArr = []; // 声明数组
+      arr.forEach((item, index) => {
+        const page = Math.floor(index / num); // 计算该元素为第几个素组内
+        if (!iconsArr[page]) { // 判断是否存在
+          iconsArr[page] = [];
+        }
+        iconsArr[page].push(item);
+      });
+      const nowDayTime = moment().format('YYYYMMDDHHmmss');
+      let resultArr = [];
+      iconsArr.find((item, idx) => {
+        let itemEnd = item[1];
+        if (nowDayTime > itemEnd) {
+        } else {
+          resultArr.push(item)
+        }
+      })
+      return resultArr;
+    },
+    getData() {
+      this.setIsNowTime(false)
+      this.isWorkOff = false;
+      let resData = this.workDayTime.data || []
+      let timeArr = []; // 时间组
+      let cusFlag = false; // 服务定制内
+      let cusEle = {};
+      this.setCusTime({
+        startTime: '',
+        endTime: ''
+      })
+      resData.forEach((ele, eleIdx) => {
+        const nowDay = moment().format('YYYYMMDD');
+        const nowTime = moment().format('HHmmss');
+        this.inCusRangeTime = false;
+        if (ele.date === nowDay) { // 是当天且有服务定制
+          const list = ele.spaceCustomContentList || [];
+          for (let i = 0; i < list.length; i++) {
+            const spaceItem = list[i]
+            if (spaceItem.startTime && nowTime >= spaceItem.startTime && nowTime < spaceItem.endTime) {
+              cusFlag = true;
+              cusEle = spaceItem;
+              timeArr.push(`${ele.date}${spaceItem.startTime}`);
+              timeArr.push(`${ele.date}${spaceItem.endTime}`);
+            }
+          }
+        }
+        if (ele.customSceneList) {
+          let bookItemArr = ele.customSceneList;
+          bookItemArr.forEach((item, idx) => {
+            // 审批通过 或者 没有审批条件 时间存入展示列表中
+            if (item.approvalStatus === 1 || !item.hasOwnProperty('approvalStatus')) {
+              timeArr.push(`${item.startDate}${item.startTime}`);
+              timeArr.push(`${item.startDate}${item.endTime}`);
+            }
+          })
+        }
+      })
+      if (cusFlag) { // 服务定制时间
+        this.isWorkOff = false;
+        this.inCusRangeTime = true;
+        this.hasWorkTime = true;
+        this.setIsNowTime(true)
+      }
+
+      if (timeArr.length === 0) {  // 没有预约时间
+        this.workTimeList = [];
+        this.hasWorkTime = false;
+        return
+      }
+
+      timeArr.sort(); //  排序
+      let timeArrCopy = JSON.parse(JSON.stringify(timeArr));
+      timeArrCopy.forEach((item, idx) => {
+        let date = Number(item.slice(0, 8));
+        let time = item.slice(8, 16);
+        let nextItem = timeArrCopy[idx + 1] || '';
+        let nextDate = Number(nextItem.slice(0, 8));
+        let nextTime = nextItem.slice(8, 16);
+        if (time === nextTime && nextDate === date) { // 去重
+          timeArr.splice(timeArr.findIndex(jtem => jtem === item), 1);
+          timeArr.splice(timeArr.findIndex(jtem => jtem === nextItem), 1);
+        }
+        if (time === '240000' && nextTime === '000000' && (nextDate - 1 === date)) { // 时间段跨天相邻
+          timeArr.splice(timeArr.findIndex(jtem => jtem === item), 1);
+          timeArr.splice(timeArr.findIndex(jtem => jtem === nextItem), 1);
+        }
+      })
+      let timeArrResult = this.arrTrans(2, timeArr) // 一维数组转换为二维数组
+      if (timeArrResult.length === 0) {
+        this.hasWorkTime = false;
+        return
+      }
+      this.workTimeList = JSON.parse(JSON.stringify(timeArrResult));
+      this.hasWorkTime = this.isHasWorkTime();
+      this.showTimeText(timeArrResult, cusFlag);// 显示时间文案
+    },
+    handleWorkOff() { // 我要离开
+      if (!checkSpaceControl(this.isControl)) return;
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+      this.$emit('component-open-sceneShowpop', 'showWorkOff')
+      const currentStart = this.currentTimeRange[0];
+      const currenEnd = this.currentTimeRange[1];
+      let paramsArr = [];
+      this.workDayTime.data.forEach((ele, eleIdx) => {
+        let bookItemArr = ele.customSceneList || [];
+        if (bookItemArr.length > 0) {
+          bookItemArr.find(item => {
+            const itemStart = `${item.startDate}${item.startTime}`
+            const itemEnd = `${item.startDate}${item.endTime}`
+            if (currentStart <= itemStart && itemEnd <= currenEnd) {
+              item.type = 3
+              item.endTimeReal = moment().format('HHmmss');
+              paramsArr.push(item);
+            }
+          })
+        }
+      })
+      saveBatchCustomScene({ 'historyBookList': paramsArr, 'type': 'workOff' }).then(res => {
+        this.$emit('component-get-workTimeData');
+        clearTimeout(isClickTimer);
+      })
+    },
+
+    changeWorkStatus() {
+      if (!checkSpaceControl(this.isControl)) return;
+      this.$emit('component-open-workTimePop', true)
+      this.$emit('component-EquipType-workTimePop', 'all');
+    }
+  }
+
+})
+</script>

+ 437 - 0
src/packagesEnv/pages/officehome/components/work-scene.wpy

@@ -0,0 +1,437 @@
+<style lang="less" scoped>
+.work-scene {
+    border-top: 1px solid #c4c4c466;
+    // height: 184rpx;
+    // padding: 28rpx 40rpx 40rpx 40rpx;
+     height:92px;
+    display: flex;
+    padding:0 40rpx;
+    justify-content: space-around;
+    align-items: center;
+}
+.off {
+    display: flex;
+    padding: 26rpx 24rpx;
+    text-align: center;
+    box-sizing: border-box;
+    align-items: center;
+    width: 156rpx;
+    height: 116rpx;
+    background: rgba(159, 183, 205, 0.15);
+    border-radius: 32rpx;
+    margin-left: 18rpx;
+    .text-font {
+        padding-left: 16rpx;
+        width: 52rpx;
+        height: 64rpx;
+        height: 64rpx;
+        font-family: 'Montserrat';
+        font-style: normal;
+        font-weight: 500;
+        font-size: 26rpx;
+        color: #4d5262;
+    }
+}
+.imgSt {
+    width: 40rpx;
+    height: 40rpx;
+}
+.book {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+    height: 116rpx;
+    background: rgba(159, 183, 205, 0.15);
+    &.hasWorkTime {
+        background: #ffe723;
+    }
+    border-radius: 16px;
+    padding-left: 24rpx;
+    .work-box {
+        // margin-left: 16rpx;
+        display: flex;
+        align-items: center;
+        // width: 220rpx;
+        .fontSi {
+            padding-top: 8rpx;
+            font-family: 'Montserrat';
+            font-style: normal;
+            font-weight: 500;
+            font-size: 30rpx;
+            line-height: 36rpx;
+            color: #1f2429;
+        }
+        .text-font {
+            font-family: 'Montserrat';
+            font-style: normal;
+            font-weight: 500;
+            font-size: 24rpx;
+            line-height: 30rpx;
+            color: #4d5262;
+        }
+    }
+
+    .book-box {
+        margin-right: 8rpx;
+        width: 96rpx;
+        height: 100rpx;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        padding: 9px;
+        box-sizing: border-box;
+        background: #ffffff;
+        box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.1),
+            0px 4px 10px rgba(0, 0, 0, 0.07);
+        border-radius: 32rpx;
+        &.bigWidth {
+            width: 196rpx;
+        }
+        .book-font {
+            // width: 52rpx;
+            // height: 64rpx;
+            font-family: 'PingFang SC';
+            font-style: normal;
+            font-weight: 400;
+            font-size: 26rpx;
+            line-height: 32rpx;
+            text-align: center;
+            color: #000000;
+        }
+    }
+}
+</style>
+<template>
+    <div class="work-scene">
+        <div
+            class="book"
+            :class="{'hasWorkTime': timeText === '工作时间' && hasWorkTime}"
+            @click="changeWorkStatus"
+        >
+            <div
+                class="work-box"
+                v-if="hasWorkTime"
+            >
+                <div class="imgSt">
+                    <img
+                        style="width:100%;height:100%"
+                        src="{{h5StaticPath+'icon-work.svg'}}"
+                        alt=""
+                        lazyload
+                    >
+                </div>
+                <div style="margin-left:20rpx">
+                    <div class="text-font">{{timeText}}</div>
+                    <div class="fontSi">{{timeRange}}</div>
+                </div>
+            </div>
+            <div
+                class="work-box"
+                v-else
+            >
+                <div class="imgSt">
+                    <img
+                        style="width:100%;height:100%"
+                        src="{{h5StaticPath+'icon-work.svg'}}"
+                        alt=""
+                        lazyload
+                    >
+                </div>
+                <div style="margin-left:20rpx">
+                    <div class="text-font">请选择工作时间段</div>
+                    <div
+                        class="text-font"
+                        style="padding-top: 8rpx;"
+                    >将自动开启办公服务</div>
+                </div>
+
+            </div>
+            <div
+                class="book-box"
+                :class="{'bigWidth':!(isWorkOff &&  !inCusRangeTime)}"
+            >
+                <div class="book-font">预约时间</div>
+            </div>
+
+        </div>
+
+        <div
+            class="off"
+            v-if="isWorkOff && !inCusRangeTime"
+            @click="handleWorkOff"
+        >
+            <div class="imgSt">
+                <img
+                    style="width:100%;height:100%"
+                    src="{{h5StaticPath+'icon-work-over.svg'}}"
+                    alt=""
+                    lazyload
+                >
+            </div>
+            <div class="text-font">我要离开</div>
+        </div>
+    </div>
+</template>
+<script>
+
+import wepy from '@wepy/core';
+import moment from 'moment';
+import store from '@/store';
+import config from '@/config';
+import { mapState, mapActions } from '@wepy/x';
+import { saveBatchCustomScene } from '@/packagesEnv/api/officehome.js'
+import { checkSpaceControl } from '@/packagesEnv/pages/common.js'
+function HHmm(time, num) {
+  const HH = time.slice(num, num + 2);
+  const mm = time.slice(num + 2, num + 4);
+  return `${HH}:${mm}`
+}
+let timer = null;
+let isClickTimer = null;
+wepy.component({
+
+  props: {
+    workDayTime: Object,
+    isControl: Boolean // 是否可控
+  },
+  store,
+  data: {
+    h5StaticPath: config.h5StaticPath + '/page-officehome/',
+    isWorkOff: false, // 到下班时间
+    hasWorkTime: true, // 有工作时间
+    workTimeList: [], // 工作时间列表
+    currentTimeRange: [], // 当前工作区间
+    timeRange: '', // 工作时间区间
+    timeText: '--', // 工作时间文案
+    inCusRangeTime: false // 在服务时间内
+  },
+  computed: {
+
+  },
+  watch: {
+    workDayTime(newVal) {
+      if (newVal.data) {
+        clearInterval(timer);
+        timer = null;
+        this.getData()
+        timer = setInterval(() => {
+          this.getData();
+        }, 1000 * 10);
+      }
+    }
+  },
+  detached() {
+    clearInterval(timer);
+    timer = null;
+  },
+  methods: {
+    ...mapActions([
+      'setCusTime',
+      'setIsNowTime'
+    ]),
+
+    isHasWorkTime() { // 有工作时间
+      let flag = false;
+      this.workTimeList.forEach((item, idx) => {
+        if (item.length !== 0) {
+          flag = true
+        }
+      })
+      if (this.timeRange) { // 有时间段
+        flag = true
+      }
+      return flag
+    },
+    concatText(today, val) {
+      let text = '';
+      if (today === val) {
+        text = '今日'
+      }
+      if (Number(val) === (Number(today) + 1)) {
+        text = '次日'
+      }
+      if (Number(val) > (Number(today) + 1)) {
+        const weekDay = moment(val).weekday();
+        const weekArr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六' ];
+        text = weekArr[weekDay];
+      }
+      return text
+    },
+    bookDayText(val, cusFlag) { // 预约显示日期
+      if (cusFlag || (this.isWorkOff && !this.inCusRangeTime)) {
+        return '工作时间'
+      }
+      if (!val.length) return ''
+      const startDate = val[0].slice(0, 8);
+      const endDate = val[1].slice(0, 8);
+      const today = moment().format('YYYYMMDD');
+      let startText = this.concatText(today, startDate);
+      let endText = this.concatText(today, endDate);
+      if (startText === endText) {
+        return `已预约-${endText}`
+      } else {
+        return `已预约(${startText}-${endText})`
+      }
+    },
+    showTimeText(val, cusFlag) {  // 显示时间文案
+      if (!this.hasWorkTime) {
+        this.hasWorkTime = false;        // console.log(this.workTimeList.length, '没有时间表,结束');
+        return;
+      }
+      const nowTime = moment().format('YYYYMMDDHHmmss');
+      let nowtimeFlag = false;
+      val.forEach((item, idx) => {
+        const start = item[0];
+        const end = item[1];
+        if (nowTime >= end) {
+          this.workTimeList.splice(idx, 1, []);  // 已经过去的从列表删除
+        }
+        if (start <= nowTime && nowTime <= end) { // 当前在这个区间内
+          this.timeText = '工作时间'
+          this.setIsNowTime(true)
+          this.isWorkOff = true; // 过服务定制时间
+          this.currentTimeRange = item;
+          nowtimeFlag = true;
+        }
+      })
+      if (!this.hasWorkTime) return;
+      if (!nowtimeFlag) {
+        this.currentTimeRange = this.workTimeList.find(item => item.length !== 0);
+        this.timeText = this.bookDayText(this.currentTimeRange[0])
+      }
+      const startTime = HHmm(this.currentTimeRange[0], 8);
+      const endTime = HHmm(this.currentTimeRange[1], 8);
+      this.timeRange = `${startTime} ~ ${endTime}`
+      this.setCusTime({ cusStartTime: startTime, cusEndTime: endTime })
+      this.timeText = this.bookDayText(this.currentTimeRange, cusFlag); // 预约显示日期
+    },
+    arrTrans(num, arr) { // 一维数组转换为二维数组
+      const iconsArr = []; // 声明数组
+      arr.forEach((item, index) => {
+        const page = Math.floor(index / num); // 计算该元素为第几个素组内
+        if (!iconsArr[page]) { // 判断是否存在
+          iconsArr[page] = [];
+        }
+        iconsArr[page].push(item);
+      });
+      const nowDayTime = moment().format('YYYYMMDDHHmmss');
+      let resultArr = [];
+      iconsArr.find((item, idx) => {
+        let itemEnd = item[1];
+        if (nowDayTime > itemEnd) {
+        } else {
+          resultArr.push(item)
+        }
+      })
+      return resultArr;
+    },
+    getData() {
+      this.setIsNowTime(false)
+      this.isWorkOff = false;
+      let resData = this.workDayTime.data || []
+      let timeArr = []; // 时间组
+      let cusFlag = false; // 服务定制内
+      let cusEle = {};
+      this.setCusTime({
+        cusStartTime: '',
+        cusEndTime: ''
+      })
+      resData.forEach((ele, eleIdx) => {
+        const nowDay = moment().format('YYYYMMDD');
+        const nowTime = moment().format('HHmmss');
+        this.inCusRangeTime = false;
+        if (ele.date === nowDay && ele.cusStartTime && nowTime >= ele.cusStartTime && nowTime < ele.cusEndTime) { // 是当天且有服务定制
+          cusFlag = true;
+          cusEle = ele;
+          timeArr.push(`${ele.date}${ele.cusStartTime}`);
+          timeArr.push(`${ele.date}${ele.cusEndTime}`);
+        }
+        if (ele.customSceneList) {
+          let bookItemArr = ele.customSceneList;
+          bookItemArr.forEach((item, idx) => {
+            timeArr.push(`${item.startDate}${item.startTime}`);
+            timeArr.push(`${item.startDate}${item.endTime}`);
+          })
+        }
+      })
+      if (cusFlag) { // 服务定制时间
+        this.isWorkOff = false;
+        this.inCusRangeTime = true;
+        this.hasWorkTime = true;
+        this.setIsNowTime(true)
+        // return;
+      }
+
+      if (timeArr.length === 0) {  // 没有预约时间
+        this.workTimeList = [];
+        this.hasWorkTime = false;
+        return
+      }
+
+      timeArr.sort(); //  排序
+      let timeArrCopy = JSON.parse(JSON.stringify(timeArr));
+      timeArrCopy.forEach((item, idx) => {
+        let date = Number(item.slice(0, 8));
+        let time = item.slice(8, 16);
+        let nextItem = timeArrCopy[idx + 1] || '';
+        let nextDate = Number(nextItem.slice(0, 8));
+        let nextTime = nextItem.slice(8, 16);
+        if (time === nextTime && nextDate === date) { // 去重
+          timeArr.splice(timeArr.findIndex(jtem => jtem === item), 1);
+          timeArr.splice(timeArr.findIndex(jtem => jtem === nextItem), 1);
+        }
+        if (time === '240000' && nextTime === '000000' && (nextDate - 1 === date)) { // 时间段跨天相邻
+          timeArr.splice(timeArr.findIndex(jtem => jtem === item), 1);
+          timeArr.splice(timeArr.findIndex(jtem => jtem === nextItem), 1);
+        }
+      })
+            //   console.log(timeArr, 'timeArrtimeArrtimeArrtimeArrtimeArr');
+      let timeArrResult = this.arrTrans(2, timeArr) // 一维数组转换为二维数组
+      if (timeArrResult.length === 0) {
+        this.hasWorkTime = false;
+        return
+      }
+      this.workTimeList = JSON.parse(JSON.stringify(timeArrResult));
+      this.hasWorkTime = this.isHasWorkTime();
+    //   console.log(this.workTimeList, '        this.workTimeList');
+      this.showTimeText(timeArrResult, cusFlag);// 显示时间文案
+    },
+    handleWorkOff() { // 我要离开
+      if (!checkSpaceControl(this.isControl)) return;
+      if (wx.canIUse('vibrateShort')) {
+        wx.vibrateShort();
+      } else {
+        wx.vibrateLong();
+      }
+      this.$emit('component-open-sceneShowpop', 'showWorkOff')
+      const currentStart = this.currentTimeRange[0];
+      const currenEnd = this.currentTimeRange[1];
+      let paramsArr = [];
+      this.workDayTime.data.forEach((ele, eleIdx) => {
+        let bookItemArr = ele.customSceneList || [];
+        if (bookItemArr.length > 0) {
+          bookItemArr.find(item => {
+            const itemStart = `${item.startDate}${item.startTime}`
+            const itemEnd = `${item.startDate}${item.endTime}`
+            if (currentStart <= itemStart && itemEnd <= currenEnd) {
+              item.type = 1
+              paramsArr.push(item);
+            }
+          })
+        }
+      })
+      saveBatchCustomScene({ 'historyBookList': paramsArr, 'type': 'workOff' }).then(res => {
+        this.$emit('component-get-workTimeData');
+        clearTimeout(isClickTimer);
+      })
+    },
+
+    changeWorkStatus() {
+      if (!checkSpaceControl(this.isControl)) return;
+      this.$emit('component-open-workTimePop', true)
+    }
+  }
+
+})
+</script>

+ 483 - 0
src/packagesEnv/pages/officehome/demo.wpy

@@ -0,0 +1,483 @@
+<style lang="less">
+page {
+    height: 100%;
+    background-color: #eceff4;
+}
+.pagedemo {
+    position: relative;
+    width: 100%;
+    image {
+        width: 100%;
+    }
+    .backDiv {
+        position: absolute;
+        top: 50px;
+        left: 10px;
+        height: 36px;
+        width: 40px;
+        z-index: 2;
+    }
+    .mainWrap {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        box-sizing: border-box;
+        bottom: 0;
+        z-index: 1;
+        padding: 592rpx 44rpx 0 44rpx;
+        .top {
+            height: 226rpx;
+            margin-bottom: 15px;
+            position: relative;
+            .chartDiv {
+                position: absolute;
+                height: 80px;
+                bottom: 0;
+                left: 0;
+                width: 100%;
+            }
+            .serviceDiv {
+                position: absolute;
+                height: 20px;
+                top: 9px;
+                right: 20px;
+                width: 110px;
+            }
+        }
+        .air {
+            height: 520rpx;
+            margin-bottom: 15px;
+            position: relative;
+            .openButton {
+                position: absolute;
+                top: 93px;
+                right: 15px;
+                width: 50px;
+                height: 44px;
+            }
+            .downCold {
+                position: absolute;
+                top: 149px;
+                right: 76px;
+                width: 40px;
+                height: 40px;
+            }
+            .upHot {
+                position: absolute;
+                top: 149px;
+                right: 15px;
+                width: 40px;
+                height: 40px;
+            }
+            .xiaoWind {
+                position: absolute;
+                bottom: 9px;
+                right: 76px;
+                width: 40px;
+                height: 40px;
+            }
+            .daWind {
+                position: absolute;
+                bottom: 9px;
+                right: 15px;
+                width: 40px;
+                height: 40px;
+            }
+        }
+        .lamp {
+            height: 456rpx;
+            margin-bottom: 15px;
+            position: relative;
+            .openButton {
+                position: absolute;
+                top: 91px;
+                right: 15px;
+                width: 50px;
+                height: 42px;
+            }
+            .closeButton {
+                position: absolute;
+                top: 140px;
+                right: 15px;
+                width: 50px;
+                height: 66px;
+            }
+        }
+        .curtain {
+            height: 280rpx;
+            margin-bottom: 15px;
+            position: relative;
+            .openButton {
+                position: absolute;
+                top: 86px;
+                right: 14px;
+                width: 40px;
+                height: 40px;
+            }
+            .closeButton {
+                position: absolute;
+                top: 86px;
+                right: 74px;
+                width: 40px;
+                height: 40px;
+            }
+        }
+        .water {
+            height: 280rpx;
+            margin-bottom: 15px;
+            position: relative;
+            .openButton {
+                position: absolute;
+                bottom: 10px;
+                right: 8px;
+                width: 55px;
+                height: 36px;
+            }
+        }
+        .fan {
+            height: 280rpx;
+            margin-bottom: 15px;
+            position: relative;
+            .openButton {
+                position: absolute;
+                bottom: 8px;
+                right: 15px;
+                width: 55px;
+                height: 36px;
+            }
+        }
+        .wet {
+            height: 280rpx;
+            margin-bottom: 15px;
+            position: relative;
+            .openButton {
+                position: absolute;
+                bottom: 8px;
+                right: 15px;
+                width: 55px;
+                height: 36px;
+            }
+        }
+    }
+}
+</style>
+
+<template>
+    <div
+        class="pagedemo"
+        :style="{'height':isHome?'1905px':'500px'}"
+    >
+        <div
+            class="backDiv"
+            @click="mainBack"
+        ></div>
+        <image
+            class="mainImg"
+            :src="img"
+            mode="widthFix"
+            @click="mainImgShow"
+        />
+        <div
+            class="mainWrap"
+            v-show="isHome"
+        >
+            <div class="top">
+                <div
+                    class="serviceDiv"
+                    @click="serviceTimeClick"
+                ></div>
+                <div
+                    class="chartDiv"
+                    @click="chartShowClick"
+                ></div>
+            </div>
+            <div class="air">
+                <div
+                    class="openButton"
+                    @click="airClick"
+                ></div>
+                <div
+                    class="downCold"
+                    @click="downCold"
+                    v-show="airOpen"
+                ></div>
+                <div
+                    class="upHot"
+                    @click="upHot"
+                    v-show="airOpen"
+                ></div>
+                <div
+                    class="xiaoWind"
+                    @click="xiaoWind"
+                    v-show="airOpen"
+                ></div>
+                <div
+                    class="daWind"
+                    @click="daWind"
+                    v-show="airOpen"
+                ></div>
+            </div>
+            <div class="lamp">
+                <div
+                    class="openButton"
+                    v-show="!lampOpen"
+                    @click="lampOpenClick"
+                ></div>
+                <div
+                    class="closeButton"
+                    v-show="lampOpen"
+                    @click="lampCloseClick"
+                ></div>
+            </div>
+            <div class="curtain">
+                <div
+                    class="openButton"
+                    @click="curtainOpenClick"
+                ></div>
+                <div
+                    class="closeButton"
+                    @click="curtainCloseClick"
+                ></div>
+            </div>
+            <div class="water">
+                <div
+                    class="openButton"
+                    @click="waterClick"
+                ></div>
+            </div>
+            <div class="fan">
+                <div
+                    class="openButton"
+                    @click="fanClick"
+                ></div>
+            </div>
+            <div class="wet">
+                <div
+                    class="openButton"
+                    @click="wetClick"
+                ></div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import wepy from '@wepy/core';
+import store from '@/store';
+import config from '@/config';
+import moment from 'moment';
+import { mapState } from '@wepy/x';
+import { checkHasUserInfo } from '@/service/user';
+import { saveCompanyConfig } from '@/service/companyConfig';
+
+
+wepy.page({
+    store,
+    data: {
+        imgpath: config.h5StaticPath + '/page-officehome-demo/',
+        img: config.h5StaticPath + '/page-officehome-demo/tiyan.png',//allopen.png
+        isHome: false,//是否是主页面
+        showChart: false,
+        serviceTime: false,
+        airOpen: true,
+        lampOpen: true,
+        curtainOpen: true,
+        waterOpen: true,
+        fanOpen: true,
+        wetOpen: true,
+    },
+    computed: {
+        ...mapState({ token: state => state.user.token, energyHeadData: state => state.portrait.energyHeadData })
+    },
+    onLoad() {
+        var _this = this;
+        checkHasUserInfo().then(function (userInfo) {
+            console.log('checkHasUserInfo', userInfo);
+            var companyId = userInfo.companyId;
+            // saveCompanyConfig(companyId).then(() => 
+            // })
+        });
+        setTimeout(() => {
+            this.img = this.imgpath + 'allopen.png';
+            this.isHome = true;
+        }, 2000);
+
+    },
+    onReady() { },
+    methods: {
+        chartShowClick() {
+            this.img = this.imgpath + 'chart.png';
+            this.isHome = false;
+            this.showChart = true;
+        },
+        serviceTimeClick() {
+            this.img = this.imgpath + 'serviceTime.png';
+            this.isHome = false;
+            // this.serviceTime = true;
+        },
+        airClick() {
+            console.log('this.airOpen', this.airOpen);
+            if (this.airOpen) {
+                this.airOpen = false;
+                this.img = this.imgpath + 'airclose.png';
+            } else {
+                this.airOpen = true;
+                this.img = this.imgpath + 'allopen.png';
+            }
+            this.lampOpen = true;
+            this.curtainOpen = true;
+            this.waterOpen = true;
+            this.fanOpen = true;
+            this.wetOpen = true;
+        },
+        downCold() {
+            this.img = this.imgpath + 'downcold.png';
+            this.isHome = false;
+        },
+        upHot() {
+            this.img = this.imgpath + 'uphot.png';
+            this.isHome = false;
+        },
+        xiaoWind() {
+            this.img = this.imgpath + 'windxiao.png';
+            this.isHome = false;
+        },
+        daWind() {
+            this.img = this.imgpath + 'windda.png';
+            this.isHome = false;
+        },
+        lampOpenClick() {
+            if (!this.lampOpen) {
+                this.lampOpen = true;
+
+                this.airOpen = true;
+                this.curtainOpen = true;
+                this.waterOpen = true;
+                this.fanOpen = true;
+                this.wetOpen = true;
+                this.img = this.imgpath + 'allopen.png';
+            }
+        },
+        lampCloseClick() {
+            console.log('this.lampOpen', this.lampOpen);
+            if (this.lampOpen) {
+                this.lampOpen = false;
+
+                this.airOpen = true;
+                this.curtainOpen = true;
+                this.waterOpen = true;
+                this.fanOpen = true;
+                this.wetOpen = true;
+                this.img = this.imgpath + 'lampclose.png';
+            }
+        },
+        fanClick() {
+            if (this.fanOpen) {
+                this.fanOpen = false;
+                this.img = this.imgpath + 'fanclose.png';
+            } else {
+                this.fanOpen = true;
+                this.img = this.imgpath + 'allopen.png';
+            }
+            this.airOpen = true;
+            this.lampOpen = true;
+            this.curtainOpen = true;
+            this.waterOpen = true;
+            this.wetOpen = true;
+        },
+        waterClick() {
+            if (this.waterOpen) {
+                this.waterOpen = false;
+                this.img = this.imgpath + 'waterclose.png';
+            } else {
+                this.waterOpen = true;
+                this.img = this.imgpath + 'allopen.png';
+            }
+            this.airOpen = true;
+            this.lampOpen = true;
+            this.curtainOpen = true;
+            this.fanOpen = true;
+            this.wetOpen = true;
+        },
+        wetClick() {
+            if (this.wetOpen) {
+                this.wetOpen = false;
+                this.img = this.imgpath + 'wetclose.png';
+            } else {
+
+                this.wetOpen = true;
+                this.img = this.imgpath + 'allopen.png';
+            }
+            this.airOpen = true;
+            this.lampOpen = true;
+            this.curtainOpen = true;
+            this.waterOpen = true;
+            this.fanOpen = true;
+        },
+        curtainOpenClick() {
+            if (!this.curtainOpen) {
+                this.airOpen = true;
+                this.lampOpen = true;
+                this.curtainOpen = true;
+                this.waterOpen = true;
+                this.fanOpen = true;
+                this.wetOpen = true;
+                this.img = this.imgpath + 'allopen.png';
+            }
+        },
+        curtainCloseClick() {
+            if (this.curtainOpen) {
+                this.curtainOpen = false;
+
+                this.airOpen = true;
+                this.lampOpen = true;
+                this.waterOpen = true;
+                this.fanOpen = true;
+                this.wetOpen = true;
+                this.img = this.imgpath + 'curtainclose.png';
+            }
+        },
+        mainBack() {
+            if (this.isHome) {
+                wx.navigateBack();
+            } else {
+                this.img = this.imgpath + 'allopen.png';
+                this.isHome = true;
+                this.showChart = false;
+
+                this.airOpen = true;
+                this.lampOpen = true;
+                this.curtainOpen = true;
+                this.waterOpen = true;
+                this.fanOpen = true;
+                this.wetOpen = true;
+            }
+        },
+        mainImgShow() {
+            //如果是参数chart页面 则只能通过back按钮
+            //如果是其他非主页面 一点击则 显示主页面图片
+            if (!this.isHome && !this.showChart) {
+                this.img = this.imgpath + 'allopen.png';
+                this.isHome = true;
+
+                this.airOpen = true;
+                this.lampOpen = true;
+                this.curtainOpen = true;
+                this.waterOpen = true;
+                this.fanOpen = true;
+                this.wetOpen = true;
+            }
+        }
+    }
+});
+</script>
+
+<config>
+{
+    "navigationStyle": "custom",
+    backgroundColor: '#f7f8fa',
+    backgroundColorTop:'#FFE823',
+    backgroundColorBottom:"#eceff4"	
+}
+</config>

+ 0 - 0
src/packagesEnv/pages/officehome/detail.wpy


部分文件因文件數量過多而無法顯示