浏览代码

确定距离

zachary 4 年之前
父节点
当前提交
65f1379b9d

+ 19 - 0
app.js

@@ -0,0 +1,19 @@
+// app.js
+App({
+  onLaunch() {
+    // 展示本地存储能力
+    const logs = wx.getStorageSync('logs') || []
+    logs.unshift(Date.now())
+    wx.setStorageSync('logs', logs)
+
+    // 登录
+    wx.login({
+      success: res => {
+        // 发送 res.code 到后台换取 openId, sessionKey, unionId
+      }
+    })
+  },
+  globalData: {
+    userInfo: null
+  }
+})

+ 13 - 0
app.json

@@ -0,0 +1,13 @@
+{
+  "pages": [
+    "pages/index/index"
+  ],
+  "window": {
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#EDEDED",
+    "navigationBarTitleText": "音频讲解",
+    "navigationBarTextStyle": "black"
+  },
+  "style": "v2",
+  "sitemapLocation": "sitemap.json"
+}

+ 9 - 0
app.wxss

@@ -0,0 +1,9 @@
+/**app.wxss**/
+page {
+  height: 100%;
+  width: 100%;
+  background-image:linear-gradient(135deg, #38A2D7 0%, #1C2E4C 100%);
+}
+
+
+

+ 145 - 0
components/audio-play/audio-play.js

@@ -0,0 +1,145 @@
+// components/audio-play.js
+import { CDN_URL,API_BASE_URL } from '../../config/index';
+var AllExample = [];
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    audio: {
+      type: Object,
+      value: {},
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    cdn_url:CDN_URL,
+    durationTime: '00:00',
+    currentTime: '00:00',
+    isPlay: false,
+    audioExample: null,
+    progress: 0,
+    allAudio: [],
+    api_base_url:API_BASE_URL,
+    targetUrl:API_BASE_URL+'/data/'
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+
+    startMusic() {
+      // 全部暂停
+      AllExample.forEach(simpleAudio => {
+        // console.log('simpleAudio', simpleAudio)
+        simpleAudio.pause();
+      })
+      // this.data.audioExample.autoplay = true;
+      // this.data.audioExample.src = this.data.targetUrl+this.properties.audio['audio'];
+      // this.data.audioExample.play();
+      // this.setData({
+      //   isPlay: true
+      // })
+      // console.log('this.properties.audio', this.properties.audio)
+      // console.log('this.data', this.data)
+      // console.log('开始音乐')
+      this.data.audioExample.autoplay = true;
+      this.data.audioExample.src = this.data.targetUrl+this.properties.audio['audio'];
+      this.data.audioExample.play();
+      this.setData({
+        isPlay: true
+      })
+      this.loadProgress()
+    },
+    // 播放中
+    loadProgress() {
+      this.data.audioExample.onCanplay((e) => {
+        this.data.audioExample.duration; //必须写,不然获取不到。。。
+        var timer = setInterval(() => {
+          // 只要音频暂停了,定时器(假的)不执行
+          if (this.data.audioExample.paused) return;
+          var currentime = this.data.audioExample.currentTime;
+          var duration = this.data.audioExample.duration;
+          var currTimeStr = this.formatTime(currentime);
+          var duraTimeStr = this.formatTime(duration);
+          var progress = parseInt((this.data.audioExample.currentTime / this.data.audioExample.duration) * 100)
+          // console.log('progress', progress)
+          // console.log('this.data.progress', this.data.progress)
+          if (progress === 100 || isNaN(this.data.progress)) {
+            clearInterval(timer)
+            console.log('停止了')
+            this.setData({
+              isPlay: false,
+              progress: 0,
+              currentTime: "00:00",
+            })
+          } else {
+            // console.log('duraTimeStr', typeof duraTimeStr, duraTimeStr, !!duraTimeStr)
+            // console.log('currTimeStr', typeof currTimeStr, currTimeStr, !!currTimeStr)
+            // console.log('this.data.audioExample', this.data.audioExample, this.data.audioExample.paused)
+            this.setData({
+              progress,
+              currentTime: currTimeStr,
+              durationTime: duraTimeStr
+            })
+          }
+        }, 1000);
+      });
+    },
+    stopMusic() {
+      this.data.audioExample.pause();
+      console.log('结束音乐')
+      this.setData({
+        isPlay: false
+      })
+    },
+    formatTime: function (num) { //格式化时间格式
+      num = num.toFixed(0);
+      let second = (num % 60);
+      if (second < 10) second = '0' + second;
+      let min = Math.floor(num / 60);
+      if (min < 10) min = '0' + min;
+      return min + ":" + second;
+    }
+  },
+  attached: function () {
+    //组件显示初始化函数
+    console.log(3);
+    var that = this;
+    this.data.audioExample = wx.createInnerAudioContext();
+    //所有组件实例都放在一个容器AllExample里
+    AllExample.push(this.data.audioExample);
+    this.data.audioExample.onPlay(function () {
+      console.log('播放中。。。')
+    })
+    this.data.audioExample.onTimeUpdate(function () {
+      console.log('监听进度。。')
+    })
+    //控制单个实例的isPlay
+    this.data.audioExample.onPause(function () {
+      that.setData({
+        isPlay: false
+      })
+    })
+    // 每个组件实例播放结束
+    this.data.audioExample.onEnded(() => {
+      this.setData({
+        currentTime: "00:00",
+        isPlay: false,
+        progress: 0,
+      })
+      console.log('播放结束了')
+    })
+    // 在组件实例进入页面节点树时执行
+  },
+  detached: function () {
+    // 在组件实例被从页面节点树移除时执行
+    // 卸载
+    this.data.audioExample.destroy();
+    AllExample = [];
+  }
+})

+ 4 - 0
components/audio-play/audio-play.json

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

+ 17 - 0
components/audio-play/audio-play.wxml

@@ -0,0 +1,17 @@
+<!--components/audio-play.wxml-->
+<view class="audio">
+  <image class="bg-img" src="{{targetUrl}}{{audio.icon}}"></image>
+  <image class="icon-img" bindtap="startMusic" src="{{cdn_url}}/project/pause.png"
+    style="display:{{isPlay?'none':'block'}}">
+  </image>
+  <image class="icon-img" bindtap="stopMusic" src="{{cdn_url}}/project/start.png"
+    style="display:{{isPlay?'block':'none'}}">
+  </image>
+  <view class="free-MusicProgress">
+    <view class="before-progress">
+      <view class="progress" style="width:{{progress}}%;"></view>
+    </view>
+    <view class="durationTime">{{currentTime}}</view>
+  </view>
+</view>
+

+ 55 - 0
components/audio-play/audio-play.wxss

@@ -0,0 +1,55 @@
+/* components/audio-play.wxss */
+.audio {
+  position: relative;
+  width:45%;
+  display:inline-block;
+  margin:1%;
+}
+.audio .bg-img {
+  width:100%;
+  height:15vh;
+  display:inline-block
+}
+.audio .icon-img {
+  width:5vh;
+  height:5vh;
+  position:absolute;
+  top:50%;
+  left:50%;
+  margin-left:-2.5vh;
+  margin-top:-2.5vh;
+}
+
+.audio .free-MusicProgress {
+  position: absolute;
+  width: 100%;
+  height: 20%;
+  bottom: 6%;
+  background: #000000;
+  opacity: 0.6;
+}
+.audio .free-MusicProgress .before-progress {
+  width:80%;
+  height:2px;
+  position: absolute;
+  top: 50%;
+  background: rgba(255,255,255,0.3);
+  margin:0 10%;
+}
+.audio .free-MusicProgress .progress {
+  height: 100%;
+  background: #38A2D7;
+  
+}
+
+
+.audio .free-MusicProgress .durationTime {
+  position: absolute;
+  top: 22%;
+  right: 6%;
+  font-size: 10px;
+  font-family: Microsoft YaHei;
+  font-weight: 400;
+  color: #FFFFFF;
+  opacity: 0.67;
+}

+ 64 - 0
components/big-pic/big-pic.js

@@ -0,0 +1,64 @@
+import { CDN_URL,API_BASE_URL } from '../../config/index';
+import http from '../../utils/http';
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    cdn_url:CDN_URL,
+    audioList:[],
+    api_base_url:API_BASE_URL
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    getAudiosList(){
+      http.get('/api/web/audio/list').then(res=>{
+        let {data} = res;
+        let target = [];
+        for(let [key,value] of Object.entries(data)) {
+            switch (key) {
+            case '1':
+              let obj1 = {};
+              obj1['title'] = '主厅';
+              obj1['list'] = value
+              target = [...target,obj1]
+              break;
+            case '2':
+              let obj2 = {};
+              obj2['title'] = '附厅';
+              obj2['list'] = value;
+              target = [...target,obj2]
+              break;
+            case '3':
+              let obj3 = {};
+              obj3['title'] = '序厅';
+              obj3['list'] = value;
+              target = [...target,obj3];
+              break;
+            default:
+              break
+          }
+        }
+        this.setData({
+          audioList:target
+        })
+      })
+    }
+  },
+  attached: function () {
+    //获取语音
+    this.getAudiosList();
+
+  },
+  detached: function () {}
+})

+ 6 - 0
components/big-pic/big-pic.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "audio-play": "../../components/audio-play/audio-play"
+  }
+}

+ 11 - 0
components/big-pic/big-pic.wxml

@@ -0,0 +1,11 @@
+<view class="content">
+  <view class="column" wx:for='{{audioList}}' wx:key="id">
+    <view class="title">
+      <text class="txt">{{item.title}}</text>
+      <image class="title-img" src="{{cdn_url}}/project/icon-bottom.png"></image>
+    </view>
+    <view class="con-list">
+      <audio-play wx:for='{{item.list}}' wx:key="id" audio="{{item}}"></audio-play>
+    </view>
+  </view>
+</view>

+ 46 - 0
components/big-pic/big-pic.wxss

@@ -0,0 +1,46 @@
+.content {
+  width: 100%;
+  height: 962rpx;
+  background: #FFFFFF;
+  box-shadow: 0rpx 6rpx 12rpx rgba(0, 0, 0, 0.65);
+  opacity: 1;
+  border-radius: 0 0 10rpx;
+  margin: 0 auto;
+  overflow: scroll;
+}
+
+.content {
+  box-sizing: border-box;
+}
+
+.content .main {
+  box-sizing: border-box;
+  width: 100%;
+}
+
+.content .column {
+  width: 100%;
+}
+
+.content .column .title {
+  width: 100%;
+  height: 100%;
+  padding: 30rpx 0;
+  font-size: 13px;
+  font-family: Microsoft YaHei;
+  font-weight: 400;
+  color: #38A2D7;
+  background: linear-gradient(180deg, #D4D4D4 0%, rgba(212, 212, 212, 0) 100%);
+  padding: 5% 4%;
+  box-sizing: border-box;
+}
+.content .column .title .title-img {
+  width:5%;
+  height:2vh;
+  margin-left:2%;
+}
+
+.content .column .con-list {
+  margin: 0 4%;
+  border-top: 1px solid #707070;
+}

+ 131 - 0
components/list-audio/list-audio.js

@@ -0,0 +1,131 @@
+// components/list-audio/list-audio.js
+import { CDN_URL,API_BASE_URL } from '../../config/index';
+var AllExample = [];
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    audio: {
+      type: Object,
+      value: {},
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    cdn_url:CDN_URL,
+    durationTime: '00:00',
+    currentTime: '00:00',
+    isPlay: false,
+    audioExample: null,
+    progress: 0,
+    allAudio: [],
+    targetUrl:API_BASE_URL+'/data/'
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    startMusic() {
+      // 全部暂停
+      AllExample.forEach(simpleAudio => {
+        simpleAudio.pause();
+      })
+      // this.data.audioExample.autoplay = true;
+      // this.data.audioExample.src = this.data.targetUrl+this.properties.audio['audio'];
+      // this.data.audioExample.play();
+      // this.setData({
+      //   isPlay: true
+      // })
+      this.data.audioExample.autoplay = true;
+      this.data.audioExample.src = this.data.targetUrl+this.properties.audio['audio'];
+      this.data.audioExample.play();
+      this.setData({
+        isPlay: true
+      })
+      this.loadProgress()
+    },
+    // 播放中
+    loadProgress() {
+      this.data.audioExample.onCanplay((e) => {
+        this.data.audioExample.duration; //必须写,不然获取不到。。。
+        var timer = setInterval(() => {
+          // 只要音频暂停了,定时器(假的)不执行
+          if (this.data.audioExample.paused) return;
+          var currentime = this.data.audioExample.currentTime;
+          var duration = this.data.audioExample.duration;
+          var currTimeStr = this.formatTime(currentime);
+          var duraTimeStr = this.formatTime(duration);
+          var progress = parseInt((this.data.audioExample.currentTime / this.data.audioExample.duration) * 100)
+          if (progress === 100 || isNaN(this.data.progress)) {
+            clearInterval(timer)
+            this.setData({
+              isPlay: false,
+              progress: 0,
+              currentTime: "00:00",
+            })
+          } else {
+            this.setData({
+              progress,
+              currentTime: currTimeStr,
+              durationTime: duraTimeStr
+            })
+          }
+        }, 1000);
+      });
+    },
+    stopMusic() {
+      this.data.audioExample.pause();
+      this.setData({
+        isPlay: false
+      })
+    },
+    formatTime: function (num) { //格式化时间格式
+      num = num.toFixed(0);
+      let second = (num % 60);
+      if (second < 10) second = '0' + second;
+      let min = Math.floor(num / 60);
+      if (min < 10) min = '0' + min;
+      return min + ":" + second;
+    }
+  },
+  attached: function () {
+    //组件显示初始化函数
+    var that = this;
+    this.data.audioExample = wx.createInnerAudioContext();
+    //所有组件实例都放在一个容器AllExample里
+    AllExample.push(this.data.audioExample);
+    this.data.audioExample.onPlay(function () {
+      console.log('播放中。。。')
+    })
+    this.data.audioExample.onTimeUpdate(function () {
+      console.log('监听进度。。')
+    })
+    //控制单个实例的isPlay
+    this.data.audioExample.onPause(function () {
+      that.setData({
+        isPlay: false
+      })
+    })
+    // 每个组件实例播放结束
+    this.data.audioExample.onEnded(() => {
+      this.setData({
+        currentTime: "00:00",
+        isPlay: false,
+        progress: 0,
+      })
+      console.log('播放结束了')
+    })
+    // 在组件实例进入页面节点树时执行
+  },
+  detached: function () {
+    // 在组件实例被从页面节点树移除时执行
+    // 卸载
+    this.data.audioExample.destroy();
+    AllExample = [];
+  }
+})

+ 4 - 0
components/list-audio/list-audio.json

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

+ 14 - 0
components/list-audio/list-audio.wxml

@@ -0,0 +1,14 @@
+<!--components/list-audio/list-audio.wxml-->
+<view class="list">
+  <view class="title">{{audio.name}}</view>
+  <view class="audio">
+    <image bindtap="startMusic" class="audio-img" src="{{cdn_url}}/project/list-start.png" style="display:{{isPlay?'none':'inline-block'}}"></image>
+    <image bindtap="stopMusic"  class="audio-img" src="{{cdn_url}}/project/list-stop.png" style="display:{{isPlay?'inline-block':'none'}}"></image>
+    <view class="free-MusicProgress">
+      <view class="before-progress">
+        <view class="progress" style="width:{{progress}}%;"></view>
+      </view>
+    </view>
+    <view class="durationTime">{{currentTime}}</view>
+  </view>
+</view>

+ 63 - 0
components/list-audio/list-audio.wxss

@@ -0,0 +1,63 @@
+/* components/list-audio/list-audio.wxss */
+.list {
+  position: relative;
+  margin: 5% 0;
+}
+
+.list .title {
+  font-size: 12px;
+  font-family: Microsoft YaHei;
+  font-weight: 400;
+  line-height: 16px;
+  color: #2F2414;
+  padding-left: 5%;
+  opacity: 1;
+}
+
+
+.audio {
+  position: relative;
+  margin: 2% 8%;
+}
+
+.audio .audio-img {
+  width:9%;
+  height:4vh;
+  display:inline-block;
+}
+
+.audio .free-MusicProgress {
+  position: absolute;
+  width: 80%;
+  height: 20%;
+  top: 32%;
+  opacity: 0.6;
+  display: inline;
+  margin: 0 2%;
+}
+
+.audio .free-MusicProgress .before-progress {
+  width: 100%;
+  height: 0.4vh;
+  position: absolute;
+  top: 50%;
+  background: #2F2414;
+  opacity: 0.5;
+}
+
+.audio .free-MusicProgress .progress {
+  height: 100%;
+  background: #38A2D7;
+}
+
+.list .durationTime {
+  position: absolute;
+  top: 20%;
+  right: -4%;
+  display: inline;
+  font-size: 10px;
+  font-family: Microsoft YaHei;
+  font-weight: 400;
+  color: #2F2414;
+  opacity: 0.67;
+}

+ 64 - 0
components/list/list.js

@@ -0,0 +1,64 @@
+// components/list/list.js
+import { CDN_URL,API_BASE_URL } from '../../config/index';
+import http from '../../utils/http';
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    cdn_url:CDN_URL,
+    audioList:[],
+    api_base_url:API_BASE_URL
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    getAudiosList(){
+      http.get('/api/web/audio/list').then(res=>{
+        let {data} = res;
+        let target = [];
+        for(let [key,value] of Object.entries(data)) {
+            switch (key) {
+            case '1':
+              let obj1 = {};
+              obj1['title'] = '主厅';
+              obj1['list'] = value
+              target = [...target,obj1]
+              break;
+            case '2':
+              let obj2 = {};
+              obj2['title'] = '附厅';
+              obj2['list'] = value;
+              target = [...target,obj2]
+              break;
+            case '3':
+              let obj3 = {};
+              obj3['title'] = '序厅';
+              obj3['list'] = value;
+              target = [...target,obj3];
+              break;
+            default:
+              break
+          }
+        }
+        this.setData({
+          audioList:target
+        })
+      })
+    }
+  },
+  attached: function () {
+    //获取语音
+    this.getAudiosList();
+
+  },
+})

+ 6 - 0
components/list/list.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "list-audio": "../../components/list-audio/list-audio"
+  }
+}

+ 12 - 0
components/list/list.wxml

@@ -0,0 +1,12 @@
+<!--components/list/list.wxml-->
+<view class="content">
+  <view class="column" wx:for='{{audioList}}' wx:key="id">
+    <view class="title">
+      <text class="txt">{{item.title}}</text>
+      <image class="title-img" src="{{cdn_url}}/project/icon-bottom.png"></image>
+    </view>
+    <view class="con-list">
+      <list-audio wx:for='{{item.list}}' wx:key="id" audio="{{item}}"></list-audio>
+    </view>
+  </view>
+</view>

+ 47 - 0
components/list/list.wxss

@@ -0,0 +1,47 @@
+/* components/list/list.wxss */
+.content {
+  width: 100%;
+  height: 962rpx;
+  background: #FFFFFF;
+  box-shadow: 0rpx 6rpx 12rpx rgba(0, 0, 0, 0.65);
+  opacity: 1;
+  border-radius: 0 0 10rpx;
+  margin: 0 auto;
+  overflow: scroll;
+}
+
+.content {
+  box-sizing: border-box;
+}
+
+.content .main {
+  box-sizing: border-box;
+  width: 100%;
+}
+
+.content .column {
+  width: 100%;
+}
+
+.content .column .title {
+  width: 100%;
+  height: 100%;
+  padding: 30rpx 0;
+  font-size: 13px;
+  font-family: Microsoft YaHei;
+  font-weight: 400;
+  color: #38A2D7;
+  background: linear-gradient(180deg, #D4D4D4 0%, rgba(212, 212, 212, 0) 100%);
+  padding: 5% 4%;
+  box-sizing: border-box;
+}
+.content .column .title .title-img {
+  width:5%;
+  height:2vh;
+  margin-left:2%;
+}
+
+.content .column .con-list {
+  margin: 0 4%;
+  border-top: 1px solid #707070;
+}

+ 341 - 0
components/map-sense/map-sense.js

@@ -0,0 +1,341 @@
+// components/map-sense.js
+
+const innerAudioContext = wx.createInnerAudioContext();
+import http from '../../utils/http';
+import {promisify,BeaconUtils} from '../../utils/util';
+let openBluetoothAdapter = promisify(wx.openBluetoothAdapter)
+let startBeaconDiscovery = promisify(wx.startBeaconDiscovery)
+
+import { CDN_URL, CONNECT_STATUS, STATUS_TEXT, API_BASE_URL } from '../../config/index';
+const STATUS_PIC = {
+  '0': 'default',
+  '1': 'loading',
+  '2': 'success',
+  '3': 'fail'
+};
+
+// 选择6档
+const TXPOWER = 0
+
+// 距离经验值(调试所得)
+const N = 14
+
+
+let AveLength = 10
+
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    status: "0",
+    cdn_url: CDN_URL,
+    connect_status: CONNECT_STATUS,
+    status_text: STATUS_TEXT,
+    status_pic: STATUS_PIC,
+    targetObj: {},
+    audio_address: {},
+    api_base_url: API_BASE_URL,
+    // 是否是扫码播放
+    isScanPlay: false,
+    cdn_url:CDN_URL,
+    classfiy:{}
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    openBluetooth(cb) {
+      openBluetoothAdapter().then(res=>{
+        cb(res)
+      },()=>{
+        wx.showToast({
+          title: '请打开蓝牙',
+          icon: 'error',
+          duration: 2000
+        })
+      })
+    },
+    toHandle() {
+      let aveArr = []
+
+      
+      this.openBluetooth(()=>{
+        startBeaconDiscovery({uuids: ['FDA50693-A4E2-4FB1-AFCF-C6EB07647825']}).then((res)=>{
+          
+          wx.onBeaconUpdate(data=>{
+            
+            //打印最新收集到的信号
+            console.log('beacon',data.beacons)
+            // 需要收集十组数据,索引号最大的组是最旧的
+            if (aveArr.length > AveLength) {
+              // aveArr[0] = data.beacons
+              //当超过十组时,应该将索引号大的组淘汰掉
+              aveArr.pop()
+            }
+
+            aveArr.unshift(data.beacons)   //在队列前面插入
+            
+            // console.log('aveArr',aveArr);
+            let all = []
+            aveArr.forEach(item => {
+                // console.log(aveArr.length,item)
+                all = all.concat(item)
+            });
+            // classfiy = {10003:[{},{},···],10002:[{},{},···],10001:[{},{},···]}
+            // console.log('all',all)
+            let classfiy = BeaconUtils.classification(all,'major')
+            let accuracyList = {}         // 存放处理后的accuary
+            // let dataArr = []    //记录各信标的信号收集数目
+            Object.keys(classfiy).forEach(key=>{
+              //每个major的AveLength个元素数组,元素为rssi
+              let arr = classfiy[key].map(item=>{
+                return item.rssi
+              })
+              //每个major的rssi平均值
+              // console.log(key,'rssi',arr)
+              let ave = BeaconUtils.arrayAverage(arr)
+              // console.log('ave',ave) 
+              //计算平均差
+              let meanDeviation = BeaconUtils.getMeanDeviation(arr,ave);
+              // console.log('meanDeviation',meanDeviation)
+              //计算各rssi的高斯模糊权重
+              let guassionArr = [];
+              for(let i = 0; i < arr.length; ++i){
+                // console.log('guassionArr ele',BeaconUtils.getOneGuassionArray(arr.length,i,meanDeviation))
+                // 返回的可能是空数组
+                guassionArr.push(BeaconUtils.getOneGuassionArray(arr.length,i,meanDeviation));
+              }
+              // console.log('guassionArr',guassionArr)
+              //计算高斯模糊后的rssi值
+              let rssiArr = [];                  //模糊后的rssi数组
+              for(let i = 0; i < arr.length; ++i){
+                let sum = 0;
+                for(let j = 0; j <arr.length; ++j){
+                  sum += guassionArr[i][j] * arr[j]
+                }
+                rssiArr.push(sum);
+                // console.log('sum',sum)   
+              }
+              // console.log('rssiArr',rssiArr);
+              //时间加权后求rssi平均值
+              let aveOnTime = BeaconUtils.arrayAverage(rssiArr.slice(Math.floor(0,1)).concat(rssiArr));
+              // console.log('aveOnTime',aveOnTime)
+              //测距
+            
+              classfiy[key].forEach(item=>{
+                  item.accuracy = BeaconUtils.calculateAccuracy(TXPOWER,aveOnTime,N)
+                  if(!accuracyList[key]){
+                    //如果还没有对应的“信标号”
+                    accuracyList[key] = [item.accuracy]
+                  }
+                  accuracyList[key].push(item.accuracy)
+                  // console.log('item',item)
+              })
+              
+              // dataArr.push(classfiy[key].length)
+              
+            })
+            // console.log(classfiy['10001'])
+            // console.log(classfiy['10002'])
+            // console.log(classfiy['10003'])
+            console.log(classfiy)
+            
+            // 筛选器,筛选出配对的一组信标
+            let aveAccuracys = new Map()                  //信标的平均距离,用于判断是否在范围内
+            let matchSigns = []                   //匹配的信号
+            let prologue = ['1000110002']         //序厅
+            let annexHall = ['1000310004']                    //附厅
+            let mainHall = ['1000510006']                     //主厅
+            let area = [prologue,annexHall,mainHall]
+            const R = 3.8                         //设定范围半径
+            Object.keys(accuracyList).forEach(key=>{
+              let aveAccuracy = BeaconUtils.arrayAverage(accuracyList[key])
+              if(aveAccuracy < R){
+                aveAccuracys.set(key,aveAccuracy)
+              }
+            })
+            //理想情况下只有两个信标的信号,刚好匹配为一组
+            //判断编号是奇数偶数
+            let result = ''
+            for(let key of aveAccuracys.keys()){
+              let num = parseInt(key)
+              if(num % 2 == 0){
+                if(aveAccuracys.has(String(num - 1))){
+                  num--
+                  result = key + num
+                  break
+                }
+              }else if(aveAccuracys.has(String(num + 1))){
+                num++
+                result = key + num
+                break
+              }
+              
+            }
+            console.log('result',result);
+            for(let i = 0; i < area.length; ++i){
+              if(area[i].includes(result)){
+                console.log(i)
+              }
+            }
+
+
+            // 计算方差
+            //计算平均数
+            // let x = BeaconUtils.arrayAverage(dataArr)
+            // let variance = BeaconUtils.getVariance(dataArr,x)
+            //打印收集到各种信号数目的方差
+            // console.log('txPower=',TXPOWER," 方差:",BeaconUtils.repeat('--',Math.floor(variance)))
+
+            // 定位
+            // 将三个信标构成直角三角形,其中10002号信标为直角,10001号为基点
+            // 根据挡位设定相邻顶点信标的距离,5档,设边长为10m
+            // let sideLength = 5
+            // let distance1,distance2,distance3      //分别对应游客到信标10001,1002,10003的距离
+            // distance1 = BeaconUtils.arrayAverage(accuracyList['10001'])
+            // distance2 = BeaconUtils.arrayAverage(accuracyList['10002'])
+            // distance3 = BeaconUtils.arrayAverage(accuracyList['10003'])
+            // let px = (Math.pow(2.8,2) - Math.pow(distance2,2) + Math.pow(distance1,2)) / (2.8 * 2)
+            // let py = (Math.pow(5.6,2) - Math.pow(distance3,2) + Math.pow(distance2,2)) / (5.6 * 2)
+            // console.log('px',px,' py',py)
+
+            this.setData({
+              classfiy,
+              status:'2'
+            })
+          })
+        }).catch(()=>{
+          wx.showToast({
+            title: '连接失败',
+            icon: 'error',
+            duration: 500
+          })
+        })
+      })
+    },
+    
+    stopBeaconDiscovery() {
+      var that = this;
+      console.log('这是取消连接')
+      wx.showToast({
+        title: '取消连接成功',
+        icon: 'error',
+        duration: 1000
+      })
+      wx.stopBeaconDiscovery({})
+      // 取消连接 停止播放音乐 目标对象置为{} 设置为第一次进入
+      innerAudioContext.stop();
+      that.setData({
+        status: '0',
+        targetObj: {}
+      })
+    },
+    // 扫一扫
+    toScanCode() {
+      var that = this;
+      that.setData({
+        isScanPlay: true
+      })
+      wx.scanCode({
+        success(res) {
+          console.log('success', res)
+          console.log('result', res['result'])
+          console.log('innerAudioContext', innerAudioContext)
+          that.setData({ targetObj: {} })
+          innerAudioContext.autoplay = true;
+          innerAudioContext.src = res['result']
+          innerAudioContext.loop = true;
+          innerAudioContext.play();
+        },
+        fail: function (res) {
+          console.log('fail innerAudioContext', innerAudioContext)
+          that.setData({
+            isScanPlay: false
+          })
+          innerAudioContext.play();
+          return;
+        },
+        complete(){
+          // console.log()
+          that.setData({
+            isScanPlay: false
+          })
+        }
+      })
+    },
+
+    getAudios() {
+      http.get('/api/web/getAudioIndex')
+        .then(res => {
+          let { data } = res,target = {};
+          data.forEach(item => {
+            switch (item.type) {
+              case 1:
+                target['10001'] = `${this.data.api_base_url}/data/${item.audio}`;
+                break;
+              case 2:
+                target['10002'] = `${this.data.api_base_url}/data/${item.audio}`;
+                break;
+              case 3:
+                target['10003'] = `${this.data.api_base_url}/data/${item.audio}`;
+                break;
+              default:
+                break
+            }
+          })
+          this.setData({
+            audio_address: target
+          })
+        })
+    }
+  },
+  lifetimes:{
+    attached: function () {
+      innerAudioContext.stop();
+      //获取语音
+      this.getAudios();
+
+      var that = this;
+      // wx.onAccelerometerChange(function (e) {
+      //   console.log('手机咚咚咚给')
+      //   if (Math.abs(e.x) > 1.1 && Math.abs(e.y) > 1.1) {
+      //     //         wx.showToast({ title: "摇一摇" })
+      //   } else if (Math.abs(e.x) > 0.07 && Math.abs(e.y) > 0.02 && that.data.status === '2') {
+      //     // 扫一扫播放的话 移动无效
+      //     if (that.data.isScanPlay) return;
+          
+      //     that.startBeaconDiscovery()
+      //   } else {
+      //     //         wx.showToast({ title: "静止" })
+      //   }
+      // }),
+        innerAudioContext.onEnded(() => {
+          console.log('播放结束了')
+          if (this.data.isScanPlay) {
+            innerAudioContext.stop()
+            this.setData({ isScanPlay: false })
+            this.targetObj = {}
+            console.log('innerAudioContext', innerAudioContext)
+            if (this.data.status == 2) {
+              console.log(2222222222222222)
+              this.startBeaconDiscovery()
+            }
+          }
+        })
+    },
+    detached: function () {
+      innerAudioContext.stop();
+      innerAudioContext.destroy();
+      wx.stopBeaconDiscovery({})
+      this.setData({status:"0"})
+    }
+  }
+})

+ 4 - 0
components/map-sense/map-sense.json

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

+ 45 - 0
components/map-sense/map-sense.wxml

@@ -0,0 +1,45 @@
+<!--components/map-sense.wxml-->
+<view class="content">
+  <view class="top">
+    <view class="top-bottom">
+      <image class="top-bottom-img" src="{{cdn_url}}/project/big-bg-{{targetObj.major === 10001? 'brown':'blue'}}.png">
+      </image>
+      <view class="{{targetObj.major === 10001 ?'light-sound':'small-sound'}}">
+        <image class="sound-img" src="{{cdn_url}}/project/sound.png"></image>
+      </view>
+    </view>
+    <view class="right-bottom">
+      <image class="right-bottom-img" src="{{cdn_url}}/project/right-{{targetObj.major === 10002? 'brown':'blue'}}.png">
+      </image>
+      <view class="{{targetObj.major === 10002 ?'light-sound':'small-sound'}}">
+        <image class="sound-img" src="{{cdn_url}}/project/sound.png"></image>
+      </view>
+    </view>
+    <view class="center-bottom">
+      <image class="center-bottom-img" src="{{cdn_url}}/project/xt-{{targetObj.major === 10003? 'brown':'blue'}}.png">
+      </image>
+      <view class="{{targetObj.major === 10003 ?'light-sound':'small-sound'}}">
+        <image class="sound-img" src="{{cdn_url}}/project/sound.png"></image>
+      </view>
+      <view class="txt">序厅</view>
+    </view>
+  </view>
+  <view class="center">
+    <image class="center-img" src="{{cdn_url}}/project/{{status_pic[status]}}.png"></image>
+    <view class="promptText">
+      {{connect_status[status]}}
+    </view>
+  </view>
+  <view class="text">
+    蓝牙成功连接后,走到相应区域则会播放不同的音频
+    注:请确保蓝牙保持打开状态。
+  </view>
+  <view style="text-align:center;">
+    <view class="bottom">
+      <view class="txt" bindtap="toScanCode">扫一扫</view>
+    </view>
+    <view class="bottom">
+      <view class="txt" bindtap="toHandle">{{status_text[status]}}</view>
+    </view>
+  </view>
+</view>

+ 212 - 0
components/map-sense/map-sense.wxss

@@ -0,0 +1,212 @@
+/* components/map-sense.wxss */
+.content {
+  width: 100%;
+  height: 962rpx;
+  background: #FFFFFF;
+  box-shadow: 0rpx 6rpx 12rpx rgba(0, 0, 0, 0.65);
+  opacity: 1;
+  border-radius: 0 0 10rpx 10rpx;
+  margin: 0 auto;
+  overflow: scroll;
+}
+
+.content {
+  box-sizing: border-box;
+  padding: 24rpx 22rpx;
+}
+
+.content .top {
+  width: 100%;
+  height: 342rpx;
+  position: relative;
+}
+
+.content .top .top-bottom .top-bottom-img,
+.content .top .right-bottom .right-bottom-img,
+.content .top .center-bottom .center-bottom-img,
+.content .center .center-img {
+  width: 100%;
+  height: 100%;
+}
+
+.content .top .top-bottom {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.content .top .right-bottom {
+  width: 44%;
+  height: 43%;
+  position: absolute;
+  right: 1%;
+  bottom: 2%;
+}
+
+.content .top .center-bottom {
+  width: 35%;
+  height: 52%;
+  position: absolute;
+  text-align: center;
+  bottom: 2%;
+  left: 22%;
+}
+
+.content .top .center-bottom .small-sound {
+  width: 44rpx;
+  height: 44rpx;
+  background: #B4DCF0;
+  border-radius: 50%;
+  position: absolute;
+  top: 30%;
+  left: 38%;
+}
+
+.content .top .center-bottom .small-sound .sound-img {
+  width: 22rpx;
+  height: 20rpx;
+  display: inline-block;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%,-50%);
+}
+
+.content .top .light-sound {
+  width: 60rpx;
+  height: 60rpx;
+  display: inline-block;
+  background: radial-gradient(circle, #1C2E4C 0%, #38A2D7 68%, #FFFFFF 100%);
+  box-shadow: 0px 2rpx 22rpx rgba(255, 255, 255, 0.8);
+  border-radius: 50%;
+  opacity: 1;
+  position: absolute;
+  top: 35%;
+  right: 35%;
+  text-align: center;
+}
+
+.content .top .light-sound .sound-img {
+  width: 28rpx;
+  height: 26rpx;
+  display: inline-block;
+}
+
+.content .top .top-bottom .light-sound {
+  width: 60rpx;
+  height: 60rpx;
+  display: inline-block;
+  background: radial-gradient(circle, #1C2E4C 0%, #38A2D7 68%, #FFFFFF 100%);
+  box-shadow: 0px 2rpx 22rpx rgba(255, 255, 255, 0.8);
+  border-radius: 50%;
+  opacity: 1;
+  position: absolute;
+  top: 17%;
+  right: 56%;
+  text-align: center;
+}
+
+.content .top .center-bottom .txt {
+  display: inline;
+  width: 48rpx;
+  height: 32rpx;
+  font-size: 24rpx;
+  font-weight: 400;
+  line-height: 200rpx;
+  color: #2F2414;
+  opacity: 0.8;
+  position: absolute;
+  top: 20%;
+  right: 40%;
+}
+
+.content .center {
+  width: 190rpx;
+  height: 190rpx;
+  margin: 10% auto;
+}
+
+.content .center .promptText {
+  text-align: center;
+  font-size: 19px;
+  font-weight: 300;
+  line-height: 25px;
+  color: rgba(47, 36, 20, 0.8);
+}
+
+.content .text {
+  width: 460rpx;
+  height: 60rpx;
+  font-size: 20rpx;
+  font-weight: 400;
+  line-height: 32rpx;
+  color: #2F2414;
+  opacity: 0.45;
+  text-align: center;
+  margin: 12% auto;
+}
+
+.content .bottom {
+  width: 378rpx;
+  height: 76rpx;
+  background: #FFFFFF;
+  border: 2rpx solid #38A2D7;
+  opacity: 1;
+  border-radius: 19px;
+  text-align: center;
+  display: inline;
+  padding: 2% 10%;
+  margin: 0 1%;
+}
+
+.content .bottom .txt {
+  display: inline;
+  width: 152rpx;
+  height: 76rpx;
+  font-size: 38rpx;
+  font-weight: 300;
+  line-height: 76rpx;
+  color: #38A2D7;
+  opacity: 1;
+
+}
+
+.content .top .small-sound {
+  width: 44rpx;
+  height: 44rpx;
+  background: #B4DCF0;
+  border-radius: 50%;
+  position: absolute;
+  top: 19%;
+  left: 36%;
+}
+
+.content .top .small-sound .sound-img {
+  width: 22rpx;
+  height: 20rpx;
+  display: inline-block;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%,-50%);
+}
+
+.content .top .right-bottom .small-sound {
+  width: 44rpx;
+  height: 44rpx;
+  background: #B4DCF0;
+  border-radius: 50%;
+  position: absolute;
+  top: 60rpx;
+  left: 100rpx;
+}
+
+.content .top .right-bottom .small-sound .sound-img {
+  width: 22rpx;
+  height: 20rpx;
+  display: inline-block;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%,-50%);
+}

+ 15 - 0
config/index.js

@@ -0,0 +1,15 @@
+export const API_BASE_URL = "http://192.168.0.44:8110";
+export const CDN_URL = 'https://4d-tjw.oss-cn-shenzhen.aliyuncs.com';
+export const TYPE = {};
+export const CONNECT_STATUS = {
+  '0':'未连接',
+  '1':'正在连接',
+  '2':'已连接',
+  '3':'连接失败'
+}
+export const STATUS_TEXT = {
+  '0':'确认连接',
+  '1':'取消连接',
+  '2':'取消连接',
+  '3':'重新连接'
+}

+ 19 - 0
pages/index/index.js

@@ -0,0 +1,19 @@
+// index.js
+// 获取应用实例
+const app = getApp();
+
+Page({
+  data: {
+    type: 'map'
+  },
+  onLoad() {
+  },
+  switchType(e) {
+    let type = e.target.dataset && e.target.dataset.type || 'map';
+    this.setData({
+      type
+    })
+  },
+  onUnload() {
+  }
+})

+ 7 - 0
pages/index/index.json

@@ -0,0 +1,7 @@
+{
+  "usingComponents": {
+    "map-sense": "../../components/map-sense/map-sense",
+    "big-pic": "../../components/big-pic/big-pic",
+    "list": "../../components/list/list"
+  }
+}

+ 16 - 0
pages/index/index.wxml

@@ -0,0 +1,16 @@
+<!--index.wxml-->
+<view class="container">
+  <view class="con">
+    <view class="nav">
+      <view class="list {{type === 'list'? 'cur':''}}" data-type='list' bindtap="switchType">列表</view>
+      <view class="line"></view>
+      <view class="bigPic {{type === 'big-pic'? 'cur':''}}" data-type='big-pic' bindtap="switchType">大图</view>
+      <view class="line"></view>
+      <view class="mail {{type === 'map'? 'cur':''}}" data-type='map' bindtap="switchType">地图</view>
+    </view>
+    <list wx:if="{{type === 'list'}}"></list>
+    <big-pic wx:if="{{type === 'big-pic'}}"></big-pic>
+    <map-sense wx:if="{{type === 'map'}}"></map-sense>
+  </view>
+    
+</view>

+ 52 - 0
pages/index/index.wxss

@@ -0,0 +1,52 @@
+/**index.wxss**/
+.container {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+
+.con{
+  width: 80%;
+  height: 100%;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%,-50%);
+}
+
+.nav {
+  width: 100%;
+  height: 82rpx;
+  display: flex;
+  justify-content: center;
+  background: #FFFFFF;
+  overflow: hidden;
+  box-shadow: 0rpx 6rpx 12rpx rgba(0, 0, 0, 0.65);
+  margin-top: 10%;
+  border-radius: 10rpx 10rpx 0rpx 0rpx;
+}
+
+.line{
+  width: 1rpx;
+  height: 100%;
+  background: #1C2E4C;
+}
+
+
+
+.list,
+.bigPic,
+.mail {
+  box-sizing: border-box;
+  width: 100%;
+  font-size: 28rpx;
+  font-weight: 400;
+  line-height: 82rpx;
+  text-align: center;
+  color: black;
+}
+
+.nav .cur {
+  background: linear-gradient(to top, #1C2E4C 0%, #38A2D7 100%);
+  color: #FFFFFF;
+}

+ 66 - 0
project.config.json

@@ -0,0 +1,66 @@
+{
+  "description": "项目配置文件",
+  "packOptions": {
+    "ignore": []
+  },
+  "setting": {
+    "urlCheck": false,
+    "es6": true,
+    "enhance": false,
+    "postcss": true,
+    "minified": true,
+    "newFeature": false,
+    "coverView": true,
+    "nodeModules": false,
+    "autoAudits": false,
+    "showShadowRootInWxmlPanel": true,
+    "scopeDataCheck": false,
+    "uglifyFileName": false,
+    "checkInvalidKey": true,
+    "checkSiteMap": true,
+    "uploadWithSourceMap": true,
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "useMultiFrameRuntime": true,
+    "useApiHook": true,
+    "useApiHostProcess": false
+  },
+  "compileType": "miniprogram",
+  "libVersion": "2.17.0",
+  "appid": "wx88c73e2a2de3c87c",
+  "projectname": "macaoApplets",
+  "debugOptions": {
+    "hidedInDevtools": []
+  },
+  "scripts": {},
+  "staticServerOptions": {
+    "baseURL": "",
+    "servePath": ""
+  },
+  "isGameTourist": false,
+  "simulatorType": "wechat",
+  "simulatorPluginLibVersion": {},
+  "condition": {
+    "search": {
+      "list": []
+    },
+    "conversation": {
+      "list": []
+    },
+    "game": {
+      "list": []
+    },
+    "plugin": {
+      "list": []
+    },
+    "gamePlugin": {
+      "list": []
+    },
+    "miniprogram": {
+      "list": []
+    }
+  }
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 43 - 0
utils/http.js

@@ -0,0 +1,43 @@
+import { API_BASE_URL } from '../config/index';
+const BASE_URL = `${API_BASE_URL}`;
+function request (url, options) {
+  return new Promise((resolve, reject) => {
+    wx.request(Object.assign({
+      url: url.indexOf('://') == -1 ? BASE_URL + url : url,
+      method: options.method,
+      data: options.data,
+      header: options.header || {},
+      success (res) {
+        if (res.data.code == 0 || res.data.code == 200 || res.data.errno === 0) {
+          resolve(res.data)
+        } else {
+          reject(res)
+        }
+      },
+      fail (err) {
+        console.log(err, '请求失败')
+        reject(err)
+      }
+    }, options))
+    
+    setTimeout(() => reject('time out'), 5000)
+  })
+}
+
+function get (url, data, options = {}) {
+  options.method = 'GET'
+  options.data = data
+  return request(url, options)
+}
+
+function post (url, data = {}, options = {}) {
+  options.method = 'POST'
+  options.data = Object.assign({}, data)
+  return request(url, options)
+}
+
+export default {
+  request,
+  get,
+  post
+}

+ 123 - 0
utils/util.js

@@ -0,0 +1,123 @@
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : `0${n}`
+}
+
+const promisify =  (api) => {
+  return (options, ...params) => {
+      return new Promise((resolve, reject) => {
+          api(Object.assign({}, options, { success: resolve, fail: reject }), ...params);
+      });
+  }
+}
+
+const BeaconUtils = {
+  
+// 计算距离
+calculateAccuracy:function(txPower, rssi, n) {
+  // return (0.89976) * Math.pow(rssi / txPower, 7.7095) + 0.111
+
+  return Math.pow(10, Math.abs(rssi - txPower) / (10 * n))
+},
+
+//求数组平均值
+arrayAverage:function (arr) {
+   let tmp = arr.reduce((acc, val) => acc + val, 0) / arr.length
+   return tmp
+},
+
+//数组分类:按照关键词key
+//data = {1003:[{},{},···],1002:[{},{},···],1001:[{},{},···]}
+classification:function (list,key) {
+  let data = {}
+  for(var i = 0; i < list.length; i++) {
+      if(!data[list[i][key]]) {
+          var arr = [];
+          arr.push(list[i]);
+          data[list[i][key]] = arr;
+      }else {
+          data[list[i][key]].push(list[i])
+      }
+  }
+
+  return data
+},
+
+//求一维队列某点的高斯模糊权重 @param(队列长度,目标位置, 平均差)
+getOneGuassionArray: function(size, kerR, sigma) {
+    // if (size % 2 > 0) {
+    //   size -= 1
+    // }
+    // if (!size) {
+    //   return []
+    // }
+    if (kerR > size-1){
+      return []
+    }
+    let sum = 0;
+    let arr = new Array(size);
+    //进来的列表的元素可能是一样的,防止平均差为0的情况
+    if(sigma == 0){
+      let weight = 1 / size;
+      return arr.fill(weight)
+    }
+    for (let i = 0; i < size; i++) {
+      arr[i] = Math.exp(-((i - kerR) * (i - kerR)) / (2 * sigma * sigma)) / (sigma * Math.pow(2*Math.PI,0.5));
+      sum += arr[i];
+    }
+
+    return arr.map(e => e / sum);
+  },
+
+  //计算数组平均差
+  //list:rssi数组
+  //average:rssi平均值
+  getMeanDeviation: function(list,average){
+    let length = list.length;
+    //数组的每一个数都要减去一个平均数,结果取绝对值
+    let arr = list.map(num=>Math.abs(num-average))
+    return arr.reduce((prev,curr)=>prev+curr) / length;
+  },
+
+  //计算数组方差
+  // list:待计算数组
+  // average:数组元素平均值
+  getVariance:function(list,average){
+    let sum = 0;
+    for(let element of list){
+      sum += Math.pow((element - average),2)
+    }
+    return sum / list.length
+  },
+
+  //js实现字符串重复的repeat函数
+  repeat:function(src, n) {
+    return (new Array(n + 1)).join(src);
+  },
+
+  //计算椭圆长度
+  //R是预设的有效范围半径
+  //L是椭圆长度
+  getLR:function(R,L){
+    let d = L - R 
+    let r = (R - d) / 2
+    let h = r + d
+    return 2*Math.pow((R*R - h*h),0.5)
+  }
+}
+
+module.exports = {
+  formatTime,
+  promisify,
+  BeaconUtils
+}