// nova-werun/pages/home/sport/sport-start/index.js const Parse = getApp().Parse; const company = getApp().globalData.company; const uid = Parse.User.current()?.id let request = require('../../../../../utils/request') const dateF = require("../../../../../utils/date") let { statusBarHeight } = wx.getSystemInfoSync() let custom = wx.getMenuButtonBoundingClientRect() let customHeight = custom.height Page({ /** * 页面的初始数据 */ data: { navheight: 0, //导航高度px isStop: false, //计时是否暂停 longitude: 0, //经度 latitude: 0, //纬度 markers: [], //地图标记点 showPermissions: true, //权限弹框-开 journey: 0, //运动的路程-米 calorie: 0, //卡路里-千卡 pace: 0, //配速-min/km timer: 0, //已记的时长-秒 status: 'noStart', //运动状态 'noStart'未开始,'inSports'运动中,'paused'暂停 percentage: '', //长按结束运动百分比 endHaloInterval: null, //光环计时器 endHaloStartDate: null, //光环开始时间戳 endHaloEndDate: null, //光环结束时间戳 isRefershLaLo: true, //是否刷新经纬度 stage: 'start', //运动过程 start,progress,end actData: null, //运动数据 formattedTime: '00:00:00', // 用于存储格式化后的时间 step: 0, //运动步数 type: 'walk', //运动类型-步行 actDataList: [], //未运动结束数据 isShowActDataList: false, //是否展示未运动结束的数据 checkActDataId: null, //点击的未结束运动 }, /** * 生命周期函数--监听页面加载 */ onLoad: async function (options) { wx.offLocationChange() this.setData({ navheight: customHeight + statusBarHeight + 5, type: options.type || 'walk', //运动方式 run/walk a_reg: options.a_reg || '', //所属报名记录 ActivityRegister.id }) console.log(options) this.getActivityData() this.beforeUnload() }, /**获取运动数据 */ async getActivityData() { let { a_reg, type } = this.data let query = new Parse.Query('ActivityData') query.equalTo('company', company) query.equalTo('user', uid) query.equalTo('type', type) query.notEqualTo('isDeleted', true) query.descending('createdAt') if (a_reg) { //如果传递了报名记录-直接进行这个运动 console.log('如果传递了报名记录-直接进行这个运动') let actData query.equalTo('actRegister', a_reg) actData = await query.first() if (actData?.get('status') == 'end') { wx.showToast({ title: '已结算成绩', icon: 'none' }) wx.navigateBack({ delta: 1 }) return } if (!actData?.id) { console.log('未查询出运动数据') let regQuery = new Parse.Query('ActivityRegister') let act_reg = await regQuery.get(a_reg) await this.creatActDateFun(act_reg) return } else { console.log('运动未结束') wx.showToast({ title: '运动未结束', icon: 'none' }) await this.assignmentFun(actData?.id) this.setData({ actData }) } } else { //未传递-显示当前类型(run/walk)所有未结束的运动 console.log('/未传递-显示当前类型所有未结束的运动') let actDataCount actDataCount = await query.count() if (actDataCount <= 0 || !actDataCount) { await this.creatActDateFun() return } else { let sql = `SELECT act_d."objectId",act_d."startDate",act_d."createdAt",act."title",act_r."booking",act."endDate" AS act_end FROM "ActivityData" act_d LEFT JOIN "Activity" act ON act."objectId" = act_d."activity" LEFT JOIN "ActivityRegister" act_r ON act_r."objectId" = act_d."actRegister" AND act_r."isDeleted" IS NOT TRUE WHERE act_d."company"='${company}' AND act_d."user"='${uid}' AND act_d."status" NOT IN ('end') AND act_d."type"='${type}' AND act_d."isDeleted" IS NOT TRUE ORDER BY act_d."startDate" DESC LIMIT 20` let actDataList = await request.customSQL(sql) || [] if (actDataList?.length <= 0 || !actDataList) { await this.creatActDateFun() return } else { console.log(actDataList) let actDataList2 = actDataList?.map(item => { let obj = item if (item.startDate || item.createdAt) obj.sport_startDate = dateF.formatTime("YYYY.mm.dd HH:MM", item.startDate || item.createdAt) if (item.booking?.from?.iso) obj.booking.fromDate = dateF.formatTime("YYYY.mm.dd HH:MM", item.booking.from.iso) /**该运动是否可继续 */ let isAttend = false let now = new Date() if (item.act_end) { let end = new Date(item.act_end) isAttend = now < end obj.actEndDate = dateF.formatTime("YYYY.mm.dd HH:MM", item.act_end) console.log('end', end, isAttend) } if (item.booking?.to?.iso) { let to = new Date(item.booking?.to?.iso) isAttend = now < to obj.booking.toDate = dateF.formatTime("YYYY.mm.dd HH:MM", item.booking.to.iso) console.log('to', to, isAttend) } obj.isAttend = isAttend return obj }) this.setData({ actDataList:actDataList2, isShowActDataList:true, }) console.log(actDataList2) } } } }, /**关闭未结束运动弹框 */ closeActDataList() { this.creatActDateFun() this.setData({ isShowActDataList: false }) }, /**点击未结束运动 */ async checkActEndDate(e) { let { actDataList, type } = this.data console.log(e) let { index } = e.currentTarget.dataset let act_end_data_id = actDataList[index].objectId let log_query = new Parse.Query('ActivityRunLog') log_query.equalTo('company', company) log_query.equalTo('user', uid) log_query.equalTo('actData', act_end_data_id) log_query.equalTo('type', type) log_query.notEqualTo('isDeleted', true) log_query.descending('createdAt') let log = await log_query.first() let that = this console.log(actDataList[index]) if(actDataList[index].isAttend||!actDataList[index].title){ wx.showModal({ title: `${actDataList[index].title||'日常运动'}`, content: `${actDataList[index].sport_startDate||'未知时间'}开始运动,已运动 ${log?.get('distance')||0} m, ${log?.get('steps')||0} 步,消耗 ${log?.get('burnCalories')||0} 千卡,是否继续进行?`, success:async(res)=> { if (res.confirm) { console.log('用户点击确定') await that.assignmentFun(act_end_data_id) that.setData({ isShowActDataList: false }) } else if (res.cancel) { console.log('用户点击取消') } } }) }else if(!actDataList[index].isAttend){ wx.showModal({ title: `${actDataList[index].title||'日常运动'}`, content: `${actDataList[index].sport_startDate||'未知时间'}开始运动,已运动 ${log?.get('distance')||0} m, ${log?.get('steps')||0} 步,消耗 ${log?.get('burnCalories')||0} 千卡。当前报名记录已过期,可查看其他报名或其他活动。`, showCancel:false, confirmText:'结束运动', success:async(res)=> { if (res.confirm) { await that.setEndActDateFun(act_end_data_id) actDataList.splice(index,1) that.setData({actDataList}) wx.showToast({ title: '已结束', icon:'none' }) } } }) } }, /**继续未结束运动 */ async goOnactEndDate() { let { checkActDataId } = this.data await this.assignmentFun(checkActDataId) this.setData({ isShowActDataList: false }) }, /** * 新建运动数据 * @param {*} act_reg 活动报名记录 Parse.Object */ async creatActDateFun(act_reg) { let { type } = this.data let ActivityData = Parse.Object.extend('ActivityData') let actData = new ActivityData() if (act_reg?.id && act_reg?.get('activity')?.id) { actData.set('activity', { __type: 'Pointer', className: 'Activity', objectId: act_reg?.get('activity')?.id }) actData.set('actRegister', { __type: 'Pointer', className: 'ActivityRegister', objectId: act_reg?.id }) } actData.set('company', { __type: 'Pointer', className: 'Company', objectId: company }) actData.set('user', { __type: 'Pointer', className: '_User', objectId: uid }) actData.set('type', type) actData.set('startDate', new Date()) console.log('未查询出显示这个', actData) await this.setData({ actData }) }, /** * 运动数据赋值操作 * @param {*} actDataId 运动数据id */ async assignmentFun(actDataId) { let { type } = this.data let log_query = new Parse.Query('ActivityRunLog') log_query.equalTo('company', company) log_query.equalTo('user', uid) log_query.equalTo('actData', actDataId) log_query.equalTo('type', type) log_query.notEqualTo('isDeleted', true) log_query.descending('createdAt') let log = await log_query.first() let data_query = new Parse.Query('ActivityData') let actData = await data_query.get(actDataId) this.setData({actData}) this.setData({ stage: 'progress', status: 'paused', isRefershLaLo: true, timer: log?.get('sportDate') || 0, pace: log?.get('matchSpeed') || 0, calorie: log?.get('burnCalories') || 0, journey: log?.get('distance') || 0, step: log?.get('steps' || 0), formattedTime: this.formatTime(log?.get('sportDate') || 0), }) }, /** * 结束运动 * @param {*} actDataId 运动数据id */ async setEndActDateFun(actDataId) { let { type } = this.data let log_query = new Parse.Query('ActivityRunLog') log_query.equalTo('company', company) log_query.equalTo('user', uid) log_query.equalTo('actData', actDataId) log_query.equalTo('type', type) log_query.notEqualTo('isDeleted', true) log_query.descending('createdAt') let log = await log_query.first() let data_query = new Parse.Query('ActivityData') let data = await data_query.get(actDataId) log?.get('steps') && data.set('steps', log?.get('steps') || 0) log?.get('distance') && data.set('distance', log?.get('distance') || 0) log?.get('matchSpeed') && data.set('matchSpeed', log?.get('matchSpeed') || 0) log?.get('sportDate') && data.set('sportDate', log?.get('sportDate') || 0) log?.get('burnCalories') && data.set('burnCalories', log?.get('burnCalories') || 0) data.set('endDate', log?.get('createdAt') || new Date()) data.set('status', 'end') await data.save() if (log?.id) { log.set('stage', 'end') await log.save() } }, /**监听页面退出事件 */ beforeUnload() { wx.enableAlertBeforeUnload({ //获取不到"确认/取消"按钮事件 message: '未开始运动、暂停运动中以及运动结束请忽略本提示。若当前正在运动,退出本页面将在微信后台记录。关闭小程序、关闭小程序运行权限、长时间未进入小程序、手机退出微信等操作都将暂停记录。', }) }, /**检查用户授权状况 */ examineAuthorization() { let that = this wx.getSetting({ success(res) { console.log(res) if (!res.authSetting['scope.userLocation'] || !res.authSetting['scope.userLocationBackground']) { that.openPermissions() } else { that.closePermissions() } } }) }, /**权限弹框-开 */ openPermissions() { this.setData({ showPermissions: true }) }, /**权限弹框-关 */ async closePermissions() { await this.setData({ showPermissions: false }) let { type } = this.data let tip = '运动' switch (type) { case 'run': tip = '跑步' break; case 'walk': tip = '步行' break; default: break; } wx.showToast({ title: `开启${tip}之旅吧~`, icon: 'none' }) this.refersh() }, /**打开微信权限设置页面 */ openWxSetting() { wx.openSetting({ success(res) { console.log(res.authSetting) }, fail(err) { console.log(err) } }) }, async refersh() { this.startSports() }, /**刷新初始位置 */ startSports() { let that = this wx.getLocation({ type: 'gcj02', success: async (res) => { console.log(res) let { longitude, latitude, } = res let markers = [{ iconPath: "https://file-cloud.fmode.cn/13WZ0W7u3l/20240724/7ebg0k104325941.png", width: 20, height: 20, longitude: longitude, latitude: latitude, id: 0, }] await that.setData({ longitude, latitude, markers }) }, fail: (err) => { console.error(err); wx.showToast({ title: '获取位置失败', icon: 'none' }); } }); }, /** * 位置监听开启 */ async setTime() { await this.setData({ status: 'inSports', isRefershLaLo: true, }) let that = this wx.offLocationChange() setTimeout(() => { that.monitorLocationChange() console.log('执行经纬度获取') }, 1000); }, /**开始运动 */ async startInterval() { await this.setData({ longitude: 0, //经度 latitude: 0, //纬度 markers: [], //地图标记点 journey: 0, //运动的路程-米 step: 0, calorie: 0, //卡路里-千卡 pace: 0, //配速-min/km interval: null, //计时器 timer: 0, //已记的时长-秒 formattedTime: '00:00:00', // 用于存储格式化后的时间 status: 'inSports', //运动中 stage: 'start', }) //===================这里在getActivityData方法设置===================== // let { // type,actData // } = this.data // if(!actData){ // let ActivityData = Parse.Object.extend('ActivityData') // actData = new ActivityData() // actData.set('company', { // __type: 'Pointer', // className: 'Company', // objectId: company // }) // actData.set('user', { // __type: 'Pointer', // className: '_User', // objectId: uid // }) // actData.set('startDate', new Date()) // actData.set('type', type) // } // this.setData({ // actData // }) setTimeout(() => { this.setTime() }, 1000); }, /** 暂停运动*/ async pausedInterval() { wx.offLocationChange() this.setData({ status: 'paused', }) let { actData, timer, pace, calorie, journey, step } = this.data console.log(actData) actData.set('status', 'paused') actData.set('distance', parseFloat(journey)) actData.set('steps', parseInt(step)) actData.set('sportDate', parseInt(timer)) actData.set('matchSpeed', parseFloat(pace)) actData.set('burnCalories', parseFloat(calorie)) await actData.save() console.log('暂停') }, /**继续运动 */ goOn() { this.setTime() }, /**长按结束-起 */ onStart() { let endHaloStartDate = new Date() this.setData({ endHaloStartDate }) let { endHaloInterval } = this.data endHaloInterval = setInterval(() => { let now = new Date() let elapsedTime = now - endHaloStartDate let percentage = Math.min((elapsedTime / 4000) * 100, 100); this.setData({ percentage: `conic-gradient(from 0deg, #015EEA ${percentage}%, white 0%)`, }); }, 40); this.setData({ endHaloInterval }) }, /**长按结束-终 */ async onEnd() { let { endHaloStartDate, endHaloInterval, actData, } = this.data let endHaloEndDate = new Date() let a = endHaloEndDate - endHaloStartDate if (a >= 4000) { //长按4秒 clearInterval(endHaloInterval) wx.offLocationChange() wx.stopLocationUpdate({ success: (res) => { console.log('后台定位已停止'); }, fail: (res) => { console.error('停止后台定位失败', res); } }); await this.setData({ status: 'noStart', endHaloStartDate: null, endHaloStartDate: null, percentage: '', endHaloInterval: null, stage: 'end' }) await this.setActivityRunLog() let { actData, timer, pace, calorie, journey, step } = this.data console.log(actData) actData.set('status', 'end') actData.set('endDate', new Date()) actData.set('distance', parseFloat(journey)) actData.set('sportDate', parseInt(timer)) actData.set('steps', parseInt(step)) actData.set('matchSpeed', parseFloat(pace)) actData.set('burnCalories', parseFloat(calorie)) await actData.save() wx.showToast({ title: '已结束本次运动', icon: 'none' }) } else { clearInterval(endHaloInterval) this.setData({ endHaloStartDate: null, endHaloStartDate: null, percentage: '', endHaloInterval: null, }) } }, /**监听位置变化 */ monitorLocationChange() { let that = this wx.startLocationUpdateBackground({ success: (res) => { wx.onLocationChange(async (change_res) => { console.log(change_res.longitude, change_res.latitude) let { longitude, //经度 latitude, //纬度 journey, //路程-m calorie, //卡路里-千卡 pace, //配速-min/km timer, //计时 isRefershLaLo, } = that.data timer = timer + 1 || 1 let formattedTime = that.formatTime(parseInt(timer)) if (isRefershLaLo) { longitude = change_res.longitude latitude = change_res.latitude let { actData } = that.data console.log(actData) actData.set('status', 'inSports') await actData.save() that.setData({ isRefershLaLo: false, actData }) } let distance = that.haversine(latitude, longitude, change_res.latitude, change_res.longitude)?.toFixed(2) journey = (parseFloat(journey || 0) + parseFloat(distance || 0))?.toFixed(2) calorie = that.getCalorie(50, journey) //体重50kg pace = distance ? (distance * 6) / 100 : 0 let step = parseInt(parseInt(journey) / 0.6) await that.setData({ formattedTime, step, timer, journey, longitude: change_res.longitude, latitude: change_res.latitude, calorie, pace: parseFloat(pace)?.toFixed(2), }) let time = timer % 5 if (time === 0) { await that.setActivityRunLog() that.setData({ stage: 'progress' }) // console.log('执行保存数据') } }); }, fail: (res) => { this.openPermissions() } }); }, /**微信步数获取-弃用-使用0.6米/步 */ async getWeRun() { wx.login({ success: (res) => { console.log(res) wx.request({ url: `https://api.weixin.qq.com/sns/jscode2session?appid=wxe6ecc0193c09696c&secret=0ee1172c8729c547d33f0ab45715d7ec&js_code=${res.code}&grant_type=authorization_code`, method: 'GET', success(res) { console.log(res) let session_key = res.data.session_key wx.getWeRunData({ success: async (res) => { console.log(res) console.log(res.encryptedData) console.log(res.iv) wx.request({ url: 'http://localhost:7337/api/o2o/wxrun', data: { app_id: 'wxe6ecc0193c09696c', session_key: session_key, encrypted_data: res.encryptedData, iv: res.iv }, method: 'POST', success(res) { console.log(res) }, }) }, fail: (err) => { console.log(err) } }) }, }) }, }) }, toRadians(degrees) { return degrees * (Math.PI / 180); }, /** * 两点距离-米 * @param {*} lat1 纬1 * @param {*} lon1 经1 * @param {*} lat2 纬2 * @param {*} lon2 经2 */ haversine(lat1, lon1, lat2, lon2) { const R = 6371000; const φ1 = this.toRadians(parseFloat(lat1)); const φ2 = this.toRadians(parseFloat(lat2)); const Δφ = this.toRadians(parseFloat(lat2) - parseFloat(lat1)); const Δλ = this.toRadians(parseFloat(lon2) - parseFloat(lon1)); const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const distance = R * c; return distance; }, /** * 计算卡路里-千卡 * @param {*} weight 体重kg * @param {*} distance 距离m */ getCalorie(weight, distance) { const caloriesBurned = weight * distance * 0.75; return (caloriesBurned / 1000).toFixed(2); }, /**记录运动过程-每5秒保存一次 */ async setActivityRunLog() { let { actData, stage, longitude, //经度 latitude, //纬度 journey, //运动的路程-米 step, calorie, //卡路里-千卡 pace, //配速-min/km timer, //已记的时长-秒 type, //步行/跑步 } = this.data let ActivityRunLog = Parse.Object.extend('ActivityRunLog') let log = new ActivityRunLog() log.set('company', { __type: 'Pointer', className: 'Company', objectId: company }) log.set('user', { __type: 'Pointer', className: '_User', objectId: uid }) log.set('actData', { __type: 'Pointer', className: 'ActivityData', objectId: actData?.id }) log.set('stage', stage) log.set('type', type) log.set('sportDate', parseInt(timer) || 0) log.set('burnCalories', parseFloat(calorie || '0')) log.set('matchSpeed', parseFloat(pace || '0')) log.set('distance', parseFloat(journey || '0')) log.set('steps', step) let location = new Parse.GeoPoint({ latitude: latitude, longitude: longitude }); log.set('location', location) await log.save() }, /**时间格式显示 */ formatTime: function (seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; // 格式化为两位数 const formattedHours = String(hours).padStart(2, '0'); const formattedMinutes = String(minutes).padStart(2, '0'); const formattedSeconds = String(secs).padStart(2, '0'); return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { this.examineAuthorization() }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () {}, /** * 生命周期函数--监听页面卸载 */ onUnload: function () {}, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { }, })