Sleep.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. <template>
  2. <div>
  3. <div class="box-header">
  4. <div class="box-title boldtext">
  5. 睡眠 <span style="margin-left: 14px">{{ currentDate }}</span>
  6. </div>
  7. <div>
  8. <el-button @click="exportData" size="small" style="margin-right: 20px">导出</el-button>
  9. <el-date-picker size="small" v-model="currentDatePicker" type="date" placeholder="选择日期"
  10. value-format="yyyy-MM-dd" @change="datePickerchange">
  11. </el-date-picker>
  12. </div>
  13. </div>
  14. <div v-show="!detailsType">
  15. <div class="sleep-time">{{ sleepHours + '小时' + sleepMinute + '分钟' }}</div>
  16. <div class="sleep-echarts">
  17. <div class="sleep-echarts-box">
  18. <div ref="sleepChart" style="width: 100%; height: 100% ;min-width: 783px;"></div>
  19. </div>
  20. </div>
  21. <div class="sleep-heart">
  22. <div class="sleep-heart-left">
  23. <div class="box-header">
  24. <div class="box-title fz15"></div>
  25. <div class="sleep-heart-num">
  26. {{ sleepInfo.score || '-' }}<span style="font-size: 12px">分</span>
  27. </div>
  28. </div>
  29. <div class="sleep-heart-echarts">
  30. <div ref="sportHeartrate" style="width: 100%; height: 100%"></div>
  31. </div>
  32. </div>
  33. <div class="sleep-heart-right">
  34. <div class="sleep-heart-comment">
  35. 您的睡眠得分是:{{ sleepInfo.score || '-' }}分,请保持好的睡眠习惯哦!
  36. </div>
  37. <div class="list">
  38. <div class="list-item">
  39. <div class="list-item-row1">
  40. <div class="list-item-row1-left">
  41. <span class="list-item-row1icon" style="background-color: #93c0fa"></span>
  42. <span>浅睡</span>
  43. </div>
  44. <div>参考值:10%-40%</div>
  45. </div>
  46. <div class="list-item-row2">
  47. <div class="list-item-row2-left">
  48. <span>{{ convertMinutes(sleepInfo.light_sleep)[0] }}</span>小时<span>{{
  49. convertMinutes(sleepInfo.light_sleep)[1] }}</span>分钟
  50. </div>
  51. <div class="list-item-row2-right">偏低</div>
  52. </div>
  53. </div>
  54. <div class="list-item">
  55. <div class="list-item-row1">
  56. <div class="list-item-row1-left">
  57. <span class="list-item-row1icon" style="background-color: #93c0fa"></span>
  58. <span>深睡</span>
  59. </div>
  60. <div>参考值:10%-40%</div>
  61. </div>
  62. <div class="list-item-row2">
  63. <div class="list-item-row2-left">
  64. <span>{{ convertMinutes(sleepInfo.deep_sleep)[0] }}</span>小时<span>{{
  65. convertMinutes(sleepInfo.deep_sleep)[1] }}</span>分钟
  66. </div>
  67. <div class="list-item-row2-right">偏低</div>
  68. </div>
  69. </div>
  70. <div class="list-item">
  71. <div class="list-item-row1">
  72. <div class="list-item-row1-left">
  73. <span class="list-item-row1icon" style="background-color: #fadeaf"></span>
  74. <span>清醒</span>
  75. </div>
  76. <div>参考值:10%-40%</div>
  77. </div>
  78. <div class="list-item-row2">
  79. <div class="list-item-row2-left">
  80. <span>{{ convertMinutes(sleepInfo.weak_sleep)[0] }}</span>小时<span>{{
  81. convertMinutes(sleepInfo.weak_sleep)[1] }}</span>分钟
  82. </div>
  83. <div class="list-item-row2-right">偏低</div>
  84. </div>
  85. </div>
  86. <div class="list-item">
  87. <div class="list-item-row1">
  88. <div class="list-item-row1-left">
  89. <span class="list-item-row1icon" style="background-color: #9899f5"></span>
  90. <span>快速眼动</span>
  91. </div>
  92. <div>参考值:10%-40%</div>
  93. </div>
  94. <div class="list-item-row2">
  95. <div class="list-item-row2-left">
  96. <span>{{ convertMinutes(sleepInfo.eyemove_sleep)[0] }}</span>小时<span>{{
  97. convertMinutes(sleepInfo.eyemove_sleep)[1] }}</span>分钟
  98. </div>
  99. <div class="list-item-row2-right">偏低</div>
  100. </div>
  101. </div>
  102. </div>
  103. </div>
  104. </div>
  105. </div>
  106. <!-- 列表展示 -->
  107. <div v-show="detailsType">
  108. <el-table :data="dataList" style="width: 100%">
  109. <el-table-column prop="start" label="开始时间" />
  110. <el-table-column prop="end" label="结束时间" />
  111. <el-table-column label="类型">
  112. <template slot-scope="scope">
  113. <span v-if="scope.row.type === 3">深睡</span>
  114. <span v-if="scope.row.type === 4">浅睡</span>
  115. <span v-if="scope.row.type === 6">清醒</span>
  116. <span v-if="scope.row.type === 7">快速眼动</span>
  117. </template>
  118. </el-table-column>
  119. </el-table>
  120. <pagination v-show="(total > 0) && detailsType" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getDataPage"/>
  121. </div>
  122. </div>
  123. </template>
  124. <script>
  125. import { querySleep, exportSleeptDate,querySleepPageByDate } from "@/api/watch/deviceInfo";
  126. export default {
  127. props: {
  128. detailsType: {
  129. type: Boolean,
  130. default: false
  131. },
  132. deviceId: {
  133. type: String || Number,
  134. default: "",
  135. },
  136. },
  137. data() {
  138. return {
  139. total:0,
  140. dataList:[],
  141. queryParams:{
  142. pageNum: 1,
  143. pageSize: 10,
  144. deviceId:"",
  145. date:"",
  146. // startTime:"",
  147. // endTime:""
  148. },
  149. chartData: [],
  150. sleepHours: '', //睡眠时间
  151. sleepMinute: '',
  152. currentDate: this.parseTime(new Date(), "{y}-{m}-{d}"),
  153. currentDatePicker: "",
  154. type: 1,
  155. startTime: "2024-08-04 20:58:00",
  156. endTime: "2024-08-05 00:27:00",
  157. sleepDefaultInfo: {
  158. 4: { name: "浅睡", color: "#93c0fa", yValue: 7 },
  159. 3: { name: "深睡", color: "#427ff7", yValue: 3 },
  160. 6: { name: "清醒", color: "#fadeaf", yValue: 9 },
  161. 7: { name: "快速眼动", color: "#9899f5", yValue: 12 },
  162. },
  163. sleepInfo: {
  164. score: '',
  165. light_sleep: '',
  166. deep_sleep: '',
  167. weak_sleep: '',
  168. eyemove_sleep: ''
  169. },
  170. chart: null,
  171. chartOption: {
  172. tooltip: {
  173. show: true,
  174. formatter: (params) => {
  175. return params.name;
  176. },
  177. },
  178. xAxis: {
  179. type: "time",
  180. splitLine: {
  181. show: false,
  182. },
  183. axisLabel: {
  184. showMaxLabel: true,
  185. showMinLabel: true,
  186. formatter: (value) => {
  187. return this.parseTime(value, "{h}:{i}");
  188. },
  189. },
  190. },
  191. yAxis: {
  192. max: 14,
  193. show: false,
  194. },
  195. series: [
  196. {
  197. type: "custom",
  198. renderItem: (params, api) => {
  199. let yValue = api.value(2);
  200. let start = api.coord([api.value(0), yValue]);
  201. let size = api.size([api.value(1) - api.value(0), yValue]);
  202. let style = api.style();
  203. return {
  204. type: "rect",
  205. shape: {
  206. x: start[0],
  207. y: start[1],
  208. width: size[0],
  209. height: size[1],
  210. },
  211. style: style,
  212. };
  213. },
  214. data: [],
  215. },
  216. ],
  217. },
  218. sportChart: null,
  219. sportChartOption: {
  220. tooltip: {
  221. trigger: "item",
  222. },
  223. legend: {
  224. bottom: "3%",
  225. left: "center",
  226. },
  227. series: [
  228. {
  229. name: "睡眠",
  230. type: "pie",
  231. radius: ['40%', '60%'],
  232. avoidLabelOverlap: false,
  233. padAngle: 2,
  234. label: {
  235. show: false,
  236. position: "center",
  237. },
  238. emphasis: {
  239. label: {
  240. show: true,
  241. fontSize: 26,
  242. fontWeight: "bold",
  243. color: "",
  244. },
  245. },
  246. labelLine: {
  247. show: false,
  248. },
  249. data: [
  250. { value: 0, name: "浅睡", itemStyle: { color: "#93c0fa" } },
  251. { value: 0, name: "深睡", itemStyle: { color: "#427ff7" } },
  252. { value: 0, name: "清醒", itemStyle: { color: "#fadeaf" } },
  253. { value: 0, name: "快速眼动", itemStyle: { color: "#9899f5" } },
  254. ],
  255. },
  256. ],
  257. },
  258. };
  259. },
  260. mounted() {
  261. this.initChart();
  262. },
  263. methods: {
  264. getDataPage(){
  265. this.loading = true;
  266. this.queryParams.deviceId = this.deviceId;
  267. // this.queryParams.startTime = this.currentDate + " 00:00:00";
  268. // this.queryParams.endTime = this.currentDate + " 23:59:59";
  269. this.queryParams.date = this.currentDate;
  270. querySleepPageByDate(this.queryParams)
  271. .then((response) => {
  272. if (response) {
  273. this.dataList = response.rows;
  274. this.total = response.total;
  275. this.loading = false;
  276. } else {
  277. console.warn("睡眠分页数据返回为空或格式不正确:", response);
  278. }
  279. })
  280. .catch((error) => {
  281. console.error("获取睡眠分页数据失败:", error);
  282. });
  283. },
  284. // 分钟转换成小时分
  285. convertMinutes(val) {
  286. if (val) {
  287. const seconds = val * 60
  288. const hours = Math.floor(seconds / 3600); // 3600秒等于1小时
  289. const minutes = Math.floor((seconds % 3600) / 60); // 余数部分除以60秒得到分钟数
  290. return [hours, minutes];
  291. } else {
  292. return [0, 0];
  293. }
  294. },
  295. datePickerchange() {
  296. this.currentDate =
  297. this.currentDatePicker || this.parseTime(new Date(), "{y}-{m}-{d}");
  298. if(this.detailsType){
  299. this.getDataPage();
  300. } else{
  301. this.getDetailsData();
  302. }
  303. },
  304. initChart() {
  305. const chartElement = this.$refs.sleepChart;
  306. const sportChartElement = this.$refs.sportHeartrate;
  307. if (chartElement) {
  308. this.chart = this.echarts.init(chartElement);
  309. this.seChartOptions();
  310. }
  311. if (sportChartElement) {
  312. this.sportChart = this.echarts.init(sportChartElement);
  313. this.sportChart.setOption(this.sportChartOption);
  314. }
  315. },
  316. seChartOptions() {
  317. this.chartOption.xAxis.min = this.startTime;
  318. this.chartOption.xAxis.max = this.endTime;
  319. // const chartData = [
  320. // { start: "2024-08-04 20:58:00", end: "2024-08-04 22:00:00", type: "1" },
  321. // { start: "2024-08-04 22:00:00", end: "2024-08-04 22:58:00", type: "2" },
  322. // { start: "2024-08-04 22:58:00", end: "2024-08-04 23:28:00", type: "3" },
  323. // { start: "2024-08-04 23:28:00", endt: "2024-08-04 23:58:00", type: "4" },
  324. // { start: "2024-08-04 23:58:00", end: "2024-08-05 00:00:00", type: "3" },
  325. // { start: "2024-08-05 00:00:00", end: "2024-08-05 00:27:00", type: "1" },
  326. // ].map((item, index)=> {
  327. // return {
  328. // name: this.sleepDefaultInfo[item.type] && this.sleepDefaultInfo[item.type].name,
  329. // value: [
  330. // new Date(item.start).getTime(),
  331. // new Date(item.end).getTime(),
  332. // this.sleepDefaultInfo[item.type] && this.sleepDefaultInfo[item.type].yValue,
  333. // ],
  334. // itemStyle: {
  335. // color: this.sleepDefaultInfo[item.type] && this.sleepDefaultInfo[item.type].color,
  336. // },
  337. // };
  338. // });
  339. const chartData = this.chartData.map((item, index) => {
  340. return {
  341. name: this.sleepDefaultInfo[item.type] && this.sleepDefaultInfo[item.type].name,
  342. value: [
  343. new Date(item.start).getTime(),
  344. new Date(item.end).getTime(),
  345. this.sleepDefaultInfo[item.type] && this.sleepDefaultInfo[item.type].yValue,
  346. ],
  347. itemStyle: {
  348. color: this.sleepDefaultInfo[item.type] && this.sleepDefaultInfo[item.type].color,
  349. },
  350. };
  351. });
  352. this.chartOption.series[0].data = chartData
  353. this.chart.setOption(this.chartOption);
  354. },
  355. getDetailsData() {
  356. querySleep(this.currentDate, this.deviceId)
  357. .then((response) => {
  358. if (response && response.data) {
  359. // console.log("------------------" + JSON.stringify(response.data, null, 2));
  360. //更新睡眠时间
  361. this.startTime = response.data.startTime;
  362. this.endTime = response.data.endTime;
  363. const time = this.convertMinutes(response.data.sleepTime)
  364. this.sleepHours = time[0];
  365. this.sleepMinute = time[1];
  366. //更新图表
  367. // const data = response.data.sleepSection.map((item) => ({
  368. // name: item.createTime,
  369. // value: [
  370. // new Date(item.createTime).getTime(), // 确保时间为毫秒时间戳
  371. // item.avgBpm,
  372. // ],
  373. // }));
  374. if (response.data.sleepSection != null) {
  375. this.chartData = response.data.sleepSection;
  376. } else{
  377. this.chartData = [];
  378. }
  379. // 更新 chartOptions.series 数据
  380. // this.chartOptions.series[0].data = data;
  381. if (this.chart) {
  382. this.seChartOptions()
  383. }
  384. //统计
  385. this.sleepInfo.score = response.data.score==null?0:response.data.score;
  386. //浅睡
  387. if (response.data.lightSleep != null) {
  388. // this.sportChartOption.series[0].data[0] = { value: response.data.lightSleep, name: '浅睡', itemStyle: { color: "#93c0fa" } };
  389. this.sleepInfo.light_sleep = response.data.lightSleep;
  390. } else{
  391. // this.sportChartOption.series[0].data[0] = { value: response.data.lightSleep, name: '浅睡', itemStyle: { color: "#93c0fa" } };
  392. this.sleepInfo.light_sleep = 0;
  393. }
  394. this.sportChartOption.series[0].data[0] = { value: response.data.lightSleep, name: '浅睡', itemStyle: { color: "#93c0fa" } };
  395. //深睡
  396. if (response.data.deepSleep != null) {
  397. // this.sportChartOption.series[0].data[1] = { value: response.data.deepSleep, name: '深睡', itemStyle: { color: "#427ff7" } };
  398. this.sleepInfo.deep_sleep = response.data.deepSleep;
  399. } else{
  400. // this.sportChartOption.series[0].data[1] = { value: response.data.deepSleep, name: '深睡', itemStyle: { color: "#427ff7" } };
  401. this.sleepInfo.deep_sleep = 0;
  402. }
  403. this.sportChartOption.series[0].data[1] = { value: response.data.deepSleep, name: '深睡', itemStyle: { color: "#427ff7" } };
  404. //清醒
  405. if (response.data.weakSleep != null) {
  406. // this.sportChartOption.series[0].data[2] = { value: response.data.weakSleep, name: '清醒', itemStyle: { color: "#fadeaf" } };
  407. this.sleepInfo.weak_sleep = response.data.weakSleep;
  408. } else{
  409. // this.sportChartOption.series[0].data[2] = { value: response.data.weakSleep, name: '清醒', itemStyle: { color: "#fadeaf" } };
  410. this.sleepInfo.weak_sleep = 0;
  411. }
  412. this.sportChartOption.series[0].data[2] = { value: response.data.weakSleep, name: '清醒', itemStyle: { color: "#fadeaf" } };
  413. //快速眼动
  414. if (response.data.eyemoveSleep != null) {
  415. // this.sportChartOption.series[0].data[3] = { value: response.data.eyemoveSleep, name: '快速眼动', itemStyle: { color: "#9899f5" } };
  416. this.sleepInfo.eyemove_sleep = response.data.eyemoveSleep;
  417. } else{
  418. // this.sportChartOption.series[0].data[3] = { value: response.data.eyemoveSleep, name: '快速眼动', itemStyle: { color: "#9899f5" } };
  419. this.sleepInfo.eyemove_sleep = 0;
  420. }
  421. this.sportChartOption.series[0].data[3] = { value: response.data.eyemoveSleep, name: '快速眼动', itemStyle: { color: "#9899f5" } };
  422. // if (this.sportChart) {
  423. // this.sportChart.setOption(this.sportChartOption);
  424. // }
  425. } else {
  426. this.defaultChart();
  427. console.warn("睡眠数据返回为空", response);
  428. }
  429. if (this.sportChart) {
  430. this.sportChart.setOption(this.sportChartOption);
  431. }
  432. })
  433. .catch((error) => {
  434. console.error("获取睡眠数据失败:", error);
  435. });
  436. },
  437. // 导出
  438. exportData() {
  439. this.$confirm("是否确认导出所有用户数据项?", "警告", {
  440. confirmButtonText: "确定",
  441. cancelButtonText: "取消",
  442. type: "warning",
  443. })
  444. .then(() => {
  445. this.exportLoading = true;
  446. return exportSleeptDate({date:this.currentDate, deviceId:this.deviceId});
  447. })
  448. .then((response) => {
  449. this.download(response.msg);
  450. this.exportLoading = false;
  451. })
  452. .catch(() => { });
  453. },
  454. },
  455. };
  456. </script>
  457. <style lang="scss" scoped>
  458. @mixin u-flex($flexD, $alignI, $justifyC) {
  459. display: flex;
  460. flex-direction: $flexD;
  461. align-items: $alignI;
  462. justify-content: $justifyC;
  463. }
  464. .fz15 {
  465. font-size: 15px !important;
  466. }
  467. .boldtext {
  468. font-size: 16px;
  469. color: #292929;
  470. line-height: 30px;
  471. font-weight: 700;
  472. }
  473. .box-header {
  474. width: 100%;
  475. box-sizing: border-box;
  476. padding: 0 15px;
  477. @include u-flex(row, center, space-between);
  478. height: 50px;
  479. }
  480. .box-title {
  481. color: #292929;
  482. font-size: 16px;
  483. font-family: PingFang SC-Medium;
  484. padding-left: 10px;
  485. border-left: 4px solid #2284ff;
  486. line-height: 13px;
  487. }
  488. .sleep-heart {
  489. @include u-flex(row, flex-start, flex-start);
  490. &-left {
  491. flex-shrink: 0;
  492. width: 490px;
  493. }
  494. &-num {
  495. color: #292929;
  496. font-size: 16px;
  497. }
  498. &-echarts {
  499. width: 490px;
  500. height: 250px;
  501. }
  502. &-right {
  503. flex: 1;
  504. max-width: 800px;
  505. color: #707070;
  506. font-size: 12px;
  507. }
  508. &-comment {
  509. background-color: #f4f4f4;
  510. color: #292929;
  511. padding: 10px;
  512. width: 100%;
  513. @include u-flex(row, center, center);
  514. }
  515. }
  516. .sleep-time {
  517. font-weight: 700;
  518. font-size: 16px;
  519. color: #292929;
  520. padding-left: 12px;
  521. }
  522. .sleep-echarts {
  523. margin-top: 20px;
  524. &-box {
  525. min-width: 773px;
  526. max-width: 1200px;
  527. height: 380px;
  528. }
  529. }
  530. .list {
  531. &-item {
  532. padding: 8px 12px;
  533. border-bottom: 1px solid #efefef;
  534. &-row1 {
  535. @include u-flex(row, center, space-between);
  536. }
  537. &-row1icon {
  538. width: 6px;
  539. height: 6px;
  540. border-radius: 50%;
  541. margin-right: 6px;
  542. margin-left: 20px;
  543. background-color: #93c0fa;
  544. display: inline-block;
  545. }
  546. &-row2 {
  547. margin-top: 6px;
  548. @include u-flex(row, center, space-between);
  549. &-left {
  550. margin-left: 32px;
  551. span {
  552. color: #292929;
  553. font-size: 14px;
  554. font-weight: 700;
  555. }
  556. }
  557. &-right {
  558. color: #707070;
  559. font-size: 14px;
  560. }
  561. }
  562. }
  563. }
  564. </style>