Sleep.vue 19 KB

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