typulse.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209
  1. <template>
  2. <view class="container">
  3. <!-- 标题 -->
  4. <view class="header" :style="{height:(44+statusBarHeight)+'px',paddingTop: statusBarHeight+ 'px'}">
  5. <text class="title">TyPulse 简单示例</text>
  6. </view>
  7. <!-- 状态显示 -->
  8. <view class="status-section">
  9. <view class="status-item">
  10. <text class="status-label">插件状态:</text>
  11. <text
  12. :class="['status-value', initStatus ? 'success' : 'error']">{{ initStatus ? '已初始化' : '未初始化' }}</text>
  13. </view>
  14. <view class="status-item">
  15. <text class="status-label">设备状态:</text>
  16. <text
  17. :class="['status-value', deviceConnected ? 'success' : 'error']">{{ deviceConnected ? '已连接' : '未连接' }}</text>
  18. </view>
  19. <view class="status-item">
  20. <text class="status-label">测量状态:</text>
  21. <text :class="['status-value', measuring ? 'warning' : 'normal']">{{ measuring ? '测量中' : '待机' }}</text>
  22. </view>
  23. </view>
  24. <!-- 连接状态指示器 -->
  25. <view class="connection-status" v-if="deviceInfo">
  26. <view class="status-indicator connected"></view>
  27. <text class="status-text">设备已连接</text>
  28. </view>
  29. <!-- 设备信息 -->
  30. <view v-if="deviceInfo" class="device-section">
  31. <text class="section-title">设备信息</text>
  32. <text class="device-battery">设备id: {{ deviceInfo.deviceId }}</text>
  33. <text class="device-name">设备名称:{{ deviceInfo.deviceName }}</text>
  34. <text class="device-battery">电量: {{ deviceInfo.battery }}%</text>
  35. </view>
  36. </view>
  37. <!-- 操作按钮 -->
  38. <view class="action-section">
  39. <view @click="toMeasure" class="btn btn-primary">
  40. {{ deviceInfo ? '继续测脉' : '去测脉' }}
  41. </view>
  42. <view v-if="deviceInfo" @click="disconnectDevice" class="btn btn-secondary">断开设备</view>
  43. <view @click="toMeasurePortrait" class="btn btn-secondary">去测脉(强制竖屏)</view>
  44. <view @click="toMeasureLandscape" class="btn btn-secondary">去测脉(强制横屏)</view>
  45. <view @click="toMeasureAuto" class="btn btn-secondary">去测脉(自动方向)</view>
  46. <view @click="toTongue" class="btn btn-primary">舌诊示例</view>
  47. <view @click="toCamera" class="btn btn-primary">摄像头调试</view>
  48. <view @click="toSmdtDevice" class="btn btn-primary">平板设备</view>
  49. <!-- <view @click="toCamera1" class="btn btn-primary">摄像头自动变焦</view> -->
  50. <!-- <view @click="startMeasure" :disabled="!deviceConnected || measuring" class="btn btn-success">开始测量</view>
  51. <view @click="stopMeasure" :disabled="!measuring" class="btn btn-danger">停止测量</view> -->
  52. </view>
  53. <!-- 测量进度 -->
  54. <view v-if="measuring" class="progress-section">
  55. <text class="progress-text">测量进度: {{ measureProgress }}%</text>
  56. <view class="progress-bar">
  57. <view class="progress-fill" :style="{ width: measureProgress + '%' }"></view>
  58. </view>
  59. <text class="stage-text">{{ currentStage }}</text>
  60. </view>
  61. <!-- 测量结果 -->
  62. <view v-if="lastResult" class="result-section">
  63. <text class="section-title">最新测量结果</text>
  64. <view class="result-item">
  65. <text class="result-label">心率:</text>
  66. <text class="result-value">{{ lastResult.heartRate }} bpm</text>
  67. </view>
  68. <view class="result-item">
  69. <text class="result-label">血压:</text>
  70. <text class="result-value">{{ lastResult.bloodPressure.systolic }}/{{ lastResult.bloodPressure.diastolic }}
  71. mmHg</text>
  72. </view>
  73. <view class="result-item">
  74. <text class="result-label">测量时间:</text>
  75. <text class="result-value">{{ lastResult.measureTime }}</text>
  76. </view>
  77. </view>
  78. <!-- 简单日志 -->
  79. <view class="log-section">
  80. <text class="section-title">操作日志</text>
  81. <scroll-view class="log-list" scroll-y="true">
  82. <view v-for="(log, index) in logs" :key="index" class="log-item">
  83. <text class="log-text">{{ log }}</text>
  84. </view>
  85. </scroll-view>
  86. </view>
  87. </view>
  88. </template>
  89. <script>
  90. export default {
  91. data() {
  92. return {
  93. // 插件实例
  94. tyPulse: null,
  95. // 状态
  96. initStatus: false,
  97. deviceConnected: false,
  98. isConnecting: false, // 防止重复连接的标志
  99. measuring: false,
  100. // 设备信息
  101. deviceInfo: null,
  102. // 测量相关
  103. measureProgress: 0,
  104. currentStage: '待机',
  105. lastResult: null,
  106. appId: "nehijR6y",
  107. appSecret: "f740435d1b84b9944a52f064dd1ecf6e8a76f546",
  108. uid: "userid000067",
  109. // 日志
  110. logs: [],
  111. devices: [],
  112. statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
  113. // 重连相关
  114. reconnectAttempts: 0,
  115. // 连接状态检查标志(防止重复执行和白屏)
  116. isCheckingConnection: false,
  117. }
  118. },
  119. onLoad() {
  120. this.addLog('页面加载完成');
  121. this.initPlugin();
  122. },
  123. onShow() {
  124. this.addLog('页面显示');
  125. // 页面显示时检查连接状态
  126. if (this.tyPulse && this.initStatus) {
  127. this.checkAndRestoreConnection();
  128. }
  129. },
  130. onHide() {
  131. this.addLog('页面隐藏,保持连接状态');
  132. // 页面隐藏时不主动断开连接,只记录状态
  133. // 这样可以在页面重新显示时恢复连接
  134. },
  135. onUnload() {
  136. this.addLog('页面卸载,清理连接状态');
  137. // 页面卸载时清理连接状态
  138. if (this.tyPulse) {
  139. // 不再主动断开连接,保持蓝牙连接状态
  140. this.addLog('页面卸载,保持蓝牙连接');
  141. }
  142. },
  143. methods: {
  144. // 添加日志
  145. addLog(message) {
  146. const time = new Date().toLocaleTimeString();
  147. this.logs.unshift(`[${time}] ${message}`);
  148. if (this.logs.length > 20) {
  149. this.logs.pop();
  150. }
  151. },
  152. // 检查并恢复连接状态(合并后的优化版本)
  153. checkAndRestoreConnection() {
  154. // 添加状态检查,防止重复执行
  155. if (this.isCheckingConnection) {
  156. this.addLog('连接状态检查正在进行中,跳过重复检查');
  157. return;
  158. }
  159. this.isCheckingConnection = true;
  160. this.addLog('开始检查连接状态...');
  161. // 延迟执行,避免页面切换时的竞争条件
  162. setTimeout(() => {
  163. this.performConnectionCheck();
  164. }, 500);
  165. },
  166. // 执行连接检查的核心逻辑
  167. async performConnectionCheck() {
  168. try {
  169. if (!this.tyPulse || !this.initStatus) {
  170. this.addLog('插件未初始化,跳过连接检查');
  171. return;
  172. }
  173. // 设置超时保护,防止长时间阻塞
  174. const timeoutPromise = new Promise((resolve) => {
  175. setTimeout(() => {
  176. resolve({ success: false, error: '连接状态检查超时' });
  177. }, 5000); // 5秒超时
  178. });
  179. // 优先使用智能连接
  180. if (this.tyPulse.smartConnect) {
  181. const smartPromise = new Promise((resolve) => {
  182. this.tyPulse.smartConnect(resolve);
  183. });
  184. const result = await Promise.race([smartPromise, timeoutPromise]);
  185. this.addLog('智能连接结果: ' + JSON.stringify(result));
  186. if (result.success) {
  187. if (result.isAlreadyConnected) {
  188. this.addLog('设备已连接');
  189. this.deviceConnected = true;
  190. // 异步获取设备信息
  191. setTimeout(() => {
  192. this.getDeviceInfo();
  193. }, 50);
  194. return;
  195. } else if (result.isAutoReconnected) {
  196. this.addLog('自动重连成功');
  197. this.deviceConnected = true;
  198. uni.showToast({
  199. title: '自动重连成功',
  200. icon: 'success'
  201. });
  202. return;
  203. }
  204. } else {
  205. if (result.needScan) {
  206. this.addLog('需要重新扫描连接,请手动点击连接按钮');
  207. } else {
  208. this.addLog('智能连接失败: ' + (result.error || '未知错误'));
  209. }
  210. }
  211. }
  212. // 如果没有智能连接方法,使用原有的检查方法
  213. if (this.tyPulse.checkConnectionStatus) {
  214. const statusPromise = new Promise((resolve) => {
  215. this.tyPulse.checkConnectionStatus(resolve);
  216. });
  217. const result = await Promise.race([statusPromise, timeoutPromise]);
  218. this.addLog('连接状态检查结果: ' + JSON.stringify(result));
  219. if (result.success && result.data) {
  220. if (result.data.isConnected) {
  221. // 当前已连接
  222. this.deviceConnected = true;
  223. this.addLog('检测到已有蓝牙连接,状态已恢复');
  224. // 异步获取设备信息,不阻塞UI
  225. setTimeout(() => {
  226. this.getDeviceInfo();
  227. }, 50);
  228. } else {
  229. this.addLog('当前没有蓝牙连接');
  230. }
  231. } else {
  232. this.addLog('连接状态检查失败: ' + (result.error || '未知错误'));
  233. }
  234. } else {
  235. // 如果没有专门的连接状态检查方法,尝试获取设备信息
  236. this.addLog('使用设备信息检查连接状态...');
  237. this.getDeviceInfo();
  238. }
  239. } catch (error) {
  240. this.addLog('连接状态检查异常: ' + error.message);
  241. } finally {
  242. this.isCheckingConnection = false;
  243. this.addLog('连接状态检查完成');
  244. }
  245. },
  246. // 初始化插件
  247. async initPlugin() {
  248. try {
  249. this.addLog('开始初始化插件...');
  250. // 获取插件实例
  251. this.tyPulse = uni.requireNativePlugin('TyPulseManager');
  252. if (!this.tyPulse) {
  253. throw new Error('无法获取插件实例');
  254. }
  255. // 初始化
  256. await new Promise((resolve, reject) => {
  257. this.tyPulse.initSDK(this.appId, this.appSecret, this.uid, (
  258. result) => {
  259. if (result.success) {
  260. resolve();
  261. } else {
  262. reject(new Error(result.error));
  263. }
  264. });
  265. });
  266. this.initStatus = true;
  267. this.addLog('插件初始化成功');
  268. uni.showToast({
  269. title: '插件初始化成功',
  270. icon: 'success'
  271. });
  272. // 延迟检查连接状态,确保插件完全初始化
  273. setTimeout(() => {
  274. this.checkAndRestoreConnection();
  275. }, 500);
  276. } catch (error) {
  277. this.addLog(`初始化失败: ${error.message}`);
  278. uni.showToast({
  279. title: '初始化失败',
  280. icon: 'error'
  281. });
  282. }
  283. },
  284. toMeasure() {
  285. if (!this.initStatus || !this.tyPulse) {
  286. this.addLog('插件未初始化,无法进行测量');
  287. uni.showToast({
  288. title: '插件未初始化',
  289. icon: 'error'
  290. });
  291. return;
  292. }
  293. this.addLog('调用原生 toMeasure...');
  294. this.tyPulse.toMeasure(result => {
  295. this.addLog('toMeasure 回调: ' + JSON.stringify(result));
  296. if (result.success) {
  297. if (result.cmdType == "pulseResult") {
  298. this.addLog('测量结果:' + JSON.stringify(result));
  299. }
  300. if (result.cmdType == "deviceConnect") {
  301. //获取设备信息
  302. this.getDeviceInfo();
  303. this.addLog('回调结果:' + JSON.stringify(result));
  304. }
  305. } else {
  306. this.addLog('测脉操作失败: ' + (result.error || '未知错误'));
  307. }
  308. });
  309. },
  310. // 设置回调
  311. setupCallbacks() {
  312. this.tyPulse.setDeviceCallback((result) => {
  313. this.addLog('设备状态变更: ' + JSON.stringify(result));
  314. switch (result.status) {
  315. case 'connected':
  316. this.deviceConnected = true;
  317. this.deviceInfo = result.deviceInfo || {};
  318. this.reconnectAttempts = 0; // 重置重连次数
  319. uni.showToast({
  320. title: '设备已连接',
  321. icon: 'success'
  322. });
  323. break;
  324. case 'disconnected':
  325. this.deviceConnected = false;
  326. this.deviceInfo = {};
  327. uni.showToast({
  328. title: '设备已断开',
  329. icon: 'none'
  330. });
  331. // 如果是意外断开,可以尝试自动重连
  332. if (result.isUnexpected) {
  333. this.addLog('设备意外断开,尝试自动重连...');
  334. setTimeout(() => {
  335. this.checkAndRestoreConnection();
  336. }, 1000);
  337. }
  338. break;
  339. case 'connection_failed':
  340. this.deviceConnected = false;
  341. this.deviceInfo = {};
  342. uni.showToast({
  343. title: '连接失败',
  344. icon: 'error'
  345. });
  346. break;
  347. }
  348. });
  349. // 测量进度回调
  350. this.tyPulse.setProgressCallback((progress) => {
  351. this.measureProgress = progress.percentage;
  352. this.currentStage = this.getStageText(progress.stage);
  353. });
  354. // 测量结果回调
  355. this.tyPulse.setMeasureCallback((result) => {
  356. this.measuring = false;
  357. this.measureProgress = 0;
  358. this.currentStage = '待机';
  359. if (result.success) {
  360. this.lastResult = result.data;
  361. this.addLog(`测量完成 - 心率: ${result.data.heartRate} bpm`);
  362. uni.showToast({
  363. title: '测量完成',
  364. icon: 'success'
  365. });
  366. } else {
  367. this.addLog(`测量失败: ${result.error}`);
  368. uni.showToast({
  369. title: '测量失败',
  370. icon: 'error'
  371. });
  372. }
  373. });
  374. },
  375. // 获取阶段文本
  376. getStageText(stage) {
  377. const stageMap = {
  378. 'preparing': '准备中',
  379. 'calibrating': '校准中',
  380. 'measuring': '测量中',
  381. 'analyzing': '分析中',
  382. 'completed': '完成'
  383. };
  384. return stageMap[stage] || '未知阶段';
  385. },
  386. // 获取设备信息
  387. getDeviceInfo() {
  388. if (!this.tyPulse || !this.initStatus) {
  389. this.addLog('插件未初始化,无法获取设备信息');
  390. return;
  391. }
  392. try {
  393. this.tyPulse.getDeviceInfo((result) => {
  394. if (result.success) {
  395. this.deviceInfo = result.data;
  396. this.deviceConnected = true; // 更新设备连接状态
  397. this.addLog(`设备信息: ${JSON.stringify(result.data)}`);
  398. uni.showToast({
  399. title: '设备已连接',
  400. icon: 'success'
  401. });
  402. } else {
  403. this.addLog(`获取设备信息失败: ${result.error}`);
  404. this.deviceInfo = null;
  405. this.deviceConnected = false; // 更新设备连接状态
  406. }
  407. });
  408. } catch (error) {
  409. this.addLog(`获取设备信息异常: ${error.message}`);
  410. this.deviceInfo = null;
  411. }
  412. },
  413. // 断开设备连接
  414. disconnectDevice() {
  415. if (!this.tyPulse || !this.initStatus) {
  416. this.addLog('插件未初始化,无法断开设备');
  417. return;
  418. }
  419. try {
  420. this.tyPulse.disconnectDevice((result) => {
  421. if (result.success) {
  422. this.deviceInfo = null;
  423. this.deviceConnected = false; // 更新设备连接状态
  424. this.addLog('设备已断开连接');
  425. uni.showToast({
  426. title: '设备已断开',
  427. icon: 'success'
  428. });
  429. } else {
  430. this.addLog(`断开设备失败: ${result.error}`);
  431. }
  432. });
  433. } catch (error) {
  434. this.addLog(`断开设备异常: ${error.message}`);
  435. }
  436. },
  437. // 安全的连接状态检查方法(防止白屏)
  438. async checkAndRestoreConnectionSafely() {
  439. try {
  440. // 添加状态检查,防止重复执行
  441. if (this.isCheckingConnection) {
  442. this.addLog('连接状态检查正在进行中,跳过重复检查');
  443. return;
  444. }
  445. this.isCheckingConnection = true;
  446. this.addLog('安全检查蓝牙连接状态...');
  447. // 设置超时保护,防止长时间阻塞
  448. const timeoutPromise = new Promise((resolve) => {
  449. setTimeout(() => {
  450. resolve({ success: false, error: '连接状态检查超时' });
  451. }, 5000); // 5秒超时
  452. });
  453. // 使用新的智能连接方法
  454. if (this.tyPulse.smartConnect) {
  455. const smartPromise = new Promise((resolve) => {
  456. this.tyPulse.smartConnect(resolve);
  457. });
  458. const result = await Promise.race([smartPromise, timeoutPromise]);
  459. this.addLog('智能连接结果: ' + JSON.stringify(result));
  460. if (result.success) {
  461. if (result.isAlreadyConnected) {
  462. this.addLog('设备已连接');
  463. this.deviceConnected = true;
  464. // 异步获取设备信息
  465. setTimeout(() => {
  466. this.getDeviceInfo();
  467. }, 50);
  468. } else if (result.isAutoReconnected) {
  469. this.addLog('自动重连成功');
  470. this.deviceConnected = true;
  471. uni.showToast({
  472. title: '自动重连成功',
  473. icon: 'success'
  474. });
  475. }
  476. } else {
  477. if (result.needScan) {
  478. this.addLog('需要重新扫描连接,请手动点击连接按钮');
  479. // 不自动触发任何连接操作,完全由用户手动控制
  480. } else {
  481. this.addLog('智能连接失败: ' + (result.error || '未知错误'));
  482. }
  483. }
  484. } else {
  485. // 如果没有智能连接方法,使用原有的检查方法
  486. if (this.tyPulse.checkConnectionStatus) {
  487. const statusPromise = new Promise((resolve) => {
  488. this.tyPulse.checkConnectionStatus(resolve);
  489. });
  490. const result = await Promise.race([statusPromise, timeoutPromise]);
  491. this.addLog('连接状态检查结果: ' + JSON.stringify(result));
  492. if (result.success && result.data) {
  493. if (result.data.isConnected) {
  494. // 当前已连接
  495. this.deviceConnected = true;
  496. this.addLog('检测到已有蓝牙连接,状态已恢复');
  497. // 异步获取设备信息,不阻塞UI
  498. setTimeout(() => {
  499. this.getDeviceInfo();
  500. }, 50);
  501. } else {
  502. // 没有连接,不做任何自动操作
  503. this.addLog('当前没有蓝牙连接,请手动点击连接按钮');
  504. }
  505. } else {
  506. this.addLog('连接状态检查失败: ' + (result.error || '未知错误'));
  507. }
  508. } else {
  509. this.addLog('没有可用的连接状态检查方法');
  510. }
  511. }
  512. } catch (error) {
  513. this.addLog('连接状态检查异常: ' + error.message);
  514. } finally {
  515. this.isCheckingConnection = false;
  516. }
  517. },
  518. // 自动重连
  519. async autoReconnect() {
  520. try {
  521. this.addLog('开始自动重连...');
  522. // 优先使用智能连接
  523. if (this.tyPulse.smartConnect) {
  524. const result = await new Promise((resolve) => {
  525. this.tyPulse.smartConnect(resolve);
  526. });
  527. this.addLog('智能连接结果: ' + JSON.stringify(result));
  528. if (result.success) {
  529. if (result.isAlreadyConnected) {
  530. this.deviceConnected = true;
  531. this.addLog('设备已连接');
  532. this.getDeviceInfo();
  533. } else if (result.isAutoReconnected) {
  534. this.deviceConnected = true;
  535. this.addLog('自动重连成功');
  536. uni.showToast({
  537. title: '自动重连成功',
  538. icon: 'success'
  539. });
  540. this.getDeviceInfo();
  541. }
  542. return;
  543. }
  544. }
  545. // 如果智能连接失败,尝试连接已保存的设备
  546. if (this.tyPulse.connectToSavedDevice) {
  547. const savedResult = await new Promise((resolve) => {
  548. this.tyPulse.connectToSavedDevice(resolve);
  549. });
  550. this.addLog('连接已保存设备结果: ' + JSON.stringify(savedResult));
  551. if (savedResult.success) {
  552. this.deviceConnected = true;
  553. this.addLog('连接已保存设备成功');
  554. uni.showToast({
  555. title: '连接已保存设备成功',
  556. icon: 'success'
  557. });
  558. this.getDeviceInfo();
  559. } else {
  560. this.addLog('连接已保存设备失败: ' + (savedResult.error || '未知错误'));
  561. }
  562. }
  563. } catch (error) {
  564. this.addLog(`自动重连失败: ${error.message}`);
  565. }
  566. },
  567. // 连接设备
  568. async connectDevice() {
  569. try {
  570. // 防止重复连接
  571. if (this.isConnecting) {
  572. this.addLog('连接正在进行中,请稍候...');
  573. return;
  574. }
  575. // 先尝试智能连接
  576. if (this.tyPulse.smartConnect) {
  577. this.isConnecting = true;
  578. this.addLog('尝试智能连接...');
  579. uni.showLoading({
  580. title: '连接中...'
  581. });
  582. const smartResult = await new Promise((resolve) => {
  583. this.tyPulse.smartConnect(resolve);
  584. });
  585. uni.hideLoading();
  586. if (smartResult.success) {
  587. if (smartResult.isAlreadyConnected) {
  588. this.deviceConnected = true;
  589. this.addLog('设备已连接');
  590. uni.showToast({
  591. title: '设备已连接',
  592. icon: 'success'
  593. });
  594. this.getDeviceInfo();
  595. return;
  596. } else if (smartResult.isAutoReconnected) {
  597. this.deviceConnected = true;
  598. this.addLog('自动重连成功');
  599. uni.showToast({
  600. title: '自动重连成功',
  601. icon: 'success'
  602. });
  603. this.getDeviceInfo();
  604. return;
  605. }
  606. }
  607. }
  608. // 如果智能连接失败或不可用,使用普通连接
  609. this.isConnecting = true;
  610. this.addLog('开始连接设备...');
  611. uni.showLoading({
  612. title: '连接中...'
  613. });
  614. // 直接弹出设备选择窗口,让用户选择设备
  615. this.addLog('弹出设备选择窗口...');
  616. const scanResult = await new Promise((resolve) => {
  617. this.tyPulse.scanAndConnect(resolve);
  618. });
  619. uni.hideLoading();
  620. if (scanResult.success) {
  621. this.addLog('设备选择窗口已启动,等待用户选择设备...');
  622. // 注意:实际的连接成功会通过setDeviceCallback中的'connected'事件通知
  623. // 这里不需要设置deviceConnected = true,等待回调处理
  624. } else {
  625. this.addLog(`连接失败: ${scanResult.error}`);
  626. uni.showToast({
  627. title: '连接失败',
  628. icon: 'error'
  629. });
  630. }
  631. } catch (error) {
  632. uni.hideLoading();
  633. this.addLog(`连接异常: ${error.message}`);
  634. uni.showToast({
  635. title: '连接异常',
  636. icon: 'error'
  637. });
  638. } finally {
  639. this.isConnecting = false;
  640. }
  641. },
  642. // 开始测量
  643. async startMeasure() {
  644. try {
  645. this.measuring = true;
  646. this.measureProgress = 0;
  647. this.currentStage = '开始测量';
  648. this.addLog('开始测量...');
  649. this.tyPulse.startMeasure((result) => {
  650. if (result.success) {
  651. this.addLog('测量启动成功');
  652. } else {
  653. this.measuring = false;
  654. this.addLog(`测量失败: ${result.error}`);
  655. }
  656. });
  657. } catch (error) {
  658. this.measuring = false;
  659. this.addLog(`开始测量失败: ${error.message}`);
  660. }
  661. },
  662. // 停止测量
  663. async stopMeasure() {
  664. try {
  665. this.addLog('停止测量...');
  666. this.tyPulse.stopMeasure((result) => {
  667. this.measuring = false;
  668. this.measureProgress = 0;
  669. this.currentStage = '待机';
  670. if (result.success) {
  671. this.addLog('测量已停止');
  672. } else {
  673. this.addLog(`停止测量失败: ${result.error}`);
  674. }
  675. });
  676. } catch (error) {
  677. this.measuring = false;
  678. this.addLog(`停止测量失败: ${error.message}`);
  679. }
  680. },
  681. toSmdtDevice(){
  682. uni.navigateTo({
  683. url:"/pages/index/smdtManager/smdtManager"
  684. })
  685. },
  686. toCamera(){
  687. uni.navigateTo({
  688. url:"/pages/device/tongue/test"
  689. })
  690. },
  691. toCamera1(){
  692. console.log("qxj toCamera1");
  693. uni.navigateTo({
  694. url:"/pages/device/tongue/testCameraAuto"
  695. })
  696. },
  697. toTongue(){
  698. uni.navigateTo({
  699. url:"/pages/device/tongue/indexOld"
  700. })
  701. },
  702. // 强制竖屏测脉
  703. toMeasurePortrait() {
  704. try {
  705. // 设置强制竖屏
  706. this.tyPulse.setOrientationConfig({
  707. autoOrientationEnabled: false,
  708. forcedOrientation: 1 // ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
  709. });
  710. this.addLog('设置强制竖屏模式');
  711. this.tyPulse.toMeasure((result) => {
  712. setTimeout(() => {
  713. uni.showToast({
  714. title: '竖屏模式: ' + JSON.stringify(result),
  715. icon: 'none'
  716. });
  717. }, 2000);
  718. if (result.success) {
  719. if (result.cmdType == "pulseResult") {
  720. this.addLog('测量结果(竖屏):' + JSON.stringify(result));
  721. }
  722. if (result.cmdType == "deviceConnect") {
  723. this.getDeviceInfo();
  724. this.addLog('回调结果(竖屏):' + JSON.stringify(result));
  725. }
  726. }
  727. });
  728. } catch (error) {
  729. this.addLog(`竖屏测脉失败: ${error.message}`);
  730. }
  731. },
  732. // 强制横屏测脉
  733. toMeasureLandscape() {
  734. try {
  735. // 设置强制横屏
  736. this.tyPulse.setOrientationConfig({
  737. autoOrientationEnabled: false,
  738. forcedOrientation: 0 // ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
  739. });
  740. this.addLog('设置强制横屏模式');
  741. this.tyPulse.toMeasure((result) => {
  742. setTimeout(() => {
  743. uni.showToast({
  744. title: '横屏模式: ' + JSON.stringify(result),
  745. icon: 'none'
  746. });
  747. }, 2000);
  748. if (result.success) {
  749. if (result.cmdType == "pulseResult") {
  750. this.addLog('测量结果(横屏):' + JSON.stringify(result));
  751. }
  752. if (result.cmdType == "deviceConnect") {
  753. this.getDeviceInfo();
  754. this.addLog('回调结果(横屏):' + JSON.stringify(result));
  755. }
  756. }
  757. });
  758. } catch (error) {
  759. this.addLog(`横屏测脉失败: ${error.message}`);
  760. }
  761. },
  762. // 自动屏幕方向测脉
  763. toMeasureAuto() {
  764. try {
  765. // 设置自动屏幕方向
  766. this.tyPulse.setOrientationConfig({
  767. autoOrientationEnabled: true,
  768. forcedOrientation: -1 // ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
  769. });
  770. this.addLog('设置自动屏幕方向模式');
  771. this.tyPulse.toMeasure((result) => {
  772. setTimeout(() => {
  773. uni.showToast({
  774. title: '自动方向: ' + JSON.stringify(result),
  775. icon: 'none'
  776. });
  777. }, 2000);
  778. if (result.success) {
  779. if (result.cmdType == "pulseResult") {
  780. this.addLog('测量结果(自动):' + JSON.stringify(result));
  781. }
  782. if (result.cmdType == "deviceConnect") {
  783. this.getDeviceInfo();
  784. this.addLog('回调结果(自动):' + JSON.stringify(result));
  785. }
  786. }
  787. });
  788. } catch (error) {
  789. this.addLog(`自动方向测脉失败: ${error.message}`);
  790. }
  791. },
  792. // 尝试自动连接(不显示设备选择弹框)
  793. async tryAutoConnect() {
  794. try {
  795. this.addLog('开始尝试自动连接...');
  796. // 优先使用智能连接
  797. if (this.tyPulse.smartConnect) {
  798. const smartResult = await new Promise((resolve) => {
  799. this.tyPulse.smartConnect(resolve);
  800. });
  801. this.addLog('智能连接结果: ' + JSON.stringify(smartResult));
  802. if (smartResult.success) {
  803. if (smartResult.isAlreadyConnected) {
  804. this.deviceConnected = true;
  805. this.addLog('设备已连接');
  806. this.getDeviceInfo();
  807. return true;
  808. } else if (smartResult.isAutoReconnected) {
  809. this.deviceConnected = true;
  810. this.addLog('自动重连成功');
  811. this.getDeviceInfo();
  812. return true;
  813. }
  814. }
  815. }
  816. // 如果智能连接失败,尝试连接已保存的设备
  817. if (this.tyPulse.connectToSavedDevice) {
  818. const savedResult = await new Promise((resolve) => {
  819. this.tyPulse.connectToSavedDevice(resolve);
  820. });
  821. this.addLog('连接已保存设备结果: ' + JSON.stringify(savedResult));
  822. if (savedResult.success) {
  823. this.deviceConnected = true;
  824. this.addLog('连接已保存设备成功');
  825. this.getDeviceInfo();
  826. return true;
  827. }
  828. }
  829. this.addLog('自动连接失败,无法连接到设备');
  830. return false;
  831. } catch (error) {
  832. this.addLog(`自动连接异常: ${error.message}`);
  833. return false;
  834. }
  835. },
  836. // 断开蓝牙连接
  837. disconnectDevice() {
  838. if (this.tyPulse) {
  839. this.tyPulse.disconnectDevice((result) => {
  840. console.log('断开连接结果:', result);
  841. if (result.success) {
  842. this.addLog('蓝牙连接已断开');
  843. this.deviceConnected = false;
  844. this.reconnectAttempts = 0; // 重置重连次数
  845. } else {
  846. this.addLog('断开连接失败: ' + (result.error || '未知错误'));
  847. }
  848. });
  849. } else {
  850. this.addLog('插件未初始化,无法断开连接');
  851. }
  852. }
  853. }
  854. }
  855. </script>
  856. <style scoped lang="scss">
  857. .container {
  858. padding: 20rpx;
  859. background-color: #f8f9fa;
  860. min-height: 100vh;
  861. }
  862. .header {
  863. text-align: center;
  864. padding: 30rpx;
  865. background-color: #007AFF;
  866. border-radius: 10rpx;
  867. margin-bottom: 20rpx;
  868. }
  869. .title {
  870. color: white;
  871. font-weight: bold;
  872. }
  873. .status-section {
  874. background-color: white;
  875. border-radius: 10rpx;
  876. padding: 20rpx;
  877. margin-bottom: 20rpx;
  878. }
  879. .status-item {
  880. display: flex;
  881. justify-content: space-between;
  882. align-items: center;
  883. padding: 15rpx 0;
  884. border-bottom: 1rpx solid #f0f0f0;
  885. }
  886. .status-item:last-child {
  887. border-bottom: none;
  888. }
  889. .status-label {
  890. color: #666;
  891. }
  892. .status-value {
  893. font-weight: bold;
  894. }
  895. .status-value.success {
  896. color: #52c41a;
  897. }
  898. .status-value.error {
  899. color: #ff4d4f;
  900. }
  901. .status-value.warning {
  902. color: #faad14;
  903. }
  904. .status-value.normal {
  905. color: #666;
  906. }
  907. /* 连接状态指示器样式 */
  908. .connection-status {
  909. display: flex;
  910. align-items: center;
  911. justify-content: center;
  912. background-color: white;
  913. border-radius: 10rpx;
  914. padding: 20rpx;
  915. margin-bottom: 20rpx;
  916. }
  917. .status-indicator {
  918. width: 20rpx;
  919. height: 20rpx;
  920. border-radius: 50%;
  921. margin-right: 15rpx;
  922. animation: pulse 2s infinite;
  923. }
  924. .status-indicator.connected {
  925. background-color: #52c41a;
  926. }
  927. .status-text {
  928. color: #52c41a;
  929. font-weight: bold;
  930. }
  931. @keyframes pulse {
  932. 0% {
  933. opacity: 1;
  934. transform: scale(1);
  935. }
  936. 50% {
  937. opacity: 0.7;
  938. transform: scale(1.1);
  939. }
  940. 100% {
  941. opacity: 1;
  942. transform: scale(1);
  943. }
  944. }
  945. .device-section,
  946. .result-section,
  947. .log-section {
  948. background-color: white;
  949. border-radius: 10rpx;
  950. padding: 20rpx;
  951. margin-bottom: 20rpx;
  952. }
  953. .section-title {
  954. font-weight: bold;
  955. color: #333;
  956. margin-bottom: 15rpx;
  957. display: block;
  958. }
  959. .device-name {
  960. color: #333;
  961. margin-bottom: 10rpx;
  962. display: block;
  963. }
  964. .device-battery {
  965. color: #666;
  966. display: block;
  967. }
  968. .action-section {
  969. background-color: white;
  970. border-radius: 10rpx;
  971. padding: 20rpx;
  972. margin-bottom: 20rpx;
  973. display: flex;
  974. flex-direction: column;
  975. gap: 15rpx;
  976. }
  977. .btn {
  978. padding: 5px;
  979. border-radius: 8rpx;
  980. border: none;
  981. text-align: center;
  982. width: 100%;
  983. margin: 6rpx;
  984. //font-size: 14px;
  985. }
  986. .btn-primary {
  987. background-color: #007AFF;
  988. color: white;
  989. }
  990. .btn-secondary {
  991. background-color: #6c757d;
  992. color: white;
  993. }
  994. .btn-success {
  995. background-color: #28a745;
  996. color: white;
  997. }
  998. .btn-danger {
  999. background-color: #dc3545;
  1000. color: white;
  1001. }
  1002. .btn:disabled {
  1003. opacity: 0.5;
  1004. background-color: #ccc;
  1005. }
  1006. .progress-section {
  1007. background-color: white;
  1008. border-radius: 10rpx;
  1009. padding: 20rpx;
  1010. margin-bottom: 20rpx;
  1011. }
  1012. .progress-text {
  1013. color: #333;
  1014. margin-bottom: 15rpx;
  1015. display: block;
  1016. }
  1017. .progress-bar {
  1018. width: 100%;
  1019. height: 20rpx;
  1020. background-color: #f0f0f0;
  1021. border-radius: 10rpx;
  1022. overflow: hidden;
  1023. margin-bottom: 15rpx;
  1024. }
  1025. .progress-fill {
  1026. height: 100%;
  1027. background-color: #007AFF;
  1028. transition: width 0.3s ease;
  1029. }
  1030. .stage-text {
  1031. color: #666;
  1032. text-align: center;
  1033. display: block;
  1034. }
  1035. .result-item {
  1036. display: flex;
  1037. justify-content: space-between;
  1038. align-items: center;
  1039. padding: 12rpx 0;
  1040. border-bottom: 1rpx solid #f0f0f0;
  1041. }
  1042. .result-item:last-child {
  1043. border-bottom: none;
  1044. }
  1045. .result-label {
  1046. color: #666;
  1047. }
  1048. .result-value {
  1049. color: #333;
  1050. font-weight: bold;
  1051. }
  1052. .log-list {
  1053. height: 700rpx;
  1054. border: 1rpx solid #e8e8e8;
  1055. border-radius: 8rpx;
  1056. padding: 10rpx;
  1057. }
  1058. .log-item {
  1059. padding: 8rpx 0;
  1060. border-bottom: 1rpx solid #f5f5f5;
  1061. }
  1062. .log-item:last-child {
  1063. border-bottom: none;
  1064. }
  1065. .log-text {
  1066. color: #666;
  1067. line-height: 1.4;
  1068. }
  1069. </style>