| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351 |
- <template>
- <view class="page">
- <view class="nav-bar" :style="{ paddingTop: statusBarHeight}">
- <view class="nav-back" @tap="goBack">
- <!-- <text class="back-text">‹ 返回</text> -->
- <image class="back-text" src="@/static/images/pages_watch/icons/back_arrow_icon24.png"></image>
- </view>
- <view class="nav-title">FitCloud智能手表</view>
- <image class="next-text" src="@/static/images/pages_watch/icons/more_icon24.png"></image>
- </view>
- <view class="content">
- <view class="section-header">当前设备</view>
- <view class="status-card connected">
- <image class="deviceimg" src="/static/images/pages_watch/icons/smartwatch_img.png" mode="aspectFit">
- </image>
- <view class="x-f flex">
- <view class="flex">
- <view class="device-name">{{newXhsDeviceId}}</view>
- <view class="status-row">
- <!-- <text class="status-label ">连接状态</text> -->
- <text class="status-value" :class="(connectionState || '').toLowerCase()">
- {{ connectionText}}
- </text>
- </view>
- </view>
- <button class="device-connect" :disabled="disconnecting" :loading="disconnecting" @tap="unbindDevice()">解绑</button>
- <button class="device-connect" @tap="connectDevice()">连接</button>
- </view>
- </view>
- <!-- connectionState === 'CONNECTED' -->
- <view class="panel" v-if="connectedDeviceMac" style="margin-top: 24rpx;">
- <view class="panel-title x-bc">
- <text>实时健康数据</text>
- <text style="color: #07c160;" v-show="dataSync=='success'">同步成功</text>
- <button class="device-item-btn" v-show="dataSync!='success'" :disabled="dataSync=== 'loading'"
- :loading="dataSync=== 'loading'" @click="xhsDataAdd(connectedDeviceMac,1)">
- {{ dataSync=== 'loading' ? '同步中' : dataSync === 'error'? '重新同步': '同步成功'}}
- </button>
- </view>
- <view class="health-monitoring">
- <view class="health-monitoring-item">
- <view class="health-monitoring-title">
- <view>
- <view class="health-monitoring-maintitle">心率</view>
- <view>心脏健康管理</view>
- </view>
- <image src="/static/images/pages_watch/icons/heart_rate_icon.png" mode="aspectFill"></image>
- </view>
- <view class="health-monitoring-res resnum">{{ healthData.heartRate}}</view>
- </view>
- <view class="health-monitoring-item">
- <view class="health-monitoring-title">
- <view>
- <view class="health-monitoring-maintitle">血氧</view>
- <view>血氧风险管控</view>
- </view>
- <image src="/static/images/pages_watch/icons/blood_oxygen_icon.png" mode="aspectFill">
- </image>
- </view>
- <view class="health-monitoring-res resnum">{{ healthData.oxygen}}</view>
- </view>
- <view class="health-monitoring-item">
- <view class="health-monitoring-title">
- <view>
- <view class="health-monitoring-maintitle">血压</view>
- <view>血压健康监测</view>
- </view>
- <image src="/static/images/pages_watch/icons/blood_pressure_icon.png" mode="aspectFill"></image>
- </view>
- <view class="health-monitoring-res resnum">
- {{ healthData.systolicPressure }}/{{ healthData.diastolicPressure }}</view>
- </view>
- </view>
- <template v-if="connectionState == 'CONNECTED'">
- <view class="btn-row">
- <button class="btn btn-primary" @click="startRealTimeData" :disabled="isMeasuring">开始测量</button>
- <button class="btn btn-danger" @click="stopRealTimeData" :disabled="!isMeasuring">停止测量</button>
- </view>
- <!-- <view class="btn-row">
- <button class="btn btn-default" @click="disconnect">断开连接</button>
- </view> -->
- </template>
- </view>
- <!-- connectionState === 'CONNECTED' v-if="connectedDeviceMac"-->
- <view class="panel es-mt-20" v-if="connectedDeviceMac">
- <view class="panel-title x-bc">
- <text>其他数据</text>
- <text style="color: #07c160;" v-show="dataOtherSync=='success'&&!isSyncing">同步成功</text>
- <button class="device-item-btn" v-show="dataOtherSync!='success'" :disabled="dataOtherSync=== 'loading'"
- :loading="dataOtherSync=== 'loading'" @click="syncData()">
- {{ dataOtherSync=== 'loading' ? '同步中' : dataOtherSync === 'error'||dataOtherSync === 'errorSync'? '重新同步': '同步成功'}}
- </button>
- </view>
- <view class="history-section" v-if="sleepDataList && sleepDataList.length > 0">
- <view class="section-title">睡眠记录 (最新{{ sleepDataList.length }}条)</view>
- <scroll-view scroll-y class="history-list">
- <view class="history-item" v-for="(item, index) in sleepDataList" :key="index">
- <view class="history-header">
- <text class="date">{{ formatTime(item.timestamp) }}</text>
- <text class="count">{{ item.items ? item.items.length : 0 }}段</text>
- </view>
- <view class="history-detail" v-if="item.items && item.items.length > 0">
- <view class="detail-row" v-for="(detail, dIndex) in item.items" :key="dIndex">
- <text class="time">{{ formatTimeOnly(detail.startTime) }} - {{ formatTimeOnly(detail.endTime) }}</text>
- <text class="status" :class="'status-' + detail.status">
- {{ getSleepStatus(detail.status) }}
- </text>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- <view class="empty-tip" v-else>暂无睡眠数据</view>
- <!-- 运动数据展示 -->
- <view class="history-section" v-if="sportDataList && sportDataList.length > 0">
- <view class="section-title">锻炼记录 (最新{{ sportDataList.length }}条)</view>
- <scroll-view scroll-y class="history-list">
- <view class="history-item" v-for="(item, index) in sportDataList" :key="index">
- <view class="history-header">
- <text class="date">{{ formatTime(item.timestamp) }}</text>
- <text class="count">类型: {{ item.sportType }}</text>
- </view>
- <view class="history-detail">
- <view class="detail-row">
- <text class="time">时长: {{ item.duration }}秒</text>
- <text class="status">消耗: {{ item.calories }}千卡</text>
- </view>
- <view class="detail-row">
- <text class="time">步数: {{ item.steps }}步</text>
- <text class="status">距离: {{ item.distance }}公里</text>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- <view class="empty-tip" v-else>暂无锻炼记录</view>
- <!-- 计步数据展示 -->
- <view class="history-section" v-if="stepDataList && stepDataList.length > 0">
- <view class="section-title">计步记录 (最新{{ stepDataList.length }}条)</view>
- <scroll-view scroll-y class="history-list">
- <view class="history-item" v-for="(item, index) in stepDataList" :key="index">
- <view class="history-header">
- <text class="date">{{ formatTime(item.timestamp) }}</text>
- </view>
- <view class="history-detail">
- <view class="detail-row">
- <text class="time">步数: {{ item.steps }}步</text>
- <text class="status">消耗: {{ item.calories }}千卡</text>
- </view>
- <view class="detail-row">
- <text class="time">距离: {{ item.distance }}公里</text>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- <view class="empty-tip" v-else>暂无计步记录</view>
- </view>
- </view>
- </view>
- </template>
- <script>
- import {
- getWatchUserInfo,
- editMyfamily
- } from "@/api/pages_watch/user.js";
- import {
- editXHSDevice,
- xhsDataAddList,
- removeXHSDevice,
- getLastData
- } from "@/api/pages_watch/device.js"
- // 引入原生插件
- const fitCloudWatch = uni.requireNativePlugin('fitCloudWatch');
- export default {
- data() {
- return {
- disconnecting: false,
- isScanning: false,
- deviceList: [],
- connectionState: 'DISCONNECTED', // CONNECTED, DISCONNECTED, CONNECTING
- connectedDeviceMac: '',
- isMeasuring: false,
- isAutoConnecting: false, // 避免收到原生旧状态干扰的标识
- healthData: {
- heartRate: 0,
- oxygen: 0,
- diastolicPressure: 0,
- systolicPressure: 0,
- respiratoryRate: 0
- },
- isSyncing: false,
- sleepDataList: [],
- sportDataList: [],
- stepDataList: [],
- statusBarHeight: uni.getSystemInfoSync().statusBarHeight + 'px',
- xhsDeviceList: [],
- fitCloudWatchUser: {},
- deviceType: 2, //0腕表1小护士2新表
- isFamily: false,
- selectUser: 0,
- boundDevice: null,
- dataSync: 'success',
- dataSyncFunInfo: "",
- dataOtherSync: 'success',
- dataOtherSyncFunInfo: "",
- otherDevice: [],
- newXhsDeviceId: ''
- }
- },
- computed: {
- connectionText() {
- const map = {
- 'CONNECTED': '已连接',
- 'DISCONNECTED': '未连接',
- 'CONNECTING': '连接中...'
- };
- return map[this.connectionState]
- }
- },
- watch: {
- connectionState(newVal) {
- this.updateXhsDeviceStatus();
- },
- connectedDeviceMac(newVal){
- this.updateXhsDeviceStatus();
- },
- },
- onLoad(option) {
- this.selectUser = Number(option.selectUser || 0)
- this.isFamily = option.selectUser != '0';
- this.newXhsDeviceId = uni.getStorageSync('fitCloudWatch_mac');
- this.getUser()
- const sys = uni.getSystemInfoSync();
- },
- onShow() {
- // 页面显示时注册事件和检查状态,保证从后台切回或从其他页面返回时都能自动重连
- this.registerGlobalEvents();
- if(this.newXhsDeviceId) {
- this.checkCurrentConnectionState();
- }
- },
- onHide() {
- // 页面隐藏时清理事件,避免多实例问题
- this.removeGlobalEvents();
- },
- onUnload() {
- // 页面卸载时停止扫描和测量,防止内存泄漏
- if (this.isScanning) {
- this.stopScan();
- }
- if (this.isMeasuring) {
- this.stopRealTimeData();
- }
- this.removeGlobalEvents();
- },
- methods: {
- goScan() {
- uni.navigateTo({
- url: '/pages_bluetooth/scanFitWatch?selectUser='+this.selectUser
- })
- },
- // 检查当前连接状态
- checkCurrentConnectionState() {
- if (!fitCloudWatch) return;
-
- try {
- fitCloudWatch.getConnectionState({}, (res) => {
- console.log(type,'当前连接状态:', res);
- if (res.state === 'CONNECTED' && res.mac) {
- // 原生层已经连接,直接恢复 UI 状态
- // 校验:当前设备是否等于连接的设备
- if(res.mac==this.newXhsDeviceId) {
- this.connectionState = 'CONNECTED';
- this.connectedDeviceMac = res.mac;
- // uni.showToast({ title: '已恢复连接状态', icon: 'none' });
- } else {
- // 尝试自动连接已绑定的设备
- this.disconnect()
- this.checkAutoConnect();
- }
- } else {
- // 原生层未连接,尝试自动连接已绑定的设备
- this.checkAutoConnect();
- }
- });
- } catch (e) {
- console.error('调用 getConnectionState 失败:', e);
- // 降级处理,直接尝试自动连接
- this.checkAutoConnect();
- }
- },
- updateXhsDeviceStatus() {
- this.xhsDeviceList.forEach((item, index) => {
- if (item.newXhsDeviceId === this.connectedDeviceMac) {
- this.$set(this.xhsDeviceList[index], 'connectionState', this.connectionState);
- } else {
- this.$set(this.xhsDeviceList[index], 'connectionState', '');
- }
- });
- },
- goBack() {
- uni.navigateBack({
- delta: 1
- });
- },
- // 注册插件的全局事件监听
- registerGlobalEvents() {
- // 先移除旧的监听,防止重复注册
- this.removeGlobalEvents();
-
- // 监听扫描到的设备
- plus.globalEvent.addEventListener('onFitCloudDeviceFound', this.onDeviceFound);
- // 监听连接状态变化
- plus.globalEvent.addEventListener('onFitCloudConnectionStateChanged', this.onConnectionStateChanged);
- // 监听连接错误
- plus.globalEvent.addEventListener('onFitCloudConnectionError', this.onConnectionError);
- // 监听实时健康数据
- plus.globalEvent.addEventListener('onFitCloudRealTimeData', this.onRealTimeData);
- // 监听实时测量错误
- plus.globalEvent.addEventListener('onFitCloudRealTimeDataError', this.onRealTimeDataError);
- // 监听睡眠数据同步
- plus.globalEvent.addEventListener('onFitCloudSleepData', this.onSleepData);
- // 监听运动数据同步
- plus.globalEvent.addEventListener('onFitCloudSportData', this.onSportData);
- // 监听计步数据同步
- plus.globalEvent.addEventListener('onFitCloudStepData', this.onStepData);
- },
- removeGlobalEvents() {
- plus.globalEvent.removeEventListener('onFitCloudDeviceFound', this.onDeviceFound);
- plus.globalEvent.removeEventListener('onFitCloudConnectionStateChanged', this.onConnectionStateChanged);
- plus.globalEvent.removeEventListener('onFitCloudConnectionError', this.onConnectionError);
- plus.globalEvent.removeEventListener('onFitCloudRealTimeData', this.onRealTimeData);
- plus.globalEvent.removeEventListener('onFitCloudRealTimeDataError', this.onRealTimeDataError);
- plus.globalEvent.removeEventListener('onFitCloudSleepData', this.onSleepData);
- plus.globalEvent.removeEventListener('onFitCloudSportData', this.onSportData);
- plus.globalEvent.removeEventListener('onFitCloudStepData', this.onStepData);
- },
- onDeviceFound(device) {
- console.log('扫描到设备:', JSON.stringify(device));
- },
- onConnectionStateChanged(res) {
- console.log('状态改变:', res.state);
-
- if (res.state === 'DISCONNECTED') {
- if (this.isAutoConnecting) {
- // 自动连接中收到的断开状态很可能是原生的旧状态,忽略它
- console.log('忽略自动连接中的断开状态事件');
- return;
- }
- this.connectionState = res.state;
- uni.showToast({ title: '已断开连接', icon: 'none' });
- this.isMeasuring = false;
- } else if (res.state === 'CONNECTED') {
- // 如果 res 里面带了 mac,更新一下
- if (res.mac==this.newXhsDeviceId) {
- this.connectionState = res.state;
- this.isAutoConnecting = false; // 连接成功,立即重置
- uni.showToast({ title: '连接成功', icon: 'success' });
- }
- } else {
- this.connectionState = res.state;
- }
- },
- onConnectionError(res) {
- console.log('连接错误:', res);
- // 如果是 SDK 内部在重试,可以不弹窗打扰用户
- if (res.isRetry) {
- console.log('正在重试连接...');
- return;
- }
- // 只有当前不在已连接状态,或者不是重试时才提示
- if (this.connectionState !== 'CONNECTED') {
- uni.showToast({
- title: '连接异常: ' + (res.error || '未知错误'),
- icon: 'none',
- duration: 3000
- });
- this.connectionState = 'DISCONNECTED';
- this.isAutoConnecting = false;
- }
- },
- onRealTimeData(data) {
- this.healthData = {
- ...this.healthData,
- ...data
- };
- },
-
- onRealTimeDataError(error) {
- uni.showToast({ title: '测量异常: ' + error.error, icon: 'none' });
- this.isMeasuring = false;
- },
-
- onSleepData(res) {
- console.log('接收睡眠数据:', res);
- if (res.sleepData && res.sleepData.length > 0) {
- this.sleepDataList = res.sleepData;
- this.xhsOtherDataAdd(this.connectedDeviceMac)
- }
- },
-
- onSportData(res) {
- console.log('接收到运动(锻炼)数据:', res);
- if (res.sportData && res.sportData.length > 0) {
- this.sportDataList = res.sportData;
- this.xhsOtherDataAdd(this.connectedDeviceMac)
- }
- },
-
- onStepData(res) {
- console.log('接收到计步数据:', res);
- if (res.stepData && res.stepData.length > 0) {
- this.stepDataList = res.stepData;
- this.xhsOtherDataAdd(this.connectedDeviceMac)
- }
- },
- // 自动连接
- checkAutoConnect() {
- if (!fitCloudWatch) return;
- const savedMac = this.newXhsDeviceId;
- if (savedMac) {
- this.connectionState = 'CONNECTING';
- this.connectedDeviceMac = savedMac;
- // 标记正在自动连接中,避免被原生的初始状态事件(DISCONNECTED)干扰
- this.isAutoConnecting = true;
- uni.showLoading({ title: '自动连接中...' });
- const userId = this.fitCloudWatchUser.userId + '_' + savedMac
- fitCloudWatch.connect({
- mac: savedMac,
- userId: userId, // 测试用户ID
- isBind: true, // 测试绑定模式
- sex: this.fitCloudWatchUser.sex || true, // 男
- age: this.fitCloudWatchUser.age || 25,
- height: this.fitCloudWatchUser.height || 175,
- weight: this.fitCloudWatchUser.weight || 70
- }, (res) => {
- uni.hideLoading();
- if (res.code !== 0) {
- this.connectionState = 'DISCONNECTED';
- this.isAutoConnecting = false;
- uni.showToast({ title: '自动连接指令发送失败', icon: 'none' });
- } else {
- // 连接指令发送成功,重置标记,让状态监听正常接管
- setTimeout(() => {
- this.isAutoConnecting = false;
- }, 1500); // 延迟 1.5 秒重置,跳过旧状态的干扰
- }
- });
- }
- },
- // 1. 开始扫描
- async startScan() {
- // if (!fitCloudWatch) {
- // uni.showToast({ title: '原生插件未加载,请在自定义基座或原生App中运行', icon: 'none' });
- // return;
- // }
-
- // // Android 平台必须动态申请定位和蓝牙权限
- // if (plus.os.name === 'Android') {
- // const permissions = [
- // 'android.permission.ACCESS_FINE_LOCATION',
- // 'android.permission.ACCESS_COARSE_LOCATION'
- // ];
-
- // // 适配 Android 12+ 蓝牙权限
- // if (parseInt(plus.os.version) >= 12) {
- // permissions.push('android.permission.BLUETOOTH_SCAN');
- // permissions.push('android.permission.BLUETOOTH_CONNECT');
- // }
-
- // for (let i = 0; i < permissions.length; i++) {
- // const result = await new Promise(resolve => {
- // plus.android.requestPermissions([permissions[i]], (res) => {
- // resolve(res.granted.length > 0);
- // }, (e) => {
- // resolve(false);
- // });
- // });
- // if (!result) {
- // uni.showToast({ title: '需要定位和蓝牙权限才能扫描设备', icon: 'none' });
- // return;
- // }
- // }
-
- // // 检查蓝牙是否开启
- // const main = plus.android.runtimeMainActivity();
- // const BluetoothAdapter = plus.android.importClass("android.bluetooth.BluetoothAdapter");
- // const BAdapter = BluetoothAdapter.getDefaultAdapter();
- // if(!BAdapter.isEnabled()){
- // uni.showModal({
- // title: '提示',
- // content: '请先打开蓝牙',
- // showCancel: false
- // });
- // return;
- // }
- // }
-
- // this.deviceList = [];
- // this.isScanning = true;
-
- // // 将保存的 MAC 传给原生,让原生直接过滤
- // const savedMac = uni.getStorageSync('fitCloudWatch_mac');
- // fitCloudWatch.startScan({ mac: savedMac || '' }, (res) => {
- // if (res.code !== 0) {
- // this.isScanning = false;
- // uni.showToast({ title: '启动扫描失败: ' + res.message, icon: 'none' });
- // }
- // });
- },
-
- // 2. 停止扫描
- stopScan() {
- if (!fitCloudWatch) return;
- fitCloudWatch.stopScan({}, (res) => {
- this.isScanning = false;
- });
- },
- // 3. 连接设备
- connectDevice() {
- if(!this.newXhsDeviceId) return
- // 连接状态检测
- if (this.connectionState === 'CONNECTED') {
- if (this.connectedDeviceMac === this.newXhsDeviceId) {
- uni.showToast({ title: '该设备已连接,无需重复连接', icon: 'none' });
- return;
- } else {
- uni.showToast({ title: '请先断开当前设备后再连接新设备', icon: 'none' });
- return;
- }
- }
- if (this.isScanning) {
- this.stopScan();
- }
- this.connectionState = 'CONNECTING';
- this.connectedDeviceMac = this.newXhsDeviceId;
-
- // 判断 xhsDeviceList 中是否已存在当前设备
- const existIndex = this.xhsDeviceList.findIndex(item => item.newXhsDeviceId === this.newXhsDeviceId);
- if (existIndex !== -1) {
- // 已存在 → 更新连接状态
- this.$set(this.xhsDeviceList[existIndex], 'connectionState', 'CONNECTING');
- }
- uni.showLoading({
- title: '连接中...'
- });
- const userId = this.fitCloudWatchUser.userId + '_' + this.newXhsDeviceId
- fitCloudWatch.connect({
- mac: this.newXhsDeviceId,
- userId: userId, // 测试用户ID
- isBind: true, // 测试绑定模式
- sex: this.fitCloudWatchUser.sex || true, // 男
- age: this.fitCloudWatchUser.age || 25,
- height: this.fitCloudWatchUser.height || 175,
- weight: this.fitCloudWatchUser.weight || 70
- }, (res) => {
- uni.hideLoading();
- if (res.code !== 0) {
- this.connectionState = 'DISCONNECTED';
- uni.showToast({
- title: '连接指令发送失败,请检查蓝牙和定位是否打开',
- icon: 'none'
- });
- }
- });
- },
- // 4. 断开连接
- disconnect() {
- fitCloudWatch.disconnect({}, (res) => {
- console.log('断开指令发送', res);
- });
- },
- getUser() {
- this.fitCloudWatchUser = uni.getStorageSync("userWatchInfo") ? JSON.parse(uni.getStorageSync("userWatchInfo")) : ''
- if(this.fitCloudWatchUser) {
- if (this.isFamily) {
- // 安全解析家庭成员设备
- this.otherDevice = this.fitCloudWatchUser.otherDevice ? JSON.parse(this.fitCloudWatchUser.otherDevice) : [];
-
- // 格式化设备ID为对象数组
- const formattedDevices = this.otherDevice.map(item => ({
- ...item,
- newXhsDeviceId: item.newXhsDeviceId ?
- item.newXhsDeviceId.split(',').map(id => ({
- newXhsDeviceId: id,
- connectionState: ''
- })) : []
- }));
- // 安全取当前选中成员的设备(防越界)
- const index = this.selectUser - 1;
- this.loading = false
- this.xhsDeviceList = formattedDevices[index]?.newXhsDeviceId || [];
- } else {
- // 个人设备格式化
- const deviceIds = this.fitCloudWatchUser.newXhsDeviceId ? this.fitCloudWatchUser.newXhsDeviceId.split(',') : [];
- this.loading = false
- this.xhsDeviceList = deviceIds.map(id => ({
- newXhsDeviceId: id,
- connectionState: ''
- }));
- }
- // 2. 设备绑定 & 自动连接逻辑(统一处理,无重复代码)
- if (!this.xhsDeviceList || this.xhsDeviceList.length === 0) {
- // 无设备:清空绑定缓存
- uni.removeStorageSync('fitCloudWatch_mac');
- this.boundDevice = null;
- } else {
- this.newXhsDeviceId = uni.getStorageSync('fitCloudWatch_mac');
- // 自动连接 & 获取数据
- this.checkCurrentConnectionState()
- }
- }
- },
- // 解绑设备
- unbindDevice() {
- uni.showModal({
- title: '解绑确认',
- content: '确定要解绑当前手表吗?',
- success: (res) => {
- if (res.confirm) {
- // 统一删除设备方法
- const removeDevice = () => {
- this.isFamily
- ? this.removeXHSDevicefamily(this.newXhsDeviceId)
- : this.removeXHSDevice(this.newXhsDeviceId);
- };
-
- try {
- fitCloudWatch.unbind({}, (unbindRes) => {
- // 无论解绑成功/失败,都执行删除
- if (unbindRes.code === 0) {
- this.disconnect();
- }
- removeDevice();
- });
- } catch (error) {
- // 出错了也强制删除设备
- removeDevice();
- console.error('解绑异常:', error);
- }
- }
- }
- });
- },
- // 5. 开始测量实时数据
- startRealTimeData() {
- this.isMeasuring = true;
- // 重置数据展示
- this.healthData = {
- heartRate: 0,
- oxygen: 0,
- diastolicPressure: 0,
- systolicPressure: 0,
- respiratoryRate: 0
- };
- fitCloudWatch.startRealTimeData({
- heartRate: true,
- oxygen: true,
- bloodPressure: true,
- respiratoryRate: true
- }, (res) => {
- if (res.code !== 0) {
- this.isMeasuring = false;
- uni.showToast({
- title: '开启测量失败',
- icon: 'none'
- });
- }
- });
- },
- // 6. 停止测量实时数据
- stopRealTimeData() {
- this.xhsDataAdd(this.connectedDeviceMac)
- fitCloudWatch.stopRealTimeData({}, (res) => {
- this.isMeasuring = false;
- });
- },
- xhsDataAdd(deviceId, type) {
- // 无设备ID直接返回
- if (!deviceId) return;
- // 条件一致时不重新构造数据
- if (type === 1 && this.dataSyncFunInfo?.[0]?.deviceId === deviceId) {
- // 不处理,直接走接口
- } else {
- // 解构防止 healthData 不存在报错
- const { systolicPressure, diastolicPressure, heartRate, oxygen } = this.healthData || {};
- // 构造参数(0:血压 1:血糖 2:心率 3尿酸 4血氧 5步数 6运动)
- this.dataSyncFunInfo = [{
- recordType: 0, // 血压
- recordValue: JSON.stringify({
- sdb: systolicPressure,
- dbp: diastolicPressure
- }),
- deviceId: deviceId,
- deviceType: this.deviceType
- }, {
- recordType: 2, // 心率
- recordValue: heartRate,
- deviceId: deviceId,
- deviceType: this.deviceType
- }, {
- recordType: 4, // 血氧
- recordValue: oxygen,
- deviceId: deviceId,
- deviceType: this.deviceType
- }];
- }
- this.dataSync = 'loading'
- xhsDataAddList(this.dataSyncFunInfo).then(res => {
- if (res.code == 200) {
- this.dataSync = 'success'
- this.dataSyncFunInfo = []
- } else {
- this.dataSync = 'error'
- uni.showToast({
- title: '数据同步失败' + JSON.stringify(res.msg),
- icon: 'none'
- })
- }
- }).catch(() => {
- this.dataSync = 'error'
- uni.showToast({
- title: '数据同步失败',
- icon: 'none'
- })
- })
- },
- // 同步睡眠、步数,运动
- xhsOtherDataAdd(deviceId, type) {
- // 无设备ID直接返回
- if (!deviceId) return;
-
- // 条件一致时不重新构造数据
- if (type === 1 && this.dataOtherSyncFunInfo?.[0]?.deviceId === deviceId) {
- // 不处理,直接走接口
- } else {
- // 构造参数(0:血压 1:血糖 2:心率 3尿酸 4血氧 5步数 6运动 7睡眠)
- this.dataOtherSyncFunInfo = [{
- recordType: 5, // 步数
- recordValue: JSON.stringify(this.stepDataList),
- deviceId: deviceId,
- deviceType: this.deviceType
- }, {
- recordType: 6, // 运动
- recordValue: JSON.stringify(this.sportDataList),
- deviceId: deviceId,
- deviceType: this.deviceType
- }, {
- recordType: 7, // 睡眠
- recordValue: JSON.stringify(this.sleepDataList),
- deviceId: deviceId,
- deviceType: this.deviceType
- }];
- }
- this.dataOtherSync = 'loading'
- xhsDataAddList(this.dataOtherSyncFunInfo).then(res => {
- if (res.code == 200) {
- this.dataOtherSync = 'success'
- this.dataOtherSyncFunInfo = []
- } else {
- this.dataOtherSync = 'error'
- uni.showToast({
- title: '数据同步失败' + JSON.stringify(res.msg),
- icon: 'none'
- })
- }
- }).catch(() => {
- this.dataOtherSync = 'error'
- uni.showToast({
- title: '数据同步失败',
- icon: 'none'
- })
- })
- },
- // 7. 同步历史数据 (包含睡眠、运动、计步等)
- syncData() {
- if(this.dataOtherSync == 'error'){
- this.xhsOtherDataAdd(this.connectedDeviceMac, 1)
- return
- }
- console.log("同步历史数据this.isSyncing",this.isSyncing)
- if (this.isSyncing) return;
-
- this.isSyncing = true;
- // 清理旧数据
- this.sleepDataList = [];
- this.sportDataList = [];
- this.stepDataList = [];
- uni.showLoading({ title: '同步中...' });
- this.dataOtherSync = 'loading'
- try {
- fitCloudWatch.syncData({}, (res) => {
- this.isSyncing = false;
- uni.hideLoading();
- if (res.code === 0) {
- uni.showToast({ title: '同步指令已发送', icon: 'success' });
- } else {
- this.dataOtherSync = 'errorSync'
- uni.showToast({ title: '同步失败: ' + (res.message || '未知错误'), icon: 'none' });
- }
- });
- } catch (err) {
- console.error("同步失败:", err);
- this.isSyncing = false;
- this.dataOtherSync = 'errorSync';
- uni.hideLoading();
- uni.showToast({ title: "同步异常,请重试", icon: "none" });
- }
- },
- formatTime(timestamp) {
- if (!timestamp) return '--';
- const date = new Date(timestamp);
- const y = date.getFullYear();
- const m = (date.getMonth() + 1).toString().padStart(2, '0');
- const d = date.getDate().toString().padStart(2, '0');
- return `${y}-${m}-${d}`;
- },
- // 辅助方法:状态转换
- getSleepStatus(status) {
- const statusMap = {
- 1: '深睡',
- 2: '浅睡',
- 3: '清醒'
- };
- return statusMap[status] || '未知';
- },
- formatTimeOnly(timestamp) {
- if (!timestamp) return '';
- const date = new Date(timestamp);
- const h = String(date.getHours()).padStart(2, '0');
- const m = String(date.getMinutes()).padStart(2, '0');
- return `${h}:${m}`;
- },
- // 小护士解除绑定设备(自己)
- removeXHSDevice(newXhsDeviceId) {
- uni.showLoading({
- title: '解绑中'
- })
- removeXHSDevice({
- newXhsDeviceId: newXhsDeviceId,
- deviceType: this.deviceType
- }).then(async res => {
- uni.hideLoading()
- if (res.code == 200) {
- this.xhsDeviceList = this.xhsDeviceList.filter(item => item.newXhsDeviceId != newXhsDeviceId)
- this.connectionState = ''
- this.connectedDeviceMac = ''
- this.newXhsDeviceId = ''
- uni.removeStorageSync('fitCloudWatch_mac');
- uni.showToast({
- title: '解绑成功',
- icon: 'success'
- });
- } else {
- uni.showToast({
- title: res.msg,
- icon: 'none'
- })
- }
- }).catch(err => {
- uni.hideLoading()
- uni.showToast({
- title: '设备解绑失败',
- icon: 'none'
- })
- })
- },
- removeXHSDevicefamily(deviceId) {
- let newXhsDeviceId = this.xhsDeviceList.map(item => item.newXhsDeviceId);
- newXhsDeviceId = newXhsDeviceId.filter(item => item != deviceId);
- this.otherDevice[this.selectUser - 1].newXhsDeviceId = newXhsDeviceId.join(',')
- uni.showLoading({
- title: '解绑中'
- })
- editMyfamily({
- otherDevice: JSON.stringify(this.otherDevice)
- }).then(async res => {
- uni.hideLoading()
- if (res.code == 200) {
- this.xhsDeviceList = this.xhsDeviceList.filter(item => item.newXhsDeviceId != deviceId)
- this.connectionState = ''
- this.connectedDeviceMac = ''
- this.newXhsDeviceId = ''
- uni.removeStorageSync('fitCloudWatch_mac');
- uni.showToast({
- title: '解绑成功',
- icon: 'success'
- });
- } else {
- uni.showToast({
- title: res.msg,
- icon: 'none'
- })
- // if (this.connected) {
- // await this.disconnect();
- // }
- uni.removeStorageSync('fitCloudWatch_mac');
- }
- }).catch(async err => {
- uni.hideLoading()
- uni.showToast({
- title: '设备解绑失败',
- icon: 'none'
- })
- });
- },
- // 获取接口数据
- getLastData(selectedDeviceId) {
- const param = {
- deviceId: selectedDeviceId,
- deviceType: this.deviceType
- }
- getLastData(param).then(res => {
- if (res.code == 200) {
- // // 0:血压 1:血糖 2:心率 3尿酸 4血氧
- // // 状态标记
- // this.dataSync = 'success';
- // 安全获取数据列表
- const {
- data = []
- } = res;
- // 一次性提取所有需要的数据(只遍历一次数组,性能更好)
- const bloodPressure = data.find(item => item.recordType === 0);
- const pulse = data.find(item => item.recordType === 2);
- const oxygen = data.find(item => item.recordType === 4);
- const glucose = data.find(item => item.recordType === 1);
- const uricAcid = data.find(item => item.recordType === 3);
- // 血压数据解析(安全处理)
- let bpData = {};
- try {
- bpData = bloodPressure?.recordValue ? JSON.parse(bloodPressure.recordValue) : {};
- } catch (e) {
- bpData = {};
- }
- // 统一赋值(带默认值,防止 undefined 报错)
- this.cardGlucose = {
- value: glucose?.recordValue || '',
- unit: 'mmol/L',
- time: glucose?.createTime || ''
- };
- this.healthData = {
- heartRate: pulse?.recordValue,
- oxygen: oxygen?.recordValue,
- diastolicPressure: bpData.dbp,
- systolicPressure: bpData.sdb,
- respiratoryRate: null
- }
- } else {
- uni.showToast({
- title: res.msg,
- icon: 'none'
- })
- }
- })
- },
- }
- }
- </script>
- <style lang="scss" scoped>
- .page {
- min-height: 100vh;
- background: #ffffff;
- }
- .deviceimg {
- width: 120rpx;
- height: 120rpx;
- margin-right: 24rpx;
- }
- .nav-bar {
- position: sticky;
- top: 0;
- z-index: 10;
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin: 0 24rpx;
- background: #fff;
- }
- .back-text {
- width: 64rpx;
- height: 64rpx;
- }
- .next-text {
- width: 48rpx;
- height: 48rpx;
- }
- .nav-back {
- width: 84rpx;
- height: 64rpx;
- display: flex;
- align-items: center;
- justify-content: flex-start;
- }
- .back-text {
- font-size: 30rpx;
- color: #ff5c03;
- }
- .nav-title {
- font-size: 34rpx;
- font-weight: 600;
- color: #333;
- }
- .content {
- padding: 32rpx 24rpx 48rpx;
- }
- .section-header {
- font-size: 32rpx;
- font-weight: 700;
- color: #333;
- margin-bottom: 24rpx;
- padding-left: 8rpx;
- }
- .status-card {
- background: #fff;
- border-radius: 24rpx;
- padding: 32rpx;
- border: 2rpx solid #f0f0f0;
- margin-bottom: 24rpx;
- display: flex;
- align-items: center;
- }
- .device-name {
- font-size: 30rpx;
- font-weight: 600;
- color: #222;
- margin-bottom: 16rpx;
- }
- .status-card.connected {
- background: #fffaf7;
- border-color: rgba(255, 92, 3, 0.15);
- box-shadow: 0 4rpx 12rpx rgba(255, 92, 3, 0.05);
- }
- .status-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .status-row:not(:last-child) {
- margin-bottom: 12rpx;
- }
- .status-label {
- font-size: 26rpx;
- color: #999;
- }
- .status-value {
- font-size: 28rpx;
- font-weight: 600;
- color: #333;
- }
- .status-value.connected {
- color: #07c160;
- }
- .status-value.disconnected {
- color: #999;
- }
- .status-value.connecting {
- color: #ff9501;
- }
- .panel {
- background: #fff;
- border-radius: 24rpx;
- padding: 24rpx;
- margin-bottom: 24rpx;
- border: 2rpx solid #f5f5f5;
- }
- .panel-title {
- font-size: 30rpx;
- font-weight: 600;
- color: #333;
- margin-bottom: 20rpx;
- }
- .health-monitoring {
- display: flex;
- align-items: center;
- justify-content: flex-start;
- flex-wrap: wrap;
- margin-right: -16rpx;
- &-item {
- width: calc(50% - 16rpx);
- min-height: 264rpx;
- margin: 0 16rpx 16rpx 0;
- overflow: hidden;
- background: #F5f7fa;
- border-radius: 16rpx 16rpx 16rpx 16rpx;
- padding: 24rpx 34rpx 24rpx 24rpx;
- box-sizing: border-box;
- }
- &-maintitle {
- margin-bottom: 4rpx;
- font-weight: 500;
- font-size: 30rpx;
- color: #333333;
- }
- &-title {
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-weight: 400;
- font-size: 24rpx;
- color: #999999;
- image {
- width: 72rpx;
- height: 72rpx;
- flex-shrink: 0;
- }
- }
- .resnum {
- font-family: DIN, DIN;
- font-weight: 500;
- font-size: 64rpx;
- }
- &-res {
- height: 78rpx;
- margin: 20rpx 0 6rpx;
- font-family: PingFang SC, PingFang SC;
- font-weight: 600;
- font-size: 48rpx;
- color: #333333;
- }
- }
- .btn-row {
- display: flex;
- gap: 16rpx;
- margin-bottom: 16rpx;
- }
- .btn-row:last-child {
- margin-bottom: 0;
- }
- .btn {
- flex: 1;
- height: 84rpx;
- line-height: 84rpx;
- border-radius: 42rpx;
- font-size: 28rpx;
- border: none;
- }
- .btn-primary {
- background: linear-gradient(90deg, #f8551f 0%, #ff9501 100%);
- color: #fff;
- }
- .btn-default {
- background: #f5f7fa;
- color: #67686f;
- }
- .btn-danger {
- background: #e64340;
- color: #fff;
- }
- .device-list {
- margin-top: 8rpx;
- background: #f5f7fa;
- border-radius: 16rpx;
- padding: 16rpx;
- }
- .list-title {
- font-size: 24rpx;
- color: #666;
- margin-bottom: 16rpx;
- }
- .scroll-y {
- max-height: 520rpx;
- }
- .device-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 18rpx 8rpx;
- border-bottom: 1rpx solid #ebedf0;
- }
- .device-item:last-child {
- border-bottom: none;
- }
- .device-info {
- display: flex;
- flex-direction: column;
- }
- .device-mac {
- margin-top: 6rpx;
- font-size: 22rpx;
- color: #999;
- }
- .device-connectbox {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .device-connect {
- font-family: PingFang SC, PingFang SC;
- font-weight: 500;
- font-size: 26rpx;
- color: #fff;
- padding: 16rpx 32rpx;
- background: #FF7700;
- border-radius: 26rpx 26rpx 26rpx 26rpx;
- // 已连接
- &.connected {
- background: #07c160;
- }
- // 连接中
- &.connecting {
- background: #ff9501;
- }
- }
- .empty-tip {
- text-align: center;
- font-size: 24rpx;
- color: #999;
- padding: 28rpx 0 18rpx;
- }
- .device-item-btn {
- background: #07c160;
- color: #fff;
- padding: 16rpx 24rpx;
- border-radius: 44rpx;
- font-size: 28rpx;
- margin: 0;
- min-width: 160rpx;
- }
- .history-section {
- margin-top: 30rpx;
- background: #fff;
- border-radius: 12rpx;
- padding: 16rpx;
- box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
- .section-title {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 16rpx;
- border-left: 6rpx solid #007aff;
- padding-left: 12rpx;
- }
-
- .history-list {
- max-height: 360rpx;
- }
-
- .history-item {
- background: #f9f9f9;
- border-radius: 8rpx;
- padding: 16rpx;
- margin-bottom: 16rpx;
- }
-
- .history-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12rpx;
- border-bottom: 2rpx dashed #eee;
- padding-bottom: 8rpx;
- }
-
- .history-header .date {
- font-size: 26rpx;
- color: #333;
- font-weight: bold;
- }
-
- .history-header .count {
- font-size: 24rpx;
- color: #007aff;
- background: rgba(0, 122, 255, 0.1);
- padding: 4rpx 12rpx;
- border-radius: 20rpx;
- }
-
- .history-detail {
- display: flex;
- flex-direction: column;
- gap: 8rpx;
- }
-
- .detail-row {
- display: flex;
- justify-content: space-between;
- font-size: 24rpx;
- color: #666;
- }
-
- .status-1 { color: #5B8FF9; } /* 深睡 */
- .status-2 { color: #5AD8A6; } /* 浅睡 */
- .status-3 { color: #F6BD16; } /* 清醒 */
- }
- </style>
|