dayu 2 vuotta sitten
commit
2c74720adf
100 muutettua tiedostoa jossa 14736 lisäystä ja 0 poistoa
  1. BIN
      .DS_Store
  2. BIN
      sleep/.DS_Store
  3. 59 0
      sleep/App.vue
  4. 100 0
      sleep/README.md
  5. 17 0
      sleep/api/index.js
  6. 82 0
      sleep/api/modules/home.js
  7. 9 0
      sleep/api/modules/rankList.js
  8. 67 0
      sleep/api/modules/search.js
  9. 29 0
      sleep/api/modules/singer.js
  10. 35 0
      sleep/api/modules/song.js
  11. 48 0
      sleep/api/modules/user.js
  12. 184 0
      sleep/colorui/animation.css
  13. 69 0
      sleep/colorui/components/cu-custom.vue
  14. 1226 0
      sleep/colorui/icon.css
  15. 3914 0
      sleep/colorui/main.css
  16. 153 0
      sleep/common/App.scss
  17. 130 0
      sleep/common/icon.css
  18. 71 0
      sleep/components/boxTitle/index.vue
  19. 117 0
      sleep/components/musicControl/index.vue
  20. 142 0
      sleep/components/musicControl/playList.vue
  21. 148 0
      sleep/components/search/index.vue
  22. 111 0
      sleep/components/tabBar/index.vue
  23. 35 0
      sleep/main.js
  24. 115 0
      sleep/manifest.json
  25. 105 0
      sleep/pages.json
  26. 191 0
      sleep/pages/auth/auth.vue
  27. 272 0
      sleep/pages/dayRecommend/index.vue
  28. 842 0
      sleep/pages/home/index.vue
  29. 135 0
      sleep/pages/index/components/musicList.vue
  30. 55 0
      sleep/pages/index/components/singerList.vue
  31. 98 0
      sleep/pages/index/components/songList.vue
  32. 300 0
      sleep/pages/index/index.vue
  33. 208 0
      sleep/pages/login/index.vue
  34. 1046 0
      sleep/pages/my/index.vue
  35. 399 0
      sleep/pages/play/index.vue
  36. 1577 0
      sleep/pages/report/report.vue
  37. 58 0
      sleep/pages/search/components/historySearch.vue
  38. 94 0
      sleep/pages/search/components/hotSearch.vue
  39. 52 0
      sleep/pages/search/index.vue
  40. 181 0
      sleep/pages/searchList/index.vue
  41. 233 0
      sleep/pages/singer/index.vue
  42. 283 0
      sleep/pages/singerPlayList/index.vue
  43. 276 0
      sleep/pages/songDetail/index.vue
  44. 266 0
      sleep/pages/songList/index.vue
  45. 13 0
      sleep/plugins/audio/index.js
  46. 74 0
      sleep/plugins/audio/util.js
  47. 597 0
      sleep/plugins/audio/zaudio.js
  48. 403 0
      sleep/plugins/pinyin/pinyin.js
  49. 77 0
      sleep/plugins/pinyin/toPinyin.js
  50. 40 0
      sleep/plugins/pinyin/utils.js
  51. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173118.png
  52. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173402.png
  53. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173412.png
  54. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173428.png
  55. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173448.png
  56. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173510.png
  57. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173526.png
  58. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117173539.png
  59. BIN
      sleep/preview/╬ó╨┼═╝╞¼_20220117174715.png
  60. BIN
      sleep/static/.DS_Store
  61. BIN
      sleep/static/NO1.png
  62. BIN
      sleep/static/NO2.png
  63. BIN
      sleep/static/NO3.png
  64. BIN
      sleep/static/background.png
  65. BIN
      sleep/static/home/homeBg2.png
  66. BIN
      sleep/static/home/homeBgHeart.png
  67. BIN
      sleep/static/home/homeHead.png
  68. BIN
      sleep/static/home/homeIconAdd.png
  69. BIN
      sleep/static/home/homeIconAlert.png
  70. BIN
      sleep/static/home/homeIconBreath.png
  71. BIN
      sleep/static/home/homeIconChange.png
  72. BIN
      sleep/static/home/homeIconGO.png
  73. BIN
      sleep/static/home/homeIconGift.png
  74. BIN
      sleep/static/home/homeIconHeart.png
  75. BIN
      sleep/static/home/homeIconPower.png
  76. BIN
      sleep/static/home/homeIconSet.png
  77. BIN
      sleep/static/home/homeIconWifi.png
  78. BIN
      sleep/static/home/homeReportBg.png
  79. BIN
      sleep/static/loginBg.png
  80. BIN
      sleep/static/logo.png
  81. BIN
      sleep/static/mine/myAnnouncement.png
  82. BIN
      sleep/static/mine/myAvatar.png
  83. BIN
      sleep/static/mine/myHelp.png
  84. BIN
      sleep/static/mine/myIconAdd.png
  85. BIN
      sleep/static/mine/myIconAlert.png
  86. BIN
      sleep/static/mine/myIconBuy.png
  87. BIN
      sleep/static/mine/myIconDefault.png
  88. BIN
      sleep/static/mine/myIconEdit.png
  89. BIN
      sleep/static/mine/myIconLeft.png
  90. BIN
      sleep/static/mine/myIconLink.png
  91. BIN
      sleep/static/mine/myIconMoney.png
  92. BIN
      sleep/static/mine/myIconNotDefault.png
  93. BIN
      sleep/static/mine/myIconPhone.png
  94. BIN
      sleep/static/mine/myIconPower.png
  95. BIN
      sleep/static/mine/myIconPromt.png
  96. BIN
      sleep/static/mine/myIconSetting.png
  97. BIN
      sleep/static/mine/myOrder.png
  98. BIN
      sleep/static/mine/myProduct.png
  99. BIN
      sleep/static/mine/mySecret.png
  100. 0 0
      sleep/static/mine/myState.png

BIN
.DS_Store


BIN
sleep/.DS_Store


+ 59 - 0
sleep/App.vue

@@ -0,0 +1,59 @@
+<script>
+import Vue from "vue";
+
+export default {
+  onLaunch: function () {
+    wx.getSystemInfo({
+      success: (e) => {
+        // #ifndef MP
+        Vue.prototype.StatusBar = e.statusBarHeight;
+        if (e.platform == "android") {
+          Vue.prototype.CustomBar = e.statusBarHeight + 50;
+        } else {
+          Vue.prototype.CustomBar = e.statusBarHeight + 45;
+        }
+        // #endif
+        // #ifdef MP-WEIXIN || MP-QQ
+        Vue.prototype.StatusBar = e.statusBarHeight;
+        let capsule = wx.getMenuButtonBoundingClientRect();
+        if (capsule) {
+          Vue.prototype.Custom = capsule;
+          Vue.prototype.CustomBar =
+            capsule.bottom + capsule.top - e.statusBarHeight;
+        } else {
+          Vue.prototype.CustomBar = e.statusBarHeight + 50;
+        }
+        // #endif
+
+        // #ifdef MP-ALIPAY
+        Vue.prototype.StatusBar = e.statusBarHeight;
+        Vue.prototype.CustomBar = e.statusBarHeight + e.titleBarHeight;
+        // #endif
+      },
+      fail(err) {
+        console.log(err);
+      },
+    });
+  },
+  onShow: function () {
+    console.log("App Show");
+	
+    // this.$audio.on("ended", "event-ended", (data) => {
+    //   setTimeout(() => {
+    //     this.$store.dispatch("changePlay", 1);
+    //   }, 300);
+    // });
+  },
+  onHide: function () {
+    console.log("App Hide");
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss">
+@import "@/common/App.scss";
+@import "@/common/icon.css";
+@import "@/colorui/main.css";
+@import "@/colorui/icon.css";
+</style>

+ 100 - 0
sleep/README.md

@@ -0,0 +1,100 @@
+
+  <a href="https://github.com/biubiubiu01/uni-music/">
+   <h1 align="left"> Uni Music</h1>
+  </a>
+
+## 前言
+> 2.0版本,播放功能优化,页面颜色主题改版,修复1.0存在的bug,实现多端发布。
+
+## 简介
+
+[Uni Music](https://github.com/biubiubiu01/uni-music/) 基于uniapp+colorUI 开发的音乐播放器,支持多端发布
+
+## 开发准备
+
+* 页面UI设计(千图网等)
+* uniapp一些基础使用和项目搭建
+* colorUI基础组件
+* iconfont图标库
+* 接口请求封装
+* 自定义头部
+* 自定义tabbar
+* 网易云音乐Api,感谢大佬提供的node服务: <a href="https://github.com/Binaryify/NeteaseCloudMusicApi">NeteaseCloudMusicApi</a>
+
+
+## 实现功能
+1. 用户登录
+1. 搜索
+1. 搜索建议和搜索历史记录
+1. 搜索播放
+1. 每日推荐
+1. 排行榜
+1. 热门歌手和歌手所有歌曲
+1. 推荐歌单和歌单详情
+1. 点击播放和播放全部
+1. 播放列表
+1. 背景音频播放
+1. 歌词查看
+1. 歌曲切换
+1. 歌曲进度条切换
+1. 添加喜欢音乐
+1. 播放历史记录
+1. 我喜欢的音乐
+
+## 文件目录说明
+```
+
+├── api                              ---接口  
+├── colorui                          --- colorUI  
+├── common                           ---全局样式和阿里图标 
+├── components                       ---公共组件  
+├── pages                            ---页面文件 
+│  ├── dayRecommend                  ---每日推荐    
+│  ├── index                         ---首页
+│  ├── login                         ---登录页
+│  ├── my                            ---我的
+│  ├── play                          ---歌曲详情页(歌词和进度条)
+│  ├── rankList                      ---排行榜
+│  ├── search                        ---搜索
+│  ├── searchList                    ---搜索结果
+│  ├── singer                        ---歌手列表
+│  ├── singerPlayList                ---歌手详情
+│  ├── songDetail                    ---歌单详情
+│  ├── songList                      ---歌单列表
+├── plugins                          --- pinyin.js
+├── static                           ---静态图片
+├── store                            ---vuex
+├── utils                            ---公共方法
+├── App.vue
+├── main.js
+├── manifest.json
+├── pages.json         
+├── README.md
+├── uni.scss            
+
+
+```
+# 克隆项目
+
+- git clone https://github.com/biubiubiu01/uni-music.git 
+- 使用 HBuilder X 导入项目
+- 修改login页面用户名密码(默认测试账号)
+- 运行项目
+
+
+# 项目预览
+
+
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173118.png)
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117174715.png)
+
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173402.png)
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173412.png)
+
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173428.png)
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173448.png)
+
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173510.png)
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173526.png)
+
+![输入图片说明](preview/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220117173539.png)

+ 17 - 0
sleep/api/index.js

@@ -0,0 +1,17 @@
+const home=require('./modules/home')
+const user=require('./modules/user')
+const search=require('./modules/search')
+const song=require('./modules/song')
+const rankList=require('./modules/rankList')
+const singer=require('./modules/singer')
+
+export default{
+	...home,
+	...user,
+	...search,
+	...song,
+	...rankList,
+	...singer
+}
+
+

+ 82 - 0
sleep/api/modules/home.js

@@ -0,0 +1,82 @@
+import request from '@/utils/request'
+import {getCache} from "@/utils/cache.js"
+
+/**
+ *  首页的轮播图 banner
+ *  @param  {Number} 
+ *  @return {Array}
+ */
+export const getBanner = (params) => {
+	var num = 0
+	switch (uni.getSystemInfoSync().platform) {
+		case 'android':
+			num = 1
+			break;
+		case 'ios':
+			num = 2
+			break;
+		default:
+			break;
+	}
+	return request.get(`/banner?type=${num}`, params)
+}
+
+/**
+ *  首页 发现好歌单  list
+ *  @param  {Number} 
+ *  @return {Array}
+ */
+export const getRecommendList = (params) => {
+	return request.post('/personalized', {...params,cookie: getCache('COOKIE')})
+}
+
+/**
+ *  首页 新歌 list
+ *  @return {Array}
+ */
+export const getNewSongList = () => {
+	return request.get('/personalized/newsong')
+}
+
+
+/**
+ *  首页 每日推荐歌曲 ---需要登录
+ *  @return {Array}
+ */
+export const getDayRecommendList = () => {
+	return request.get('/recommend/songs')
+}
+
+/**
+ *  首页 每日推荐歌单 ---需要登录
+ *  @return {Array}
+ */
+export const getDayRecommendMusicList = () => {
+	return request.get('/recommend/resource')
+}
+
+/**
+ *  首页 热门歌手 ---需要登录
+ *  @return {Array}
+ */
+export const getHotSingerList = () => {
+	return request.post('/top/artists', {
+		offset: 0,
+		limit: 18
+	})
+}
+
+/**
+ *  网友精选歌单
+ *  @return {Array}
+ */
+export const getSelectionData = () => {
+	return request.get('/top/playlist', {
+		limit: 10,
+		order: 'hot'
+	})
+}
+
+
+
+

+ 9 - 0
sleep/api/modules/rankList.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+/**
+ *  首页 歌手单曲
+ *  @return {Array}
+ */
+export const getRankList = () => {
+	return request.get('/toplist/detail')
+}

+ 67 - 0
sleep/api/modules/search.js

@@ -0,0 +1,67 @@
+import request from '@/utils/request'
+
+/**
+ *  默认搜索关键词
+ *  @return {Object}
+ */
+export const getDefaultSearch = (params) => {
+  return request.get('/search/default', params)
+}
+
+/**
+ *  热门搜索 详细
+ *  @return {Array}
+ */
+export const getHotDetailSearch = (params) => {
+  return request.get('/search/hot/detail', params)
+}
+
+/**
+ *  搜索建议
+ *  @param {String} keywords
+ *  @param {String} type
+ *  @return {Array}
+ */
+export const getSuggestList = (params) => {
+  let data = {
+    ...params,
+    type: 'mobile',
+  }
+  return request.get('/search/suggest', data)
+}
+
+/**
+ *  搜索
+ *  @param {String} keywords
+ *  @return {Array}
+ */
+export const getSearchList = (params) => {
+  return request.get('/cloudsearch', params)
+}
+
+/**
+ *  根据id去获取音乐url
+ *  @param  {Number} id
+ *  @return {Array}
+ */
+export const getMusicUrl = (params) => {
+  return request.get('/song/url', params)
+}
+
+/**
+ *  判断是否有版权
+ *  @param  {Number} id
+ *  @return {Array}
+ */
+export const checkMusicUrl = (params) => {
+  return request.get('/check/music', params)
+}
+
+/**
+ *  根据id去获取歌词
+ *  @param  {Number} id
+ *  @return {Array}
+ */
+export const getLyric = (params) => {
+  return request.get('/lyric', params)
+}

+ 29 - 0
sleep/api/modules/singer.js

@@ -0,0 +1,29 @@
+import request from '@/utils/request'
+
+/**
+ *  获取所有 热门歌手
+ *  @return {Array}
+ */
+export const getSingerList = () => {
+	return request.get('/toplist/artist')
+}
+
+
+/**
+ *  获取 歌手基本信息
+ *  @param {String}  id
+ *  @return {Array}
+ */
+export const getSingerInfo = (params) => {
+	return request.get('/artist/detail', params)
+}
+
+
+/**
+ *  歌手 全部歌曲
+ *  @param {String}  id
+ *  @return {Array}
+ */
+export const getSingerAllMusic = (params) => {
+	return request.get('/artist/songs', params)
+}

+ 35 - 0
sleep/api/modules/song.js

@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+
+/**
+ *  歌单 获取歌单分类标签
+ *  @return {Array}
+ */
+export const getSongTagList = (params) => {
+	return request.get('/playlist/catlist', params)
+}
+
+/**
+ *  歌单  所有歌单
+ *  @return {Array}
+ */
+export const getSongList = (params) => {
+	return request.get('/top/playlist', params)
+}
+
+/**
+ *  首页 歌单详情
+ *  @param  {string} id
+ *  @return {Array}
+ */
+export const getPlayListData = (params) => {
+	return request.get('/playlist/detail', params)
+}
+
+/**
+ *  首页 通过id获取歌曲详情  
+ *  @param  {string} ids
+ *  @return {Array}
+ */
+export const getPlayDetail = (params) => {
+	return request.get('/song/detail', params)
+}

+ 48 - 0
sleep/api/modules/user.js

@@ -0,0 +1,48 @@
+import request from '@/utils/request'
+
+/**
+ *  游客登录
+ *  @param  {Number} phone 
+ *  @param  {Number} password 
+ *  @return {Array}
+ */
+export const login = params => {
+	return request.get('/register/anonimous', params)
+}
+
+/**
+ *  获取登录状态
+ */
+export const getLoginStatus = params => {
+	return request.get('/login/status', params)
+}
+
+
+
+/**
+ *  喜欢音乐 或取消喜欢
+ */
+export const likeMusic = params => {
+	return request.get('/like', params)
+}
+
+/**
+ *  喜欢音乐列表
+ */
+export const likeData = params => {
+	return request.get('/likelist', params)
+}
+
+/**
+ *  获取用户喜欢的歌单
+ */
+export const getUserInfo = params => {
+	return request.get('/user/playlist', params)
+}
+
+/**
+ *  获取用户最近播放记录
+ */
+export const getUserHistory = params => {
+	return request.get('/user/record', params)
+}

+ 184 - 0
sleep/colorui/animation.css

@@ -0,0 +1,184 @@
+/* 
+  Animation 微动画  
+  基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
+ */
+
+/* css 滤镜 控制黑白底色gif的 */
+.gif-black{  
+  mix-blend-mode: screen;  
+}
+.gif-white{  
+  mix-blend-mode: multiply; 
+}
+
+
+/* Animation css */
+[class*=animation-] {
+    animation-duration: .5s;
+    animation-timing-function: ease-out;
+    animation-fill-mode: both
+}
+
+.animation-fade {
+    animation-name: fade;
+    animation-duration: .8s;
+    animation-timing-function: linear
+}
+
+.animation-scale-up {
+    animation-name: scale-up
+}
+
+.animation-scale-down {
+    animation-name: scale-down
+}
+
+.animation-slide-top {
+    animation-name: slide-top
+}
+
+.animation-slide-bottom {
+    animation-name: slide-bottom
+}
+
+.animation-slide-left {
+    animation-name: slide-left
+}
+
+.animation-slide-right {
+    animation-name: slide-right
+}
+
+.animation-shake {
+    animation-name: shake
+}
+
+.animation-reverse {
+    animation-direction: reverse
+}
+
+@keyframes fade {
+    0% {
+        opacity: 0
+    }
+
+    100% {
+        opacity: 1
+    }
+}
+
+@keyframes scale-up {
+    0% {
+        opacity: 0;
+        transform: scale(.2)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes scale-down {
+    0% {
+        opacity: 0;
+        transform: scale(1.8)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes slide-top {
+    0% {
+        opacity: 0;
+        transform: translateY(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes slide-bottom {
+    0% {
+        opacity: 0;
+        transform: translateY(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes shake {
+
+    0%,
+    100% {
+        transform: translateX(0)
+    }
+
+    10% {
+        transform: translateX(-9px)
+    }
+
+    20% {
+        transform: translateX(8px)
+    }
+
+    30% {
+        transform: translateX(-7px)
+    }
+
+    40% {
+        transform: translateX(6px)
+    }
+
+    50% {
+        transform: translateX(-5px)
+    }
+
+    60% {
+        transform: translateX(4px)
+    }
+
+    70% {
+        transform: translateX(-3px)
+    }
+
+    80% {
+        transform: translateX(2px)
+    }
+
+    90% {
+        transform: translateX(-1px)
+    }
+}
+
+@keyframes slide-left {
+    0% {
+        opacity: 0;
+        transform: translateX(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}
+
+@keyframes slide-right {
+    0% {
+        opacity: 0;
+        transform: translateX(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}

+ 69 - 0
sleep/colorui/components/cu-custom.vue

@@ -0,0 +1,69 @@
+<template>
+	<view>
+		<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
+			<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
+				<view class="action" @tap="BackPage" v-if="isBack">
+					<text class="cuIcon-back"></text>
+					<slot name="backText"></slot>
+				</view>
+				<view class="content" :style="[{top:StatusBar + 'px'}]">
+					<slot name="content"></slot>
+				</view>
+				<slot name="right"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				StatusBar: this.StatusBar,
+				CustomBar: this.CustomBar
+			};
+		},
+		name: 'cu-custom',
+		computed: {
+			style() {
+				var StatusBar= this.StatusBar;
+				var CustomBar= this.CustomBar;
+				var bgImage = this.bgImage;
+				var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+				if (this.bgImage) {
+					style = `${style}background-image:url(${bgImage});`;
+				}
+				return style
+			}
+		},
+		props: {
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			isBack: {
+				type: [Boolean, String],
+				default: false
+			},
+			bgImage: {
+				type: String,
+				default: ''
+			},
+		},
+		methods: {
+			BackPage() {
+				if (getCurrentPages().length < 2 && 'undefined' !== typeof __wxConfig) {
+					let url = '/' + __wxConfig.pages[0]
+					return uni.redirectTo({url})
+				}
+				uni.navigateBack({
+					delta: 1
+				});
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1226 - 0
sleep/colorui/icon.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 3914 - 0
sleep/colorui/main.css


+ 153 - 0
sleep/common/App.scss

@@ -0,0 +1,153 @@
+/* tabbar */
+.bg-tabbar {
+	background-color: #167DF2;
+}
+.tabbar {
+	.tabbar-text-color {
+		color: #ffffff;
+	}
+	.tabbar-text-active-color {
+		color: #ffffff;
+	}
+}
+.text-overflow{
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+.loading {
+	text-align: center;
+	margin-top: 10px;
+}
+
+/************************************************************
+** 请将全局样式拷贝到项目的全局 CSS 文件或者当前页面的顶部 **
+** 否则页面将无法正常显示                                  **
+************************************************************/
+
+html {
+  font-size: 16px;
+}
+
+body {
+  margin: 0;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
+    'Droid Sans', 'Helvetica Neue', 'Microsoft Yahei', sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  background-color: #000000;
+}
+
+view,
+image,
+text {
+  box-sizing: border-box;
+  flex-shrink: 0;
+}
+
+#app {
+  width: 100vw;
+  height: 100vh;
+}
+
+.flex-row {
+  display: flex;
+  flex-direction: row;
+}
+
+.flex-col {
+  display: flex;
+  flex-direction: column;
+}
+
+.justify-start {
+  justify-content: flex-start;
+}
+
+.justify-end {
+  justify-content: flex-end;
+}
+
+.justify-center {
+  justify-content: center;
+}
+
+.justify-between {
+  justify-content: space-between;
+}
+
+.justify-around {
+  justify-content: space-around;
+}
+
+.justify-evenly {
+  justify-content: space-evenly;
+}
+
+.items-start {
+  align-items: flex-start;
+}
+
+.items-end {
+  align-items: flex-end;
+}
+
+.items-center {
+  align-items: center;
+}
+
+.items-baseline {
+  align-items: baseline;
+}
+
+.items-stretch {
+  align-items: stretch;
+}
+
+.self-start {
+  align-self: flex-start;
+}
+
+.self-end {
+  align-self: flex-end;
+}
+
+.self-center {
+  align-self: center;
+}
+
+.self-baseline {
+  align-self: baseline;
+}
+
+.self-stretch {
+  align-self: stretch;
+}
+
+.flex-1 {
+  flex: 1 1 0%;
+}
+
+.flex-auto {
+  flex: 1 1 auto;
+}
+
+.grow {
+  flex-grow: 1;
+}
+
+.grow-0 {
+  flex-grow: 0;
+}
+
+.shrink {
+  flex-shrink: 1;
+}
+
+.shrink-0 {
+  flex-shrink: 0;
+}
+
+.relative {
+  position: relative;
+}

+ 130 - 0
sleep/common/icon.css

@@ -0,0 +1,130 @@
+@font-face {
+  font-family: 'iconfont';  
+  src: url('https://at.alicdn.com/t/font_2078163_1h8jaknkttj.woff2?t=1636643884430') format('woff2'),
+       url('https://at.alicdn.com/t/font_2078163_1h8jaknkttj.woff?t=1636643884430') format('woff'),
+       url('https://at.alicdn.com/t/font_2078163_1h8jaknkttj.ttf?t=1636643884430') format('truetype');
+}
+.iconfont {
+	font-family: "iconfont" !important;
+	font-size: 16px;
+	font-style: normal;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+}
+
+[class*="icon-"] {
+	font-family: "iconfont";
+	font-size: inherit;
+	font-style: normal;
+}
+
+.icon-xihuan:before {
+	content: "\e60a";
+}
+
+.icon-paihang:before {
+	content: "\e719";
+}
+
+.icon-maikefeng:before {
+	content: "\e60c";
+}
+
+.icon-remen:before {
+	content: "\e7a5"; 
+}
+
+
+.icon-kaishi:before {
+	content: "\e781";
+}
+
+.icon-kaishi2:before {
+	content: "\e600";
+}
+
+.icon-kaishi3:before {
+	content: "\e69b";
+}
+
+.icon-right:before {
+	content: "\e634";
+}
+
+.icon-liebiao:before {
+	content: "\e601";
+}
+
+.icon-playStart:before {
+	content: "\e602";
+}
+
+.icon-playStop:before {
+	content: "\e685";
+}
+
+.icon-stop:before {
+	content: "\e669";
+}
+
+.icon-play-left:before {
+	content: "\e610";
+}
+
+.icon-play-right:before {
+	content: "\e611";
+}
+.icon-play:before {
+	content: "\e608";
+}
+.icon-pause:before {
+	content: "\e635";
+}
+.icon-xunhuan:before {
+	content: "\e617";
+}
+
+.icon-shoucang:before {
+	content: "\e60d";
+}
+
+.icon-like:before {
+	content: "\e6c8";
+}
+
+.icon-unlike:before {
+	content: "\e609";
+}
+
+.icon-phone:before {
+	content: "\e6c7";
+}
+
+.icon-password:before {
+	content: "\e620";
+}
+
+.icon-closeEye:before {
+	content: "\e603";
+}
+
+.icon-openEye:before {
+	content: "\e604";
+}
+
+.icon-weixin:before {
+	content: "\e73b";
+}
+
+.icon-qq:before {
+	content: "\e73e";
+}
+
+.icon-weibo:before {
+	content: "\e73c";
+}
+
+
+
+
+

+ 71 - 0
sleep/components/boxTitle/index.vue

@@ -0,0 +1,71 @@
+<template>
+	<view class="box-tile" :class="className">
+		<text class="title-wrapper">{{ title }}</text>
+		<text class="more" @click="$emit('handlePlay')" v-if="iconName">
+			<text>{{ buttonName }}</text>
+			<text :class="['iconfont icon-title icon-' + iconName]"  v-if="iconName"></text>
+		</text>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		title: {
+			type: String,
+			default: ''
+		},
+		iconName: {
+			type: String,
+			default: ''
+		},
+		buttonName: {
+			type: String,
+			default: ''
+		},
+		className: {
+			type: String,
+			default: ''
+		}
+	}
+};
+</script>
+<style lang="scss" scoped>
+.box-tile {
+	box-sizing: border-box;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding-left: 3px;
+	
+	.title-wrapper {
+		font-size: 18px;
+		font-weight: 500;
+		line-height: 18px;
+		color: #ffffff;
+		position: relative;
+		text-indent: 2px;
+		// &:before {
+		// 	content: '';
+		// 	width: 4px;
+		// 	height: 18px;
+		// 	background-image: linear-gradient(to bottom, rgb(253, 117, 102), rgb(247, 73, 79));
+		// 	position: absolute;
+		// 	left: 0px;
+		// 	top: 3px;
+		// }
+	}
+	.more {
+		border-radius: 9px;
+		color:  rgba(22, 125, 242, 1);;
+		padding: 3px 10px 3px 13px;
+		font-size: 12px;
+		border: 1px solid rgba(22, 125, 242, 1);
+	}
+	.icon-title {
+		margin-left: 2px;
+		color: rgba(22, 125, 242, 1);;
+		font-size: 12rpx;
+	}
+}
+</style>

+ 117 - 0
sleep/components/musicControl/index.vue

@@ -0,0 +1,117 @@
+<template>
+	<view class="radius shadow fotter-container"  v-if="playInfo">
+		<view class="fixed-container" @click.stop="toSongDetail">
+			<view class="cu-avatar playImage round" :style="'background-image:url(' + playInfo.coverImgUrl + ')'"></view>
+			<view class="play-center">
+				<view class="music-name">{{ playInfo.title }}</view>
+				<!-- <view class="music-author">{{ playInfo.singer }}</view> -->
+			</view>
+			<view class="play-right">
+				<view class="play-list relative" @click.stop="changePlaying">
+					<text :class="['iconfont', !paused ? 'icon-playStop' : 'icon-playStart']" style="font-size: 28px"></text>
+				</view>
+				<text class="iconfont icon-liebiao play-list" @click.stop="modelShow = true"></text>
+			</view>
+		</view>
+		<play-list v-model="modelShow"></play-list>
+	</view>
+</template>
+
+<script>
+import playList from './playList.vue';
+import { mapState } from 'vuex';
+export default {
+	data() {
+		return {
+			modelShow: false
+		};
+	},
+	components: {
+		playList
+	},
+	computed: mapState({
+		playInfo: state => state.playInfo,
+		paused: state => state.paused
+	}),
+	methods: {
+		//修改播放状态
+		changePlaying() {
+			let list = this.$audio.audiolist;
+			let index = list.findIndex(item => item.id == this.playInfo.id);
+			this.$audio.operate(index);
+			this.$store.commit('SET_PASUED', !this.paused);
+		},
+
+		toSongDetail() {
+			if (this.modelShow) return;
+			uni.navigateTo({
+				url: '../play/index'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.fotter-container {
+	height: 110rpx;
+	width: 100%;
+	position: fixed;
+	bottom: 50px;
+	left: 0;
+	z-index: 9999;
+
+	.fixed-container {
+		position: fixed;
+		z-index: 20;
+		width: 100%;
+		height: 110rpx;
+		bottom: 50px;
+		// box-shadow: 0 1px 2px rgb(0, 21, 0.1);
+		display: flex;
+		background: rgba(22, 125, 242, 1);;
+	}
+
+	.playImage {
+		width: 80rpx;
+		height: 80rpx;
+		margin: auto 15px auto 10px;
+	}
+
+	.play-center {
+		flex: auto;
+		display: flex;
+		flex-wrap: wrap;
+		align-content: center;
+		max-width: calc(100% - 300rpx);
+
+		.music-name {
+			color: #ffffff;
+			font-size: 32rpx;
+			margin-bottom: 4px;
+			width: 100%;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+		}
+
+		.music-author {
+			font-size: 24rpx;
+			color: rgba(0, 0, 0, 0.5);
+			width: 100%;
+		}
+	}
+
+	.play-right {
+		width: 92px;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+
+		.play-list {
+			font-size: 30px;
+			margin-right: 12px;
+		}
+	}
+}
+</style>

+ 142 - 0
sleep/components/musicControl/playList.vue

@@ -0,0 +1,142 @@
+<template>
+	<view class="cu-modal bottom-modal" :class="{ show: value }" @click.self="$emit('input', false)" :key="number">
+		<view class="cu-dialog play-list-dialog">
+			<view class="cu-bar bg-white list-title">
+				播放列表
+				<text class="light-text">(共{{ playList.length }}首)</text>
+			</view>
+			<scroll-view class="bg-white play-list " scroll-y style="height:65vh">
+				<view class="music-item flex" :class="{ active: item.id == playInfo.id }" v-for="(item, index) in playList" :key="item.id" @click="startPlayInfo(item)">
+					<image :src="item.coverImgUrl" class="music-img"></image>
+					<view class="music-info">
+						<view class="music-name text-overflow">{{ item.title }}</view>
+						<view class="music-singer text-overflow flex">{{ item.singer }}</view>
+					</view>
+					<text class="lg basic-icon-color cuIcon-deletefill" style="font-size: 18px;" @click.stop="removePlayList(item)"></text>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getCache } from '@/utils/cache.js';
+import { mapState } from 'vuex';
+export default {
+	props: {
+		value: {
+			type: Boolean,
+			default: false
+		}
+	},
+	data() {
+		return {
+			playList: this.$audio.audiolist,
+			number:Math.random()*100000
+		};
+	},
+	created() {
+		this.$audio.on('setAudio', 'event-a', list => {
+			this.playList = list;
+			this.number=Math.random()*100000
+		});
+	},
+	computed: mapState({
+		playInfo: state => state.playInfo,
+		paused: state => state.paused
+	}),
+	methods: {
+		async removePlayList(val) {
+			if (this.playList.length == 1) {
+				this.$audio.stop();
+				//如果是音乐详情页,那就返回首页
+				this.$emit('backHome');
+			} else {
+				//如果删除的歌曲是正在播放的歌曲
+				if (this.playInfo.id == val.id) {
+					this.$store.dispatch('changePlay', 1);
+				}
+			}
+			this.$store.dispatch('removeMusic', val);
+		},
+
+		async startPlayInfo(item) {
+			if (item.id == this.playInfo.id) return;
+			this.$store.dispatch('playMusic', item);
+		}
+	},
+	destroyed() {
+		this.$audio.off('setAudio', 'event-a');
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.play-list-dialog {
+	border-top-left-radius: 26px !important;
+	border-top-right-radius: 26px !important;
+	.list-title {
+		font-size: 32rpx;
+		color: #000;
+		justify-content: center;
+		.light-text {
+			font-size: 24rpx;
+			margin-left: 6px;
+			color: rgba(0, 0, 0, 0.5);
+		}
+	}
+	.play-list {
+		.music-item {
+			height: 60px;
+			box-sizing: border-box;
+			padding: 0 20px;
+			align-items: center;
+			margin-bottom: 5px;
+			position: relative;
+		
+			&.active {
+				background-image: linear-gradient(to right, rgba(247, 73, 79, 0.1), rgba(247, 73, 79, 0.05));
+				.music-info {
+					.music-name,
+					.music-singer {
+						color: #f84e51 !important;
+					}
+					&::before {
+						content: '';
+						width: 4px;
+						height: 55px;
+						background-image: linear-gradient(to bottom, rgb(253, 117, 102), rgb(247, 73, 79));
+						position: absolute;
+						left: 0px;
+						top: 3px;
+					}
+				}
+			}
+			&:last-of-type {
+				margin-bottom: 0;
+			}
+			.music-img {
+				width: 45px !important;
+				height: 45px !important;
+				border-radius: 6px;
+			}
+			.music-info {
+				margin-right: 15px;
+				margin-left: 15px;
+				width: calc(100vw - 135px);
+				text-align: left;
+				.music-name {
+					font-size: 30rpx;
+					margin-bottom: 5px;
+					color: #000;
+				}
+				.music-singer {
+					color: rgba(0, 0, 0, 0.5);
+					font-size: 24rpx;
+					align-items: center;
+				}
+			}
+		}
+	}
+}
+</style>

+ 148 - 0
sleep/components/search/index.vue

@@ -0,0 +1,148 @@
+<template>
+	<view class="cu-bar search">
+		<view class="search-form round">
+			<text class="cuIcon-search"></text>
+			<input type="text" class="input-wrapper relative" :placeholder="defaultKeywords" confirm-type="search" v-model="keywords" @input="debounceSuggest" @confirm="handleSearch"  @focus="getSuggestList" >
+			   <text class="cuIcon-close closeIcon" v-show="keywords" @click="keywords=''" ></text>
+			</input>
+		</view>
+		<view class="suggestList" v-show="keywords&&suggetShow" @click.self="handleClose">
+			<view class="suggestMain">
+				<view class="suggest-item" style="color:rgb(86,124,166)" @click="searchMusic(keywords)">
+					搜索 " {{keywords}} "
+				</view>
+				<view class="suggest-item" v-for="(item,index) in suggestList" :key="index" @click="searchMusic(item.keyword)">
+					 <text class="cuIcon-search" style="margin-right: 25rpx;"></text>
+					 {{item.keyword}}
+				</view>
+			</view>
+			
+		</view>
+	</view>
+</template>
+
+<script>
+	import {debounce} from "@/utils/index.js"
+	export default{
+		props:{
+			text:{
+				type:String,
+				default:''
+			}
+		},
+		data(){
+			return{
+				keywords:'',
+				defaultKeywords:'',
+				debounceSuggest:null,
+				suggestList:[], //搜索建议list
+				suggetShow:false
+			}
+	    },
+		created(){
+			this.debounceSuggest=debounce(() => {
+			    this.getSuggestList()
+			}, 500)
+			if(!this.text){
+				this.getDefaultSearch()
+			}else{
+				this.defaultKeywords=this.text
+			}
+			
+		},
+		methods:{
+			//获取默认搜索关键词
+			getDefaultSearch(){
+				this.$api.getDefaultSearch().then(res=>{
+					this.defaultKeywords=res.data.realkeyword||''
+				})
+			},
+			
+			//关闭搜索建议
+			handleClose(){
+				this.suggestList=[]
+				this.suggetShow=false
+			},
+			
+			//获取搜索建议
+			getSuggestList(){
+				if(!this.keywords.trim()){
+				  return 
+				}
+				this.suggetShow=true
+				this.$api.getSuggestList({keywords:this.keywords.trim()}).then(res=>{
+					 this.suggestList=res.result.allMatch||[]
+				})
+			},
+			
+			//点击搜索建议
+			searchMusic(val){
+				this.keywords=val
+				this.suggetShow=false
+				this.handleSearch()
+				
+			},
+			
+			//点击搜索
+			handleSearch(){
+				if(!this.keywords.trim()){
+				   this.keywords=this.defaultKeywords
+				} 
+				this.suggetShow=false
+				this.$store.dispatch('addHistoryList',this.keywords)
+				this.$emit('handleSearch',this.keywords)
+			}
+		},
+		watch:{
+			text(nl){
+				this.keywords=nl
+			},
+			
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+
+	.closeIcon{
+		position: absolute;
+		right: 20px;
+		top:9px;
+		font-size: 36rpx;
+		z-index: 20;
+	}
+	.suggestList{
+		background-color: rgba(255,255,255,0.1);
+		z-index: 25;
+		position: fixed;
+		left: 0;
+		right: 0;
+		top: 190rpx;
+		bottom: 0;
+		.suggestMain{
+			position: absolute;
+			left: 25px;
+			right: 25px;
+			top: 0;
+			z-index: 30;
+			background-color: #fff;
+			border: 1px solid #bebebe;
+			border-radius: 4px;
+			box-shadow: 0 4px 7px #aaa;
+			text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9);
+			box-sizing: border-box;
+			padding: 0 25rpx;
+			.suggest-item{
+				padding: 24rpx 0;
+				border-bottom: 1upx solid rgba(0, 0, 0, 0.1);
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				box-sizing: border-box;
+				width: 95%;
+			}
+		}
+		
+
+	}
+</style>

+ 111 - 0
sleep/components/tabBar/index.vue

@@ -0,0 +1,111 @@
+<template>
+	<view>
+		<view class="cu-bar tabbar bg-tabbar shadow foot">
+			<view class="action" @click="handleToHome">
+				<view class="cuIcon-cu-image"><image :src="'../../static/tabBar/' + [currentPage == 'home' ? 'homeSel' : 'home'] + '.png'"></image></view>
+				<view :class="currentPage == 'home' ? 'tabbar-text-active-color' : 'tabbar-text-color'" style="margin-top:2px">首页</view>
+			</view>
+			<view class="action" @click="handleToReport">
+				<view class="cuIcon-cu-image"><image :src="'../../static/tabBar/' + [currentPage == 'report' ? 'reportSel' : 'report'] + '.png'"></image></view>
+				<view :class="currentPage == 'report' ? 'tabbar-text-active-color' : 'tabbar-text-color'" style="margin-top:2px">报告</view>
+			</view>
+			<view class="action" @click="handleToDiscovery">
+				<view class="cuIcon-cu-image"><image :src="'../../static/tabBar/' + [currentPage == 'index' ? 'discoverSel' : 'discover'] + '.png'"></image></view>
+				<view :class="currentPage == 'index' ? 'tabbar-text-active-color' : 'tabbar-text-color'" style="margin-top:2px">发现</view>
+			</view>
+			<view class="action"  @click="handleToMy">
+				<view class="cuIcon-cu-image"><image :src="'../../static/tabBar/' + [currentPage == 'my' ? 'mySel' : 'my'] + '.png'"></image></view>
+				<view :class="currentPage == 'my' ? 'tabbar-text-active-color' : 'tabbar-text-color'" style="margin-top:2px">我的</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { mapState } from 'vuex';
+export default {
+	props: {
+		currentPage: {
+			type: String,
+			default: 'home'
+		}
+	},
+	computed: mapState({
+		playInfo: state => state.playInfo,
+		paused: state => state.paused
+	}),
+	data() {
+		return {
+			time: null,
+			rotate: 0
+		};
+	},
+	created() {
+		this.init();
+	},
+	methods: {
+		init() {
+			if (!this.paused) {
+				this.initRotate();
+			} else {
+				clearInterval(this.timer);
+			}
+		},
+		handleToPlay() {
+			uni.navigateTo({
+				url: '/pages/play/index'
+			});
+		},
+		
+		handleToDiscovery() {
+			uni.navigateTo({
+				url: '/pages/index/index'
+			});
+		},
+		handleToReport() {
+			uni.navigateTo({
+				url: '/pages/report/report'
+			});
+		},
+		handleToMy(){
+			uni.navigateTo({
+				url: '/pages/my/index'
+			});
+		},
+		
+		handleToHome(){
+			uni.navigateTo({
+				url: '/pages/home/index'
+			});
+		},
+
+		initRotate() {
+			this.timer = setInterval(() => {
+				this.rotate += 15;
+			}, 1000);
+		}
+	},
+	watch: {
+		paused() {
+			this.init();
+		}
+	}
+};
+</script>
+<style lang="scss" scoped>
+.music-img {
+	position: absolute;
+	width: 35px;
+	z-index: 2;
+	height: 35px;
+	border-radius: 50%;
+	line-height: 35px;
+	font-size: 25px;
+	top: -17px;
+	left: 0;
+	right: 0;
+	margin: auto;
+	padding: 0;
+	transition: all 1s linear;
+}
+</style>

+ 35 - 0
sleep/main.js

@@ -0,0 +1,35 @@
+import Vue from 'vue'
+import App from './App'
+import store from './store'
+
+Vue.config.productionTip = false
+
+App.mpType = 'app'
+
+
+import api from "@/api/index"
+Vue.prototype.$api = api;
+
+import musicControl from "@/components/musicControl/index.vue"
+Vue.component('music-control', musicControl)
+
+import boxTitle from "@/components/boxTitle/index.vue"
+Vue.component('box-title', boxTitle)
+
+import tabBar from "@/components/tabBar/index.vue"
+Vue.component('tab-bar', tabBar)
+
+import cuCustom from './colorui/components/cu-custom.vue'
+Vue.component('cu-custom', cuCustom)
+
+import {audio} from '@/plugins/audio/index.js'
+Vue.prototype.$audio =audio
+
+import {UniSteps} from './uni_modules/uni-steps/components/uni-steps/uni-steps.vue'
+Vue.component('uni-steps', UniSteps)
+
+const app = new Vue({
+	...App,
+	store
+})
+app.$mount()

+ 115 - 0
sleep/manifest.json

@@ -0,0 +1,115 @@
+{
+    "name" : "sleep",
+    "appid" : "",
+    "description" : "爱上听音乐",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "modules" : {},
+        "distribute" : {
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            "ios" : {
+                "UIBackgroundModes" : [ "audio" ]
+            },
+            "sdkConfigs" : {},
+            "icons" : {
+                "android" : {
+                    "mdpi" : "unpackage/res/icons/48x48.png",
+                    "ldpi" : "unpackage/res/icons/48x48.png",
+                    "hdpi" : "unpackage/res/icons/72x72.png",
+                    "xhdpi" : "unpackage/res/icons/96x96.png",
+                    "xxhdpi" : "unpackage/res/icons/144x144.png",
+                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+                },
+                "ios" : {
+                    "appstore" : "unpackage/res/icons/1024x1024.png",
+                    "ipad" : {
+                        "app" : "unpackage/res/icons/76x76.png",
+                        "app@2x" : "unpackage/res/icons/152x152.png",
+                        "notification" : "unpackage/res/icons/20x20.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "proapp@2x" : "unpackage/res/icons/167x167.png",
+                        "settings" : "unpackage/res/icons/29x29.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "spotlight" : "unpackage/res/icons/40x40.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone" : {
+                        "app@2x" : "unpackage/res/icons/120x120.png",
+                        "app@3x" : "unpackage/res/icons/180x180.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "notification@3x" : "unpackage/res/icons/60x60.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "settings@3x" : "unpackage/res/icons/87x87.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
+                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    }
+                }
+            }
+        }
+    },
+    "quickapp" : {},
+    "mp-weixin" : {
+        "appid" : "",
+        "requiredBackgroundModes" : [ "audio" ],
+        "setting" : {
+            "urlCheck" : true,
+            "es6" : false,
+            "postcss" : true,
+            "minified" : true,
+            "requiredBackgroundModes" : [ "audio" ]
+        },
+        "usingComponents" : true,
+        "permission" : {}
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "h5" : {
+        "router" : {
+            "base" : "./"
+        }
+    },
+    "vueVersion" : "2"
+}

+ 105 - 0
sleep/pages.json

@@ -0,0 +1,105 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				"enablePullDownRefresh": true
+			}
+		}, 
+		{
+			"path": "pages/report/report",
+			"style": {
+				"enablePullDownRefresh": true
+			}
+		}, 
+		{
+			"path": "pages/login/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/my/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/search/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/searchList/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/play/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/songDetail/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/dayRecommend/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/songList/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},{
+			"path": "pages/singer/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},{
+			"path": "pages/home/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		},
+		{
+			"path": "pages/singerPlayList/index",
+			"style": {
+				"enablePullDownRefresh": false
+			}
+		}
+	    ,{
+            "path" : "pages/report/report",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+        ,{
+            "path" : "pages/auth/auth",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+    ],
+	"globalStyle": {
+		"navigationStyle": "custom",
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "",
+		"navigationBarBackgroundColor": "#ffffff",
+		"backgroundColor": "#ffffff"
+	}
+}

+ 191 - 0
sleep/pages/auth/auth.vue

@@ -0,0 +1,191 @@
+<template>
+	<view class="flex-col section">
+	  <cu-custom bgColor="#fff"><view slot="content" style="color: #FFFFFF">UU睡眠</view></cu-custom>
+	  
+	  <view class="flex-col items-center group_3 space-y-20">
+	    <text class="text_2 text_3">UU睡眠</text>
+	    <text class="font_1 text_2">AI时代,全家人的睡眠健康顾问</text>
+	  </view>
+	  <view class="flex-col group_4 space-y-20">
+	    <text class="font_2 text_4">为了保证用户安全和更好的用户体验,请确认授权以下信息</text>
+	    <view class="flex-row items-center group_5 space-x-6">
+	      <view class="section_5"></view>
+	      <text class="font_3 text_5">获取你的昵称、头像、地区、性别</text>
+	    </view>
+	    <view class="flex-row items-center group_5 space-x-6">
+	      <view class="section_5"></view>
+	      <text class="font_3 text_5">获取你的手机号码</text>
+	    </view>
+	  </view>
+	  <view class="flex-col justify-start items-center self-end button"><text class="font_2 text_6">确认授权</text></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style lang="scss">
+.section {
+  padding-bottom: 20.5rem;
+  backdrop-filter: blur(0.19rem);
+  background-image: url('https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/649415135a7e3f0310661c1e/649415b654fe0000116ae544/16876231037161018927.png');
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  .section_2 {
+    padding: 1.13rem 0.44rem 0.38rem;
+    background-color: #ffffff4d;
+    .group {
+      padding: 0 0.5rem;
+      .image {
+        width: 1.06rem;
+        height: 0.69rem;
+      }
+      .image_2 {
+        width: 0.94rem;
+        height: 0.69rem;
+      }
+      .image_3 {
+        width: 1.5rem;
+        height: 0.72rem;
+      }
+    }
+    .space-x-6-reverse {
+      & > view:not(:last-child),
+      & > text:not(:last-child),
+      & > image:not(:last-child) {
+        margin-right: 0.38rem;
+      }
+    }
+    .group_2 {
+      padding: 0.63rem 0 0.38rem;
+      .text {
+        font-size: 1.16rem;
+      }
+      .section_3 {
+        padding: 0.38rem 0.75rem;
+        background-color: #ffffff99;
+        border-radius: 1rem;
+        border: solid 0.031rem #00000014;
+        .image-wrapper {
+          width: 1.19rem;
+          .image_5 {
+            width: 1.19rem;
+            height: 0.44rem;
+          }
+          .image_4 {
+            width: 1.13rem;
+            height: 1.06rem;
+          }
+        }
+        .section_4 {
+          background-color: #00000033;
+          width: 0.063rem;
+          height: 1.16rem;
+        }
+      }
+      .space-x-11 {
+        & > view:not(:first-child),
+        & > text:not(:first-child),
+        & > image:not(:first-child) {
+          margin-left: 0.69rem;
+        }
+      }
+      .pos {
+        position: absolute;
+        right: 0;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+    }
+  }
+  .space-y-22 {
+    & > view:not(:first-child),
+    & > text:not(:first-child),
+    & > image:not(:first-child) {
+      margin-top: 1.38rem;
+    }
+  }
+  .group_3 {
+    padding: 4rem 0;
+    .text_2 {
+      opacity: 0.76;
+    }
+    .text_3 {
+      color: #ffffff;
+      font-size: 1.88rem;
+      font-family: PingFangSC;
+      line-height: 1.72rem;
+    }
+  }
+  .space-y-20 {
+    & > view:not(:first-child),
+    & > text:not(:first-child),
+    & > image:not(:first-child) {
+      margin-top: 1.25rem;
+    }
+  }
+  .font_1 {
+    font-size: 1.13rem;
+    font-family: PingFangSC;
+    line-height: 1.06rem;
+    color: #ffffff;
+  }
+  .group_4 {
+    margin: 0.25rem 1.25rem 0;
+    padding-top: 1.38rem;
+    border-top: solid 0.063rem #ffffff4d;
+    .text_4 {
+      margin: 0 0.5rem;
+      line-height: 1.19rem;
+    }
+    .group_5 {
+      padding: 0 0.44rem;
+      .section_5 {
+        background-color: #ffffff99;
+        border-radius: 50%;
+        width: 0.13rem;
+        height: 0.13rem;
+      }
+      .font_3 {
+        font-size: 0.75rem;
+        font-family: PingFangSC;
+        line-height: 0.69rem;
+        color: #ffffff;
+      }
+      .text_5 {
+        opacity: 0.6;
+      }
+    }
+    .space-x-6 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-left: 0.38rem;
+      }
+    }
+  }
+  .button {
+    margin-right: 0.81rem;
+    margin-top: 2.5rem;
+    padding: 0.5rem 0;
+    background-image: linear-gradient(90deg, #0a1ecf 0%, #5611f7f5 100%);
+    border-radius: 1rem;
+    width: 20.94rem;
+    .text_6 {
+      line-height: 0.94rem;
+    }
+  }
+  .font_2 {
+    font-size: 1rem;
+    font-family: PingFangSC;
+    color: #ffffff;
+  }
+}
+</style>

+ 272 - 0
sleep/pages/dayRecommend/index.vue

@@ -0,0 +1,272 @@
+<template>
+	<view class="day-recommend-container">
+		<view class="vague-wrapper flex align-center">
+			<image src="../../static/background.png" class="background-img"></image>
+			<view class="absolve-wrapper">
+				<cu-custom class="head-title" :isBack="true" bgColor="unset"><block slot="content">每日推荐</block></cu-custom>
+				<view class="day-recommend-info" :style="{ 'padding-top': CustomBar + 'px' }">
+					<image :src="bgImg" mode="widthFix" class="bgImg"></image>
+					<view class="day-info">
+						<text class="bold-text">{{ day }}</text>
+						<text class="small-text">/{{ month }}</text>
+						<view class="descript">生如蝼蚁当立鸿鹄之志,命薄似纸应有不屈之心,乾坤未定,你我皆是黑马</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="recommend-main" :class="{ hasPlayInfo: playInfo.id }">
+			<view class="recommend-list">
+				<view class="music-title" @click="handlePlayAllMusic">
+					<text class="iconfont icon-kaishi3 basic-icon-color playIcon"></text>
+					全部播放
+					<text class="light-text">(共{{ dayRecommendList.length }}首)</text>
+				</view>
+				<scroll-view scroll-y scroll-with-animation style="height: calc(100% - 55px)">
+					<view
+						class="music-item flex"
+						:class="{ active: item.id == playInfo.id }"
+						v-for="(item, index) in dayRecommendList"
+						:key="item.id"
+						@click="handlePlayMusic(item)"
+					>
+						<image :src="item.al.picUrl + '?param=60y60'" mode="widthFix" class="music-img"></image>
+						<view class="music-info">
+							<view class="music-name text-overflow">{{ item.name }}</view>
+							<view class="music-singer text-overflow flex">
+								<span class="small-icon">{{ item.id % 2 == 0 ? 'SQ' : 'HD' }}</span>
+								{{ item.ar ? item.ar.map(item => item.name).join('/') : '' }}
+							</view>
+						</view>
+					</view>
+				</scroll-view>
+			</view>
+		</view>
+		<music-control v-if="playInfo.id" />
+	</view>
+</template>
+
+<script>
+import { getImage, getName } from '@/utils/index.js';
+import { getMonth, getDay } from '@/utils/date.js';
+import { mapState } from 'vuex';
+export default {
+	data() {
+		return {
+			dayRecommendList: [],
+			bgImg: '',
+			day: getDay(),
+			month: getMonth(),
+			CustomBar:this.CustomBar-15
+		};
+	},
+	computed: mapState({
+		playInfo: state => state.playInfo
+	}),
+	created() {
+		this.getDayRecommendData();
+	},
+	methods: {
+		//获取猜你喜欢歌曲
+		async getDayRecommendData() {
+			const { data } = await this.$api.getDayRecommendList();
+			this.dayRecommendList = data.dailySongs || [];
+			if (data.dailySongs.length > 0) {
+				this.bgImg = this.dayRecommendList[0].al.picUrl + 'param?300y300';
+			}
+		},
+
+		//点击播放
+		handlePlayMusic(val) {
+			if (this.playInfo.id == val.id) {
+				uni.navigateTo({
+					url: '../play/index'
+				});
+				return;
+			}
+			this.$store.dispatch('playMusic', {
+				src: '',
+				title: val.name,
+				singer: getName(val),
+				coverImgUrl: getImage(val),
+				id: val.id
+			});
+		},
+
+		//播放全部
+		handlePlayAllMusic() {
+			const list = this.dayRecommendList.map(item => {
+				return {
+					src: '',
+					title: item.name,
+					singer: getName(item),
+					coverImgUrl: getImage(item),
+					id: item.id
+				};
+			});
+			this.$store.dispatch('playAllMUsic', list);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.day-recommend-container {
+	width: 100%;
+	height: 100%;
+	.vague-wrapper {
+		height: 30%;
+		width: 100%;
+		position: relative;
+		.background-img{
+		   width: 100%;
+		   height: 100%;
+		}
+		.absolve-wrapper{
+			position: absolute;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			top: 0;
+			width: 100%;
+			height: 100%;
+			background-color: rgba(0,0,0,0.5);
+		}
+		.head-title {
+			color: #fff;
+			position: absolute;
+			top: 0;
+			width: 100%;
+			z-index: 9999;
+		}
+		.day-recommend-info {
+			box-sizing: border-box;
+			height: 100%;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			width: 100%;
+			.bgImg {
+				width: 200rpx;
+				border-radius: 8px;
+				margin-right: 20px;
+			}
+			.day-info {
+				margin-bottom: 12rpx;
+				color: #fff;
+				max-width: calc(100% - 175px);
+				.small-text {
+					font-size: 42rpx;
+				}
+				.bold-text {
+					font-size: 60rpx;
+					font-weight: bold;
+					margin-right: 3px;
+				}
+				.descript {
+					font-size: 24rpx;
+					color: #e1d7f0;
+					margin-top: 17rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					display: -webkit-box;
+					-webkit-box-orient: vertical;
+					-webkit-line-clamp: 2;
+					line-height: 18px;
+				}
+			}
+		}
+	}
+
+	.recommend-main {
+		height: 70%;
+		width: 100%;
+		position: relative;
+		&.hasPlayInfo {
+			height: calc(70% - 110rpx);
+		}
+		.recommend-list {
+			position: absolute;
+			top: -5%;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: #fff;
+			border-top-left-radius: 26px;
+			border-top-right-radius: 26px;
+			.music-title {
+				padding-left: 20px;
+				margin: 15px 0;
+				box-sizing: border-box;
+				color: #000;
+				font-size: 32rpx;
+				font-weight: 600;
+				.playIcon {
+					margin-right: 8px;
+					font-size: 40rpx;
+				}
+				.light-text {
+					font-size: 24rpx;
+					margin-left: 6px;
+					color: rgba(0, 0, 0, 0.5);
+				}
+			}
+			.music-item {
+				height: 70px;
+				box-sizing: border-box;
+				padding: 0 20px;
+				align-items: center;
+				margin-bottom: 10px;
+				position: relative;
+				&:last-of-type {
+					margin-bottom: 0;
+				}
+				&.active {
+					background-image: linear-gradient(to right, rgba(247, 73, 79, 0.1), rgba(247, 73, 79, 0.05));
+					.music-info {
+						.music-name,
+						.small-icon,
+						.music-singer {
+							color: #f84e51 !important;
+						}
+						&::before {
+							content: '';
+							width: 4px;
+							height: 65px;
+							background-image: linear-gradient(to bottom, rgb(253, 117, 102), rgb(247, 73, 79));
+							position: absolute;
+							left: 0px;
+							top: 3px;
+						}
+					}
+				}
+				.music-img {
+					width: 58px;
+					border-radius: 6px;
+				}
+				.music-info {
+					margin-left: 15px;
+					.music-name {
+						font-size: 30rpx;
+						margin-bottom: 7px;
+						color: #000;
+					}
+					.music-singer {
+						color: rgba(0, 0, 0, 0.5);
+						font-size: 24rpx;
+						align-items: center;
+						.small-icon {
+							margin-right: 6px;
+							transform: scale(0.9);
+							color: rgba(0, 0, 0, 0.5);
+							font-size: 12px;
+							padding: 1px 3px;
+							border: 1px solid rgba(0, 0, 0, 0.2);
+							border-radius: 4px;
+						}
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 842 - 0
sleep/pages/home/index.vue

@@ -0,0 +1,842 @@
+<template>
+	<view class="flex-col justify-start home-container">
+		<view class="container-bg"></view>
+		<cu-custom bgColor="#fff"><view slot="content" style="color: #FFFFFF">UU睡眠</view></cu-custom>
+		
+		<view class="flex-col home-module space-y-20">
+			
+			<!-- no device added -->
+			<view v-if="!deviceAdded">
+				<view class="flex-col items-start home-header space-y-10">
+					<text class="home-header-title">欢迎回来!</text>
+					<text class="font_3 text_3 home-header-subtitle">我是小云,您的健康智慧管家。</text>
+				</view>
+				
+				<view class="flex-row items-center home_add_devive space-x-10">
+				  <image
+				    class="image_4"
+					src="../../static/home/homeIconAdd.png"
+				  />
+				  <text class="text_5">添加设备</text>
+				</view>
+				
+				<view class="home-ad flex-col group_5 space-y-40">
+					<view class="flex-col home-header space-y-6">
+						<view class="flex-row items-center space-x-8">
+							<text class="font_4 text_8">UU AI智能睡眠监测仪</text>
+							<view class="flex-row items-center section_7">
+								<text class="font_5 text_9">去看看</text>
+								<image
+									class="shrink-0 image_9"
+									src="../../static/home/homeIconGO.png"
+								/>
+							</view>
+						</view>
+						
+						<view class="flex-row items-center space-x-10">
+							<text class="font_5 text_3 text_10">AI时代,全家人的睡眠健康顾问</text>
+						</view>
+					</view>			
+				</view>
+				
+				<view class="flex-col">
+				  <view class="flex-col group_5">		  
+					<view class="flex-col home_blank_heart">
+				      <view class="flex-row items-center group_7 space-x-4">
+				        <image
+				          class="image_6"
+				          src="../../static/home/homeIconHeart.png"
+				        />
+				        <text class="font_1 text_9">心率</text>
+				      </view>
+				      <view class="flex-row justify-end items-start">
+				        <view class="section_5 view"></view>
+				        <text class="font_3 text_10">--</text>
+				      </view>
+				      <view class="flex-col group_8">
+				        <text class="self-start font_4 text_11">次/分</text>
+				        <view class="shrink-0 self-end section_5"></view>
+				      </view>
+				    </view>
+					
+				    <view class="flex-col home_blank_breath">
+				      <view class="flex-row items-center group_10 space-x-4">
+				        <image
+				          class="image_7"
+				          src="../../static/home/homeIconBreath.png"
+				        />
+				        <text class="font_1 text_12">呼吸</text>
+				      </view>
+				      <view class="flex-row justify-end items-start">
+				        <view class="section_5 view_2"></view>
+				        <text class="font_3 text_13">--</text>
+				      </view>
+				      <view class="flex-col group_8">
+				        <text class="self-start font_4 text_11">次/分</text>
+				        <view class="shrink-0 self-end section_5"></view>
+				      </view>
+				    </view>
+					
+				  </view>
+				  
+				  <view class="flex-row items-center home_blank_alert space-x-16">
+				    <image
+				      class="image_8"
+				      src="../../static/home/homeIconGift.png"
+				    />
+				    <view class="flex-col items-start flex-auto space-y-4">
+				      <text class="font_5">昨晚您的睡眠质量很好</text>
+				      <text class="font_5">暂时没有预警信息</text>
+				    </view>
+				  </view>
+				</view>
+			</view>
+			
+			
+			<!-- device added -->
+			<view v-if="deviceAdded" class="flex-col">
+				
+				<view class="flex-col items-start home-header space-y-10">
+					<text class="home-header-title">欢迎回来!</text>
+					<text class="font_3 text_3 home-header-subtitle">我是小云,您的健康智慧管家。</text>
+				</view>
+				
+				<!-- home device -->
+				<view class="flex-col home-device space-y-15">
+					<view class="flex-row home-device-header">
+						<view class="flex-row justify-between flex-auto equal-division">
+							<view class="flex-row equal-division-item space-x-4">
+								<text class="font_3 text_5">场景</text>
+								<text class="font_3">老人房</text>
+							</view>
+							<view class="flex-row equal-division-item space-x-14">
+								<view class="flex-row space-x-4">
+									<image class="shrink-0 image_6" src="../../static/home/homeIconWifi.png" />
+									<text class="font_3">在线</text>
+								</view>
+								<view class="flex-row items-center space-x-6">
+									<text class="font_3 text_6">45%</text>
+									<image class="shrink-0 image_7" src="../../static/home/homeIconPower.png" />
+								</view>
+							</view>
+						</view>
+						
+						<view class="flex-row items-center section_5 space-x-4">
+							<image class="shrink-0 image_8"	src="../../static/home/homeIconChange.png"/>
+							<text class="font_3">切换</text>
+						</view>
+					</view>
+					
+					<view class="flex-row justify-center self-center home-device-status space-x-8">
+						<text class="font_4 text_7">当前状态</text>
+						<text class="font_4">在床</text>
+					</view>
+					
+					<view>
+						<uni-steps :options="list1" :active="active" />
+					</view>
+					
+					<view class="flex-row self-center items-center home-device-setting">
+						<image 
+							class="shrink-0 image_8"
+							src="../../static/home/homeIconSet.png"
+						/>
+						<text>设置目标</text>
+					</view>
+				</view>
+				
+				<view class="home-report flex-col group_5 space-y-40">
+					<view class="flex-col home-header space-y-6">
+						<view class="flex-row items-center space-x-8">
+							<text class="font_4 text_8">睡眠报告</text>
+							<view class="flex-row items-center section_7">
+								<text class="font_5 text_9">去看看</text>
+								<image
+									class="shrink-0 image_9"
+									src="../../static/home/homeIconGO.png"
+								/>
+							</view>
+						</view>
+						
+						<view class="flex-row items-center space-x-10">
+							<text class="font_5 text_3 text_10">小云已为您生成最后一份睡眠报告</text>
+							<text class="font_5 text_3 text_11">2021-08-24</text>
+						</view>
+					</view>
+					
+				</view>
+				
+				<view class="flex-col space-y-40 home-data">
+					<view class="flex-row self-start group_6">
+						<view class="flex-row group_7 space-x-6 home-data-item">
+							<view class="flex-col shrink-0 self-stretch group_9 space-y-20">
+								<view class="flex-row items-center space-x-4">
+									<image
+										class="shrink-0 image_10"
+										src="../../static/home/homeIconHeart.png"
+									/>
+									<text class="font_1">心率</text>
+								</view>
+								<text class="self-center font_6">73</text>
+							</view>
+							<text class="self-start font_7 text_14">次/分</text>
+						</view>
+	          
+						<view class="flex-row justify-center group_8 space-x-6 home-data-item">
+							<view class="flex-col space-y-20">
+								<view class="flex-row space-x-4">
+									<image
+										class="shrink-0 image_4"
+										src="../../static/home/homeIconBreath.png"
+									/>
+									<text class="font_1 text_12">呼吸</text>
+								</view>
+								<text class="self-center font_6 text_13">24</text>
+							</view>
+							<text class="self-start font_7 text_15">次/分</text>
+						</view>
+					</view>
+				</view>
+			
+				<view class="flex-col home-alert space-y-4">
+					<view class="flex-row items-center space-x-4 home-alert-header">
+						<image
+							class="image_10"
+							src="../../static/home/homeIconHeart.png"
+						/>
+						<text class="font_1">实时预警</text>
+					</view>
+					
+					<view class="flex-col home-alert-list">
+						<view class="flex-row justify-between items-center list-item" :key="i" v-for="(item, i) in list_alerts">
+							<view class="flex-row items-center space-x-6">
+								<image
+									class="shrink-0 image_11"
+									src="../../static/home/homeIconAlert.png"
+								/>
+								<text class="font_8">心率警报</text>
+								<text class="font_9 text_16">2:25</text>
+							</view>
+							
+							<view class="flex-row items-baseline space-x-4">
+								<text class="font_10">102</text>
+								<text class="font_8">次/分</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+	  </view>
+	  
+	  <tab-bar currentPage="home" />
+	  
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			deviceAdded: true,
+			list_alerts: [null, null],
+			active: 4,
+			list1: [{
+				title: '昨日就寝 11:24PM'
+			}, {
+				title: '就寝目标 11:00PM'
+			}, {
+				title: '起床目标 7:00AM'
+			}, {
+				title: '今日起床 7:14AM'
+			}]
+		};
+	},
+	created() {
+		
+	},
+	computed: {
+		
+	},
+	methods: {
+	
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+	.home_add_devive {
+	  margin-right: 1.25rem;
+	  margin: 1rem;
+	  padding: 3.75rem 0;
+	  background-color: #0233c699;
+	  border-radius: 1rem;
+	  backdrop-filter: blur(0.63rem);
+	  width: 20.94rem;
+	  
+	  .image_4 {
+	    margin-left: 6.38rem;
+	    width: 1.5rem;
+	    height: 1.5rem;
+	  }
+	  .text_5 {
+	    color: #ffffff;
+	    font-size: 1.5rem;
+	    font-family: PingFangSC;
+	    line-height: 1.38rem;
+	  }
+	}
+	.space-x-10 {
+	  & > view:not(:first-child),
+	  & > text:not(:first-child),
+	  & > image:not(:first-child) {
+	    margin-left: 0.63rem;
+	  }
+	}
+	
+	.home_ad {
+	  margin-left: 2.25rem;
+	  background: url(../../static/home/homeBg2.png);
+	  background-repeat: no-repeat;
+	  background-size: contain;
+	  
+	  .text_6 {
+	    padding: 0.13rem 0;
+	    color: #ffffff;
+	    font-size: 1rem;
+	    font-family: PingFangSC;
+	    line-height: 0.94rem;
+	  }
+	  .section_4 {
+	    padding: 0.13rem 0.38rem;
+	    background-image: linear-gradient(90deg, #2c25f799 0%, #061599 100%);
+	    border-radius: 0.44rem;
+	    width: 2.88rem;
+	    height: 0.88rem;
+	    .font_2 {
+	      font-size: 0.69rem;
+	      font-family: PingFangSC;
+	      line-height: 0.63rem;
+	      color: #ffffff;
+	    }
+	    .text_7 {
+	      font-size: 0.63rem;
+	      line-height: 0.56rem;
+	    }
+	    .image_5 {
+	      width: 0.19rem;
+	      height: 0.31rem;
+	    }
+	  }
+	}
+	.space-x-6 {
+	  & > view:not(:first-child),
+	  & > text:not(:first-child),
+	  & > image:not(:first-child) {
+	    margin-left: 0.38rem;
+	  }
+	}
+	
+	.group_5 {
+	  padding-left: 1rem;
+	  padding-right: 1rem;
+	  
+	  .font_2 {
+	    font-size: 0.69rem;
+	    font-family: PingFangSC;
+	    line-height: 0.63rem;
+	    color: #ffffff;
+	  }
+	  .text_8 {
+	    font-size: 0.63rem;
+	    line-height: 0.59rem;
+	    opacity: 0.6;
+	  }
+	  .home_blank_heart {
+	    margin-top: 1rem;
+		opacity: 1;
+		border-radius: 16px;
+		background: linear-gradient(180deg, rgba(52, 26, 75, 1) 0%, rgba(22, 125, 242, 1) 100%);
+		backdrop-filter: blur(20px);
+		padding:1rem 0rem 2rem 1rem;
+		
+	    .group_7 {
+	      padding-bottom: 1.13rem;
+	      .image_6 {
+	        width: 1.13rem;
+	        height: 0.88rem;
+	      }
+	      .text_9 {
+	        line-height: 1.03rem;
+	      }
+	    }
+	    .view {
+	      margin-right: -20.13rem;
+	    }
+	    .text_10 {
+	      margin-right: 18.75rem;
+	      margin-top: 1rem;
+	    }
+	  }
+	  .home_blank_breath {
+	    margin-top: 1rem;
+	    opacity: 1;
+	    border-radius: 16px;
+	    background: linear-gradient(180deg, rgba(52, 26, 75, 1) 0%, rgba(22, 125, 242, 1) 100%);
+	    backdrop-filter: blur(20px);
+	    padding:1rem 0rem 2rem 1rem;
+		
+	    .group_10 {
+	      padding-bottom: 1.25rem;
+	      .image_7 {
+	        width: 1.13rem;
+	        height: 1.06rem;
+	      }
+	      .text_12 {
+	        line-height: 1rem;
+	      }
+	    }
+	    .view_2 {
+	      margin-right: -20rem;
+	    }
+	    .text_13 {
+	      margin-right: 18.63rem;
+	      margin-top: 1rem;
+	    }
+	  }
+	  .space-x-4 {
+	    & > view:not(:first-child),
+	    & > text:not(:first-child),
+	    & > image:not(:first-child) {
+	      margin-left: 0.25rem;
+	    }
+	  }
+	  .font_1 {
+	    font-size: 1.13rem;
+	    font-family: PingFangSC;
+	    line-height: 1.06rem;
+	    color: #ffffff;
+	  }
+	  .section_5 {
+	    background-color: #ffffff1a;
+	    width: 15.94rem;
+	    height: 0.063rem;
+	  }
+	  .font_3 {
+	    font-size: 2.5rem;
+	    font-family: PingFangSC;
+	    line-height: 0.19rem;
+	    color: #ffffff;
+	  }
+	  .group_8 {
+	    margin-top: 1.38rem;
+	    padding-left: 0.63rem;
+	    .font_4 {
+	      font-size: 0.69rem;
+	      font-family: PingFangSC;
+	      line-height: 0.63rem;
+	      color: #ffffff80;
+	    }
+	    .text_11 {
+	      font-size: 0.75rem;
+	      line-height: 0.69rem;
+	    }
+	  }
+	}
+	.home_blank_alert {
+	  margin: 1rem 1.25rem 0;
+	  padding: 0.94rem;
+	  background-color: #02299ecc;
+	  border-radius: 1rem;
+	  .image_8 {
+	    width: 7.06rem;
+	    height: 4.94rem;
+	  }
+	  .space-y-4 {
+	    & > view:not(:first-child),
+	    & > text:not(:first-child),
+	    & > image:not(:first-child) {
+	      margin-top: 0.25rem;
+	    }
+	    .font_5 {
+	      font-size: 0.88rem;
+	      font-family: PingFangSC;
+	      line-height: 1.06rem;
+	      color: #167df2;
+	    }
+	  }
+	}
+	.space-x-16 {
+	  & > view:not(:first-child),
+	  & > text:not(:first-child),
+	  & > image:not(:first-child) {
+	    margin-left: 1rem;
+	  }
+	}
+	
+	
+.container-bg {
+	position: absolute;
+	left: -130px;
+	top: -130px;
+	width: 480px;
+	height: 344px;
+	opacity: 1;
+	border-radius: 63px;
+	-webkit-transform: rotate(-315deg);
+	transform: rotate(-315deg);
+	background: linear-gradient(90deg, rgba(86, 17, 247, 0.5) 0%, #4d9efa 100%);
+}
+
+.home-container {
+  padding-bottom: 2rem;
+  background-color: #000000;
+  width: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: 100%;
+  
+  .home-module {
+    padding: 1.75rem 0 3.38rem;
+    overflow-y: auto;
+	z-index: 100;
+	
+    .home-header {
+      padding: 0 1.25rem;
+      .home-header-title {
+        color: #ffffff;
+        font-size: 1.25rem;
+        font-family: PingFangSC;
+        line-height: 1.16rem;
+      }
+	  .text_8 {
+		  color: #ffffff;
+		  opacity: 1;
+	  }
+      .text_3 {
+        opacity: 0.6;
+      }
+      .home-header-subtitle {
+        line-height: 0.72rem;
+      }
+      .space-x-10 {
+        & > view:not(:first-child),
+        & > text:not(:first-child),
+        & > image:not(:first-child) {
+          margin-left: 0.63rem;
+        }
+        .text_10 {
+          line-height: 0.59rem;
+        }
+        .text_11 {
+          line-height: 0.47rem;
+        }
+      }
+    }
+    .space-y-10 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-top: 0.63rem;
+      }
+    }
+
+    .home-device {
+      margin: 1rem 1.25rem;
+      padding-bottom: 2rem;
+      background-color: #0233c699;
+      border-radius: 1rem;
+      backdrop-filter: blur(0.63rem);
+	  
+	  .home-device-setting {
+		  width: 70px;
+		  height: 20px;
+		  opacity: 1;
+		  border-radius: 7px;
+		  background: linear-gradient(90deg, rgba(255, 35, 247, 1) 0%, rgba(91, 30, 154, 1) 100%);
+		  
+		  .image_8 {
+			  width: 10px;
+			  height: 10px;
+		  }
+		  
+		  font-size: 10px;
+		  font-weight: 400;
+		  line-height: 10px;
+		  color: rgba(255, 255, 255, 1);
+		  padding: 0.3rem;
+	  }
+      .home-device-header {
+        background-color: #ffffff33;
+        border-radius: 1rem 1rem 0px 0px;
+        .equal-division {
+          padding: 0 1rem;
+          .equal-division-item {
+            padding: 0.38rem 0;
+            .text_5 {
+              opacity: 0.5;
+            }
+          }
+          .space-x-14 {
+            & > view:not(:first-child),
+            & > text:not(:first-child),
+            & > image:not(:first-child) {
+              margin-left: 0.88rem;
+            }
+          }
+        }
+        .section_5 {
+          padding: 0.38rem 0.5rem;
+          background-image: linear-gradient(90deg, #2c25f799 0%, #061599 100%);
+          border-radius: 0px 1rem 0px 0px;
+          height: 1.5rem;
+          .image_8 {
+            width: 0.75rem;
+            height: 0.75rem;
+          }
+        }
+      }
+      .home-device-status {
+        padding: 0.25rem 0 0.25rem 0.25rem;
+        background-color: #ffffff33;
+        border-radius: 0.75rem;
+        width: 7.88rem;
+        .text_7 {
+          opacity: 0.4;
+        }
+      }
+    }
+    .space-y-15 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-top: 0.94rem;
+      }
+    }
+    .font_3 {
+      font-size: 0.75rem;
+      font-family: PingFangSC;
+      line-height: 0.69rem;
+      color: #ffffff;
+    }
+	
+    .home-report {
+      margin: 0 1.25rem;
+      padding: 1rem 0;
+      background-image: url(../../static/home/homeReportBg.png);
+      background-size: contain;
+      background-repeat: no-repeat;
+	  
+      .space-y-6 {
+        & > view:not(:first-child),
+        & > text:not(:first-child),
+        & > image:not(:first-child) {
+          margin-top: 0.38rem;
+        }
+      }
+    }
+	.home-ad {
+		margin: 0 1.25rem;
+		padding: 1rem 0;
+		background-image: url(../../static/home/homeBg2.png);
+		background-size: contain;
+		background-repeat: no-repeat;
+		
+		.space-y-6 {
+		  & > view:not(:first-child),
+		  & > text:not(:first-child),
+		  & > image:not(:first-child) {
+		    margin-top: 0.38rem;
+		  }
+		}
+	}
+	
+	.home-data {
+		margin: 0rem 1.25rem;
+		padding: 1rem 0rem;
+		
+		.home-data-item {
+			padding: 1rem 0rem;
+			margin: 0 0.3rem;
+			opacity: 1;
+			border-radius: 16px;
+			background: linear-gradient(180deg, rgba(52, 26, 75, 0.65) 0%, rgba(22, 125, 242, 1) 100%);
+			backdrop-filter: blur(20px);
+		}
+	}
+	.group_6 {
+	  overflow-x: hidden;
+	  width: 21.53rem;
+	  
+	  .group_7 {
+	    overflow-x: hidden;
+	    width: 10rem;
+		
+	    .group_9 {
+	      margin-left: 2.25rem;
+	    }
+	    .text_14 {
+	      margin-top: 3.5rem;
+	    }
+	  }
+	  .group_8 {
+	    width: 10rem;
+		
+	    .text_15 {
+	      margin-top: 3.63rem;
+	    }
+	  }
+	  .font_6 {
+	    font-size: 3rem;
+	    font-family: PingFangSC;
+	    line-height: 2.19rem;
+	    color: #ffffff;
+	  }
+	  .font_7 {
+	    font-size: 0.75rem;
+	    font-family: PingFangSC;
+	    line-height: 0.69rem;
+	    color: #ffffff80;
+	  }
+	}
+    .space-y-40 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-top: 2.5rem;
+      }
+    }
+    .space-x-8 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-left: 0.5rem;
+      }
+      .text_8 {
+        padding: 0.13rem 0;
+      }
+      .section_7 {
+        padding: 0.13rem 0.38rem;
+        background-image: linear-gradient(90deg, #ff23f7 0%, #5b1e9a 100%);
+        border-radius: 0.44rem;
+        height: 0.88rem;
+        .text_9 {
+          line-height: 0.56rem;
+        }
+        .image_9 {
+          width: 0.19rem;
+          height: 0.31rem;
+        }
+      }
+    }
+    .font_4 {
+      font-size: 1rem;
+      font-family: PingFangSC;
+      line-height: 0.94rem;
+      color: #ffffff;
+    }
+    .font_5 {
+      font-size: 0.63rem;
+      font-family: PingFangSC;
+      color: #ffffff;
+    }
+    .space-x-6 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-left: 0.38rem;
+      }
+      .text_6 {
+        line-height: 0.56rem;
+      }
+      .image_7 {
+        width: 1.31rem;
+        height: 0.69rem;
+      }
+      .image_11 {
+        width: 1.13rem;
+        height: 0.94rem;
+      }
+      .font_9 {
+        font-size: 0.88rem;
+        font-family: PingFangSC;
+        line-height: 0.69rem;
+        color: #167df2;
+      }
+      .text_16 {
+        line-height: 0.63rem;
+      }
+    }
+    .space-x-4 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-left: 0.25rem;
+      }
+      .image_6 {
+        width: 0.94rem;
+        height: 0.75rem;
+      }
+      .image_10 {
+        width: 1.13rem;
+        height: 0.88rem;
+      }
+      .font_1 {
+        font-size: 1.13rem;
+        font-family: PingFangSC;
+        line-height: 1.03rem;
+        color: #ffffff;
+      }
+      .image_4 {
+        width: 1.13rem;
+        height: 1.06rem;
+      }
+      .text_12 {
+        line-height: 1rem;
+      }
+      .font_10 {
+        font-size: 1.13rem;
+        font-family: PingFangSC;
+        line-height: 0.81rem;
+        color: #ff465a;
+      }
+    }
+    .home-alert {
+      margin: 0 1.25rem;
+      padding: 1rem 0.88rem 0.13rem;
+      background-color: #0233c6cc;
+      border-radius: 1rem;
+      .home-alert-header {
+      }
+      .home-alert-list {
+        .list-item {
+          padding: 0.88rem 0;
+          border-bottom: solid 0.063rem #ffffff1a;
+        }
+      }
+    }
+    .space-y-4 {
+      & > view:not(:first-child),
+      & > text:not(:first-child),
+      & > image:not(:first-child) {
+        margin-top: 0.25rem;
+      }
+    }
+    .font_8 {
+      font-size: 0.88rem;
+      font-family: PingFangSC;
+      line-height: 0.81rem;
+      color: #ffffff;
+    }
+  }
+  .space-y-20 {
+    & > view:not(:first-child),
+    & > text:not(:first-child),
+    & > image:not(:first-child) {
+      margin-top: 1.25rem;
+    }
+    .text_13 {
+      line-height: 2.16rem;
+    }
+  }
+}
+</style>

+ 135 - 0
sleep/pages/index/components/musicList.vue

@@ -0,0 +1,135 @@
+<template>
+	<scroll-view class="music-list-wrapper">
+		<swiper class="swiper-list" next-margin="30px">
+			<swiper-item class="music-swiper-item" v-for="(item, index) in songList" :key="index">
+				<view class="song-item flex align-center" @click="addPlayed(k)" v-for="(k, i) in item" :key="k.id">
+					<image :src="getImgUrl(k)" mode="scaleToFill" class="music-song-item-image"></image>
+					<view class="flex-col music-text" :class="{ active: k.id == playInfo.id }">
+						<text class="music-song-item-name">{{ k.name }}</text>
+						<text class="music-song-item-author">{{ getAuthor(k) }}</text>
+					</view>
+					<text class="iconfont startIcon" :class="!paused && playInfo.id == k.id ? 'icon-stop' : 'icon-kaishi3'"></text>
+				</view>
+			</swiper-item>
+		</swiper>
+	</scroll-view>
+</template>
+
+<script>
+import { getImage, getName } from '@/utils/index.js';
+import { mapState } from 'vuex';
+export default {
+	props: {
+		currentList: {
+			type: Array
+		}
+	},
+	data() {
+		return {
+		};
+	},
+
+	computed: {
+		songList() {
+			let list = [];
+			for (let i = 0; i < this.currentList.length; i += 3) {
+				let endVal = i + 3;
+				if (endVal > this.currentList.length) {
+					endVal = this.currentList.length;
+				}
+				list.push(this.currentList.slice(i, endVal));
+			}
+			return list;
+		},
+		...mapState({
+			playInfo: state => state.playInfo,
+			paused: state => state.paused
+		})
+	},
+	methods: {
+		getImgUrl(val) {
+			return getImage(val);
+		},
+		getAuthor(val) {
+			return getName(val);
+		},
+		//点击播放
+		async addPlayed(val) {
+			//如果当前播放 重复点击,就跳转到歌曲播放页面
+			if (this.playInfo.id == val.id) {
+				uni.navigateTo({
+					url: '../play/index'
+				});
+				return;
+			}
+			this.$store.dispatch('playMusic', {
+				src: '',
+				title: val.name,
+				singer: this.getAuthor(val),
+				coverImgUrl: this.getImgUrl(val),
+				id: val.id
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.music-list-wrapper {
+	margin-top: 6px;
+	width: 100%;
+	
+	.swiper-list {
+		margin-top: 4px;
+		height: 190px;
+		.music-swiper-item {
+			padding-left: 5px;
+			box-sizing: border-box;
+			
+			.song-item {
+				width: 100%;
+				.music-song-item-image {
+					width: 50px;
+					height: 50px;
+					border-radius: 7px;
+					margin: 7px 10px 0 0;
+				}
+
+				.music-text {
+					width: calc(100% - 120px);
+					display: inline-block;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+					&.active {
+						.music-song-item-name {
+							color: #f84e51;
+						}
+						.music-song-item-author {
+							color: #f84e51;
+						}
+					}
+
+					.music-song-item-name {
+						color: #ffffff;
+						font-size: 30rpx;
+						margin-right: 5px;
+					}
+
+					.music-song-item-author {
+						display: block;
+						font-size: 12px;
+						color: rgba(22, 125, 242, 1);
+					}
+				}
+
+				.startIcon {
+					margin-left: 15px;
+					font-size: 40rpx;
+					color: #f84e51;
+				}
+			}
+		}
+	}
+}
+</style>

+ 55 - 0
sleep/pages/index/components/singerList.vue

@@ -0,0 +1,55 @@
+<template>
+	<view class="flex flex-direction" style="margin-top: 10px;">
+		<scroll-view scroll-x class="music-list-wrapper" scroll-with-animation>
+			<view class="singer-list-item" v-for="item in currentList" :key="item.id" @click="searchDetailSinger(item.id)">
+				<image :src="item.picUrl + '?param=100y100'" mode="scaleToFill" class="music-singer-item-image"></image>
+				<view class="music-singer-item-text">{{ item.name }}</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		currentList: {
+			type: Array
+		}
+	},
+	methods: {
+		searchDetailSinger(id) {
+			uni.navigateTo({
+				url: '../singerPlayList/index?id=' + id
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.music-list-wrapper {
+	width: 100%;
+	white-space: nowrap;
+
+	.singer-list-item {
+		width: 20%;
+		margin: 0 6px;
+		display: inline-block;
+		overflow: hidden;
+		position: relative;
+		border-radius: 7px;
+		text-align: center;
+
+		.music-singer-item-image {
+			width: 70px;
+			height: 70px;
+			border-radius: 50%;
+		}
+		.music-singer-item-text{
+			font-size: 14px;
+			color: #000;
+			margin-top: 3px;
+		}
+	}
+}
+</style>

+ 98 - 0
sleep/pages/index/components/songList.vue

@@ -0,0 +1,98 @@
+<template>
+	<scroll-view scroll-x class="music-list-wrapper" scroll-with-animation>
+		<view class="list-item" v-for="item in currentList" :key="item.id">
+			<!-- <view class="hotCount" v-if="item.playCount > 0 || item.playcount > 0"> -->
+				<!-- <text class="iconfont icon-kaishi" style="margin-right: 3.5px;"></text> -->
+				<!-- <text>{{ playCount(item.playCount || item.playcount) }}</text> -->
+			<!-- </view> -->
+			<view
+				class="bg-img flex align-end list-item-image"
+				:style="'background-image:url(' + (item.picUrl || item.coverImgUrl) + '?param=230y230)'"
+				@click="toPlayListDetail(item)"
+			>
+				<view class="bg-shadeBottom padding-top-xl radioName" v-if="type">{{ item.name }}</view>
+			</view>
+			<view class="list-item-text">{{ type ? item.rcmdtext : item.name }}</view>
+		</view>
+	</scroll-view>
+</template>
+
+<script>
+import { filterPlayCount } from '@/utils/index.js';
+export default {
+	props: {
+		currentList: {
+			type: Array
+		},
+		type: {
+			type: String,
+			default: ''
+		}
+	},
+	methods: {
+		playCount(val) {
+			return filterPlayCount(val);
+		},
+		toPlayListDetail(val) {
+			uni.navigateTo({
+				url: '../songDetail/index?id=' + val.id
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.music-list-wrapper {
+	white-space: nowrap;
+	margin-top: 10px;
+	
+	.list-item {
+		width: 130px;
+		margin: 0 6px;
+		display: inline-block;
+		overflow: hidden;
+		position: relative;
+
+		.hotCount {
+			position: absolute;
+			width: 100%;
+			height: 40px;
+			padding: 3px 6px 0 0;
+			text-align: right;
+			color: #fff;
+			z-index: 10;
+			font-size: 12px;
+			background-image: linear-gradient(rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0));
+			border-radius: 6px;
+		}
+
+		.list-item-image {
+			height: 130px;
+			border-radius: 6px;
+		}
+
+		.list-item-text {
+			width: 100%;
+			margin-top: 4px;
+			white-space: normal;
+			color: #ffffff;
+			font-size: 14px;
+			height: 18px;
+			line-height: 18px;
+			opacity: 0.8;
+			
+		}
+
+		.radioName {
+			width: 100%;
+			color: #fff;
+			font-size: 12px;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+			padding: 0 0 8px 8px;
+		}
+	}
+}
+</style>

+ 300 - 0
sleep/pages/index/index.vue

@@ -0,0 +1,300 @@
+<template>
+	<view class="recommend-container page-background">
+		<view class="bg-black">
+			<cu-custom bgColor="#000000"><view slot="content" style="color: #ffffff">UU睡眠</view></cu-custom>
+		</view>
+		<scroll-view :style="{ height: height }" class="main-container" scroll-y>
+			
+			<view class="bg-black">
+				<view class="banner-wrapper">
+					<swiper class="screen-swiper square-dot" style="min-height: 317upx;" :indicator-dots="true" :circular="true" :autoplay="true" interval="5000" duration="500">
+						<swiper-item v-for="item in swiperList" :key="item.bannerId">
+							<image :src="item.pic || item.imageUrl" mode="scaleToFill" class="banner-img"></image>
+						</swiper-item>
+					</swiper>
+				</view>
+			</view>
+			
+			<view class="music-wrapper" v-if="recommendList.length > 0">
+				<box-title title="UU推荐" buttonName="更多" iconName="right" @handlePlay="handleNative('../songList/index')"></box-title>
+				<song-list :currentList="recommendList"></song-list>
+			</view>
+			
+			
+			<view class="music-wrapper" v-if="dayRecommendList.length > 0">
+				<box-title title="睡眠音乐" buttonName="更多" iconName="kaishi2" @handlePlay="handlePlay('dayRecommendList')"></box-title>
+				<music-list :currentList="dayRecommendList"></music-list>
+			</view>
+			
+			<view class="music-wrapper" v-if="dayRecommendList.length > 0">
+				<box-title title="睡眠小知识" buttonName="更多" iconName="kaishi2" @handlePlay="handlePlay('dayRecommendList')"></box-title>
+				
+				<view class="flex-col justify-start knowledge-list">
+				  <view class="flex-row items-center space-x-14 knowledge-item">
+				    <view class="flex-col flex-auto space-y-8">
+				      <text class="self-start font_4 knowledge-item-title">睡眠科普:你真的了解睡眠吗?</text>
+				      <text class="knowledge-item-desc">
+				        套用模型省时省力,不用冥思苦想,但是,每一个需要设计的内容和体系应该是迥然不同的,所以每一个设计方案也…
+				      </text>
+				    </view>
+				    <image
+				      class="shrink-0 image_12"
+				      src="https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/649415135a7e3f0310661c1e/649415b654fe0000116ae544/16874496879438729687.png"
+				    />
+				  </view>
+				  
+				  <view class="flex-row items-center space-x-14 knowledge-item">
+				    <view class="flex-col flex-auto space-y-8">
+				      <text class="self-start font_4 knowledge-item-title">睡眠科普:你真的了解睡眠吗?</text>
+				      <text class="knowledge-item-desc">
+				        套用模型省时省力,不用冥思苦想,但是,每一个需要设计的内容和体系应该是迥然不同的,所以每一个设计方案也…
+				      </text>
+				    </view>
+				    <image
+				      class="shrink-0 image_12"
+				      src="https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/649415135a7e3f0310661c1e/649415b654fe0000116ae544/16874496879438729687.png"
+				    />
+				  </view>
+				  
+				  <view class="flex-row items-center space-x-14 knowledge-item">
+				    <view class="flex-col flex-auto space-y-8">
+				      <text class="self-start font_4 knowledge-item-title">睡眠科普:你真的了解睡眠吗?</text>
+				      <text class="knowledge-item-desc">
+				        套用模型省时省力,不用冥思苦想,但是,每一个需要设计的内容和体系应该是迥然不同的,所以每一个设计方案也…
+				      </text>
+				    </view>
+				    <image
+				      class="shrink-0 image_12"
+				      src="https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/649415135a7e3f0310661c1e/649415b654fe0000116ae544/16874496879438729687.png"
+				    />
+				  </view>
+				</view>
+			</view>
+			
+		</scroll-view>
+		
+		<music-control />
+		
+		<tab-bar currentPage="index" />
+	</view>
+</template>
+
+<script>
+import songList from './components/songList.vue';
+import musicList from './components/musicList.vue';
+import singerList from './components/singerList.vue';
+import { getImage, getName } from '@/utils/index.js';
+export default {
+	data() {
+		return {
+			//轮播图
+			swiperList: [],
+			recommendList: [],
+			dayRecommendList: [],
+			dayRecommendMusicList: [],
+			newSongList: [],
+			hotSingerList: [],
+			selectData: [],
+		};
+	},
+	components: {
+		songList,
+		musicList,
+		singerList
+	},
+	computed: {
+		cookie() {
+			return this.$store.state.cookie;
+		},
+		height() {
+			let height = this.CustomBar / (uni.upx2px(this.CustomBar) / this.CustomBar) + 220;
+			return `calc(100%  - ${height}rpx)`;
+		}
+	},
+	onShow() {
+		this.getDayRecommendData();
+		this.getDayRecommendMusicData();
+	},
+	created() {
+		this.getData();
+	},
+	onPullDownRefresh() {
+		this.getData();
+		setTimeout(() => {
+			uni.stopPullDownRefresh();
+		}, 1000);
+	},
+	methods: {
+		getData() {
+			this.getBannerData();
+			this.getRecommendData();
+			this.getNewSongData();
+			this.getSelectionData();
+			this.getHotSingerData();
+		},
+
+		//播放全部 猜你喜欢
+		handlePlay(key) {
+			const list = this[key].map(item => {
+				return {
+					src: '',
+					title: item.name,
+					singer: getName(item),
+					coverImgUrl: getImage(item),
+					id: item.id
+				};
+			});
+			this.$store.dispatch('playAllMUsic', list);
+		},
+
+		handleNative(val) {
+			uni.navigateTo({
+				url: val
+			});
+		},
+
+		// 获取轮播图数据
+		async getBannerData() {
+			const data = await this.$api.getBanner();
+			
+			this.swiperList = data.banners || [];
+		},
+
+		//获取推荐歌单数据
+		async getRecommendData() {
+			const data = await this.$api.getRecommendList();
+			this.recommendList = data.result.slice(0, 8);
+		},
+
+		//获取猜你喜欢歌曲
+		async getDayRecommendData() {
+			const { data } = await this.$api.getDayRecommendList();
+			this.dayRecommendList = data.dailySongs;
+		},
+
+		//获取推荐歌单
+		async getDayRecommendMusicData() {
+			const data = await this.$api.getDayRecommendMusicList();
+			this.dayRecommendMusicList = data.recommend || [];
+		},
+
+		//获取新歌数据
+		async getNewSongData() {
+			const data = await this.$api.getNewSongList();
+			this.newSongList = data.result.slice(0, 9);
+		},
+
+		//获取网友精选歌单
+		async getSelectionData() {
+			const data = await this.$api.getSelectionData();
+			this.selectData = data.playlists || [];
+		},
+
+		//获取热门歌手数据
+		async getHotSingerData() {
+			const data = await this.$api.getHotSingerList();
+			this.hotSingerList = (data.artists || []).slice(0, 10);
+		},
+
+		toSearch() {
+			uni.navigateTo({
+				url: '../search/index'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.bg-black {
+	background-color: #000000;
+}
+.recommend-container {
+	position: relative;
+	background-color: #000000;
+	height: 100%;
+	padding-bottom: 100px;
+
+	.main-container {
+		position: relative;
+		.banner-wrapper {
+			box-sizing: border-box;
+			padding: 10px;
+			.banner-img {
+				height: 158px;
+				border-radius: 6px;
+			}
+		}
+		.list-wrapper {
+			margin-top: 8px;
+			padding-bottom: 12px;
+			.list-item {
+				text-align: center;
+				font-size: 24rpx;
+			}
+			.list-ico {
+				font-size: 72rpx;
+				margin-bottom: 6px;
+			}
+			.list-title {
+				font-size: 28rpx;
+			}
+		}
+
+		.music-wrapper {
+			margin-top: 5px;
+			background: #000000;
+			box-sizing: border-box;
+			padding: 15px;
+			
+			&:last-of-type {
+				margin-bottom: 0;
+			}
+		}
+	}
+	
+	.knowledge-list {
+		margin-top: 1rem;
+		
+	  .space-x-14 {
+	    & > view:not(:first-child),
+	    & > text:not(:first-child),
+	    & > image:not(:first-child) {
+	      margin-left: 0.88rem;
+	    }
+	    .space-y-8 {
+	      & > view:not(:first-child),
+	      & > text:not(:first-child),
+	      & > image:not(:first-child) {
+	        margin-top: 0.5rem;
+	      }
+	      .font_4 {
+	        font-size: 0.88rem;
+	        font-family: PingFangSC;
+	        line-height: 0.81rem;
+	        color: #ffffff;
+	      }
+	      .knowledge-item-title {
+	        opacity: 0.8;
+	      }
+	      .knowledge-item-desc {
+	        color: #4d4b5e;
+	        font-size: 0.63rem;
+	        font-family: PingFangSC;
+	        line-height: 0.88rem;
+	      }
+	    }
+	    .image_12 {
+	      margin-right: 1.25rem;
+	      width: 3.75rem;
+	      height: 3.75rem;
+	    }
+	  }
+	  
+	  .knowledge-item {
+		  margin-left: 3px;
+		  margin-bottom: 20px;
+	  }
+	}
+}
+</style>

+ 208 - 0
sleep/pages/login/index.vue

@@ -0,0 +1,208 @@
+<template>
+	<view class="login-container">
+		<image src="../../static/loginBg.png" class="background-img"></image>
+		<view class="absolve-wrapper">
+			<view class="logo"><image src="../../static/logo.png" class="logo-img"></image></view>
+			<view class="text-title">我有故事和歌,你听吗?</view>
+			<view class="login-main">
+				<view class="input-wrapper">
+					<i class="iconfont icon-phone icon"></i>
+					<input placeholder="请输入手机号" v-model="userInfo.phone" maxlength="11" />
+				</view>
+				<view class="input-wrapper">
+					<i class="iconfont icon-password icon"></i>
+					<input v-if="isText" type="text" placeholder="请输入密码" v-model="userInfo.password" />
+					<input v-else type="password" placeholder="请输入密码" v-model="userInfo.password" />
+					<i class="iconfont eye" :class="isText ? 'icon-openEye' : 'icon-closeEye'" @click="isText = !isText"></i>
+				</view>
+				<button class="cu-btn round login-button lg cu-load" :class="{ loading: loading }" @click="getUserInfo">登录</button>
+			</view>
+			<view class="other-login">
+				<view class="other-text">第三方登录</view>
+				<view class="login-list">
+					<i class="iconfont icon-weixin other-item" @click="handleTest"></i>
+					<i class="iconfont icon-qq other-item" @click="handleTest"></i>
+					<i class="iconfont icon-weibo other-item" @click="handleTest"></i>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			loading: false,
+			isText: false,
+			userInfo: {
+				phone: '100000',
+				password: '123456'
+			}
+		};
+	},
+	methods: {
+		getUserInfo() {
+			this.loading = true;
+			let that = this;
+			//测试账号,可以改成你自己的
+			const userInfo = this.userInfo;
+			that.loading = true;
+			that.$store
+				.dispatch('login', userInfo)
+				.then(res => {
+					that.loading = false;
+					uni.navigateTo({
+						url: '/pages/index/index'
+					});
+				})
+				.catch(err => {
+					that.loading = false;
+					uni.showToast({
+						title: '登录失败',
+						icon: 'none',
+						duration: 3000,
+						complete: function() {
+							setTimeout(function() {
+								uni.hideToast();
+							}, 3000);
+						}
+					});
+				});
+		},
+		handleTest(){
+			 uni.showToast({
+			     title: '功能开发中哦...',
+				 icon :'none',
+			     duration: 2000
+			 });
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.login-container {
+	width: 100%;
+	height: 100vh;
+	position: relative;
+	overflow: hidden;
+	// background: url(../../static/loginBg.png) no-repeat;
+	// background-size: contain;
+	.absolve-wrapper{
+		position: absolute;
+		top: 0;
+		bottom: 0;
+		right: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+	}
+	.background-img{
+      width: 100%;
+	  height: 100%;
+	}
+	.logo {
+		text-align: center;
+		margin-top: 12%;
+		.logo-img {
+			width: 100px;
+			height: 100px;
+		}
+	}
+	.text-title {
+		font-size: 48rpx;
+		margin-top: 5%;
+		text-align: center;
+		color: #fffefe;
+	}
+	.login-main {
+		box-sizing: border-box;
+		position: absolute;
+		top: 50%;
+		transform: translateY(-50%);
+		left: 10%;
+		right: 10%;
+		.input-wrapper {
+			display: flex;
+			align-items: center;
+			margin-bottom: 30rpx;
+			padding-bottom: 30rpx;
+			border-bottom: 1px solid rgb(242, 242, 242);
+			position: relative;
+			.icon {
+				margin-right: 10px;
+				font-size: 16px;
+			}
+			.eye {
+				float: right;
+				position: absolute;
+				right: 5px;
+				font-size: 16px;
+			}
+		}
+		.login-button {
+			width: 100%;
+			color: #fff;
+			background-image: linear-gradient(to right, #ffa6b6, #ff7c93);
+			letter-spacing: 2px;
+			height: 45px;
+			margin-top: 75rpx !important;
+		}
+	}
+	.other-login {
+		position: absolute;
+		bottom: 5%;
+		left: 10%;
+		right: 10%;
+		.other-text {
+			text-align: center;
+			color: rgba(0, 0, 0, 0.4);
+			font-size: 28rpx;
+			position: relative;
+			&::before,
+			&::after {
+				position: absolute;
+				background: rgba(0, 0, 0, 0.15);
+				content: '';
+				height: 1px;
+				top: 50%;
+				width: 100px;
+			}
+			&::before {
+				left: 5px;
+			}
+			&::after {
+				right: 5px;
+			}
+		}
+		.login-list{
+			margin-top: 15px;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			.other-item{
+				flex: 1;
+				font-size: 40px;
+				text-align: center;
+				&.icon-weixin{
+					color: #17d874;
+				}
+				&.icon-qq{
+					color: #32d6f5;
+				}
+				&.icon-weibo{
+					color: rgb(234,93,92);
+				}
+
+			}
+		}
+	}
+	/deep/ {
+		.input-placeholder {
+			color: rgb(194, 194, 194);
+			font-size: 30rpx;
+		}
+	}
+}
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1046 - 0
sleep/pages/my/index.vue


+ 399 - 0
sleep/pages/play/index.vue

@@ -0,0 +1,399 @@
+<template>
+  <div class="play-container">
+    <div
+      class="mask-container"
+      :style="{ 'background-image': 'url(' + playInfo.coverImgUrl + ')' }"
+    >
+      <!-- <div
+        class="album-cover"
+        :style="{ 'background-image': 'url(' + playInfo.coverImgUrl + ')' }"
+      ></div> -->
+      <div class="cover-mask" style="opacity: 0.6"></div>
+    </div>
+    <cu-custom style="color: #fff" :isBack="true">
+      <block slot="content" class="musicName">{{ playInfo.title }}</block>
+    </cu-custom>
+    <view class="author" :style="{ top: CustomBar + 'px' }">{{
+      playInfo.singer
+    }}</view>
+    <view
+      class="img-container"
+      :style="{ transform: 'translate(-50%, -50%) rotate(' + rotate + 'deg)' }"
+      v-show="!lyricShow"
+    >
+      <image
+        :src="playInfo.coverImgUrl"
+        class="authorImg"
+        @click="getLyric"
+      ></image>
+    </view>
+    <scroll-view
+      scroll-y
+      v-if="lyricShow"
+      @click="lyricShow = false"
+      :scroll-top="scrollTop"
+      class="lyric-container"
+      :style="{ top: CustomBar + 35 + 'px' }"
+    >
+      <view v-if="lyricList.length > 0">
+        <view
+          class="lyric-item"
+          :class="{ active: index == currentLyricIndex }"
+          v-for="(item, index) in lyricList"
+          :key="index"
+          style="text-align: center"
+        >
+          {{ item.content }}
+        </view>
+      </view>
+      <p v-else class="noLyric">暂无歌词</p>
+    </scroll-view>
+
+    <view class="bottom-control">
+      <view class="progress">
+        <view class="audio-number">{{ playDetailInfo.current }}</view>
+        <slider
+          class="audio-slider"
+          activeColor="rgb(248, 78, 81)"
+          block-size="8"
+          :value="playDetailInfo.current_value"
+          :max="playDetailInfo.duration_value"
+          @change="handleChange"
+        ></slider>
+        <view class="audio-number">{{ playDetailInfo.duration }}</view>
+      </view>
+      <view class="iconList flex">
+        <text
+          class="iconfont"
+          :class="
+            likeList.includes(playInfo.id)
+              ? 'icon-like lighIcon'
+              : 'icon-unlike'
+          "
+          @click="likeMusic()"
+          style="margin-right: 65rpx"
+        ></text>
+        <text
+          class="iconfont icon-play-left"
+          @click="handleChangePlay(-1)"
+        ></text>
+        <text
+          class="iconfont"
+          :class="!paused ? 'icon-play' : 'icon-pause'"
+          style="font-size: 90rpx; margin: 0 35px"
+          @click="playMusic"
+        ></text>
+        <text
+          class="iconfont icon-play-right"
+          @click="handleChangePlay(1)"
+        ></text>
+        <text
+          class="iconfont icon-liebiao"
+          style="margin-left: 65rpx; font-size: 56rpx"
+          @click.stop="modelShow = true"
+        ></text>
+      </view>
+    </view>
+    <play-list v-model="modelShow" @backHome="backHome"></play-list>
+  </div>
+</template>
+
+<script>
+import playList from "../../components/musicControl/playList.vue";
+import { mapState } from "vuex";
+export default {
+  data() {
+    return {
+      modelShow: false,
+      scrollTop: 0,
+      currentLyricIndex: 0,
+      //歌词
+      lyricList: [],
+      //旋转角度
+      rotate: 0,
+      //喜欢音乐list
+      likeList: [],
+      //是否显示歌词
+      lyricShow: false,
+      //定时器
+      timer: null,
+      playDetailInfo: {
+        current: "00:00",
+        duration: "00:00",
+        current_value: 0,
+        duration_value: 0,
+      },
+	  CustomBar:this.CustomBar
+    };
+  },
+  components: { playList },
+  computed: {
+    ...mapState({
+      playInfo: (state) => state.playInfo,
+      paused: (state) => state.paused,
+      userInfo: (state) => state.userInfo,
+    }),
+  },
+  onShow() {
+    if (!this.paused) {
+      this.initRotate();
+    } else {
+      clearInterval(this.timer);
+    }
+    this.getLikeData();
+    this.playDetailInfo = this.$audio.playinfo;
+    this.initLyric();
+    this.$audio.syncStateOn("page-index-get-state", ({ playinfo }) => {
+      this.playDetailInfo = { ...playinfo };
+      this.initLyric();
+    });
+  },
+  methods: {
+    initLyric() {
+      if (this.lyricList.length == 0) return;
+      let timeStamp = this.playDetailInfo.current;
+      this.currentLyricIndex = this.lyricList.findIndex((item, index) => {
+        return item.time < timeStamp && this.lyricList[index + 1]
+          ? this.lyricList[index + 1].time > timeStamp
+          : true;
+      });
+      this.scrollTop = this.currentLyricIndex * 36;
+    },
+    //获取喜欢音乐列表
+    async getLikeData() {
+      const uid = this.userInfo.id;
+      const timestamp = new Date().getTime();
+      const data = await this.$api.likeData({ uid, timestamp });
+      this.$nextTick(() => {
+        this.likeList = data.ids || [];
+      });
+    },
+
+    //喜欢或取消喜音乐
+    async likeMusic() {
+      let bool = this.likeList.includes(this.playInfo.id);
+      const timestamp = new Date().getTime();
+      const data = await this.$api.likeMusic({
+        id: this.playInfo.id,
+        timestamp,
+        like: !bool,
+      });
+      this.getLikeData();
+    },
+
+    //拖动 完成
+    handleChange(event) {
+      this.$audio.seek(event.target.value);
+    },
+
+    //播放或暂停
+    playMusic() {
+      if (this.paused) {
+        this.initRotate();
+      } else {
+        clearInterval(this.timer);
+      }
+      let list = this.$audio.audiolist;
+      let index = list.findIndex((item) => item.id == this.playInfo.id);
+      this.$audio.operate(index);
+      this.$store.commit("SET_PASUED", !this.paused);
+    },
+
+    //旋转
+    initRotate() {
+      this.timer = setInterval(() => {
+        this.rotate += 9;
+      }, 1000);
+    },
+
+    //获取歌词
+    async getLyric(bool) {
+      this.lyricShow = true;
+      let data = [];
+      const id = this.playInfo.id;
+      const res = await this.$api.getLyric({ id });
+      data = (res.lrc.lyric || "").split("\n");
+      let timeReg = /^\[.*\]/;
+      let json = [];
+      data.forEach((item) => {
+        if (item.match(timeReg)) {
+          let time = item.match(timeReg)[0].substr(1, 8);
+          let minute = time.substr(0, 2);
+          let second = time.substr(3, 2);
+          let ms = time.substr(6, 2);
+          json.push({
+            time,
+            ms:
+              parseInt(minute) * 60 * 1000 +
+              parseInt(second) * 1000 +
+              parseInt(ms) * 10,
+            content: item.substr(11),
+          });
+        }
+      });
+      this.lyricList = json;
+    },
+
+    //切换歌曲  --上一首或下一首
+    handleChangePlay(count) {
+      this.$store.dispatch("changePlay", count);
+    },
+
+    backHome() {
+      uni.switchTab({
+        url: "/pages/index/index",
+      });
+    },
+  },
+  onHide() {
+    //卸载page-index-get-state,提高页面性能
+    this.$audio.syncStateOff("page-index-get-state");
+  },
+  watch: {
+    playInfo: {
+      handler() {
+        this.scrollTop = 0;
+        this.getLyric(true);
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.play-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .author {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    color: rgba(255, 255, 255, 0.9);
+    font-size: 28rpx;
+  }
+  .mask-container {
+    overflow: hidden;
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 0;
+    background-size: cover;
+    background-position: center;
+    &:after {
+      content: "";
+      position: absolute;
+      width: 130%;
+      height: 130%;
+      left: 0;
+      top: 0;
+      z-index: 1;
+      filter: blur(15px);
+      transform: translate(-3rem, -3rem);
+      background: inherit;
+      background-size: 100% 100%;
+    }
+    // .album-cover {
+    //   position: absolute;
+    //   top: 0;
+    //   bottom: 0;
+    //   left: 0;
+    //   z-index: 2;
+    //   width: 100%;
+    //   height: 100%;
+    //   background-size: cover;
+    //   background-position: center;
+    //   filter: blur(20px);
+    // }
+    .cover-mask {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      z-index: 3;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0.8);
+    }
+  }
+  .img-container {
+    position: absolute;
+    top: 35%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 450rpx;
+    height: 450rpx;
+    background: url(../../static/musicImg.png) no-repeat;
+    background-size: 100% 100%;
+    border-radius: 50%;
+    border: 2px solid rgba(255, 255, 255, 0.3);
+    transition: all 1s linear;
+    .authorImg {
+      border-radius: 50%;
+      width: 315rpx;
+      height: 315rpx;
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+  .lighIcon {
+    color: rgb(248, 78, 81) !important;
+  }
+  .lyric-container {
+    position: absolute;
+    bottom: calc(10% + 100px);
+    .lyric-item {
+      color: #e1d7f0;
+      height: 40px;
+      line-height: 40px;
+      &.active {
+        color: rgb(248, 78, 81);
+      }
+    }
+    .noLyric {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      color: #fff;
+      transform: translate(-50%, -50%);
+    }
+  }
+
+  .bottom-control {
+    position: absolute;
+    bottom: 10%;
+    left: 10px;
+    right: 10px;
+    .progress {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      .audio-number {
+        width: 120upx;
+        font-size: 24upx;
+        line-height: 1;
+        color: #fff;
+        text-align: center;
+      }
+      .audio-slider {
+        flex: 1;
+        margin: 0;
+      }
+    }
+    .iconList {
+      justify-content: center;
+      align-items: center;
+      margin-top: 26rpx;
+      .iconfont {
+        color: #fff;
+        font-size: 48rpx;
+      }
+    }
+  }
+}
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1577 - 0
sleep/pages/report/report.vue


+ 58 - 0
sleep/pages/search/components/historySearch.vue

@@ -0,0 +1,58 @@
+<template>
+	<div class="history-container flex" v-if="tagList.length > 0">
+		<div class="title-wrapper historyTitle">历史</div>
+			
+		<scroll-view scroll-x class="historyList" scroll-with-animation scroll-left="0">
+			<view class="cu-tag round" style="padding:0 24rpx" v-for="(item, index) in tagList" :key="index" @click="handleClick(item)">{{ item }}</view>
+		</scroll-view>
+
+		<text class="cuIcon-delete closeIcon" @click="clearHistory"></text>
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {};
+	},
+	computed: {
+		tagList() {
+			return this.$store.state.historyList;
+		}
+	},
+	methods: {
+		handleClick(val) {
+			this.$store.dispatch('addHistoryList', val);
+			this.$emit('chooseMusic', val);
+		},
+
+		//清空
+		clearHistory() {
+			this.$store.dispatch('clearHistoryList');
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.history-container {
+	height: 35px;
+	width: 100%;
+	position: relative;
+	align-items: center;
+	.historyTitle {
+		width: 85rpx;
+		font-size: 28rpx;
+		color: #000;
+	}
+	.historyList {
+	   width: calc(100% - 160rpx);
+		white-space: nowrap;
+	}
+	.closeIcon {
+		font-size: 38rpx;
+		width: 75rpx;
+		text-align: right;
+	}
+}
+</style>

+ 94 - 0
sleep/pages/search/components/hotSearch.vue

@@ -0,0 +1,94 @@
+<template>
+	<div class="hotSearch-container">
+		<p style="font-weight: 600;font-size: 28rpx;margin-bottom: 10px;">热搜榜</p>
+		<div class="hotList">
+			<div class="hot-item flex" v-for="(item, index) in hotList" :key="index" :class="{ topThree: index <= 2 }" @click="handleClick(item.searchWord)">
+				<span class="index">{{ index + 1 }}</span>
+				<span class="searchLabel">{{ item.searchWord }}</span>
+				<image v-if="item.iconUrl" :src="item.iconUrl" mode="" class="iconImg" :class="item.iconType == 5 ? 'topImg' : 'hotImg'"></image>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			hotList: []
+		};
+	},
+	created() {
+		this.getDetailSearch();
+	},
+	methods: {
+		//获取热门搜索详细列表
+		getDetailSearch() {
+			this.$api.getHotDetailSearch().then(res => {
+				this.hotList = res.data || [];
+			});
+		},
+
+		handleClick(val) {
+			this.$store.dispatch('addHistoryList', val);
+			this.$emit('chooseMusic', val);
+		},
+	}
+};
+</script>
+
+<style lang="scss">
+.hotSearch-container {
+	width: 100%;
+	position: relative;
+	margin-top: 20rpx;
+	.hotList {
+		margin-top: 15rpx;
+		width: 100%;
+		flex-wrap: wrap;
+		.hot-item {
+			width: 50%;
+			margin-bottom: 30rpx;
+			display: inline-block;
+			&.topThree {
+				.index {
+					color: rgb(248, 78, 81);
+				}
+				.word {
+					font-family: 'Microsoft Yahei', Arial, Helvetica, sans-serif;
+					font-weight: 600;
+					color: rgba(0,0,0,0.5);
+				}
+			}
+			.index {
+				width: 44rpx;
+				display: inline-block;
+			}
+			.searchLabel {
+				max-width: calc(100% - 125rpx);
+				display: inline-block;
+				white-space: nowrap;
+				vertical-align: middle;
+				overflow: hidden;
+				text-overflow:ellipsis;
+				color: rgb(0,0,0);
+			}
+
+			.iconImg {
+				margin-left: 15rpx;
+				vertical-align: middle;
+
+				&.topImg {
+					width: 30rpx;
+					height: 32rpx;
+					vertical-align: text-bottom;
+				}
+				&.hotImg {
+					width: 60rpx;
+					height: 30rpx;
+				}
+			}
+		}
+	}
+}
+</style>

+ 52 - 0
sleep/pages/search/index.vue

@@ -0,0 +1,52 @@
+<template>
+	<div class="search-container">
+		<cu-custom bgColor="#fff" :isBack="true"><view slot="content" style="color: #000">搜索</view></cu-custom>
+		<search @handleSearch="searchMusic" :text="text"></search>
+		<scroll-view scroll-y class="content-list" scroll-with-animation>
+			<history-search @chooseMusic="chooseMusic"></history-search>
+			<hot-search @chooseMusic="chooseMusic"></hot-search>
+		</scroll-view>
+		<music-control v-if="playInfo.id" />
+	</div>
+</template>
+
+<script>
+import search from '@/components/search/index.vue';
+import historySearch from './components/historySearch.vue';
+import hotSearch from './components/hotSearch.vue';
+import { mapState } from 'vuex';
+export default {
+	data() {
+		return {
+			text: ''
+		};
+	},
+	components: { historySearch, hotSearch, search },
+	computed: mapState({
+		playInfo: state => state.playInfo
+	}),
+	methods: {
+		//点击搜索
+		searchMusic(val) {
+			uni.navigateTo({
+				url: '../searchList/index?keywords=' + val
+			});
+		},
+
+		//点击热搜列表
+		chooseMusic(val) {
+			this.text = val;
+			this.searchMusic(val);
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.content-list {
+	padding: 0 15px;
+	box-sizing: border-box;
+	width: 100%;
+	margin-top: 5px;
+}
+</style>

+ 181 - 0
sleep/pages/searchList/index.vue

@@ -0,0 +1,181 @@
+<template>
+	<div class="searchList-container">
+		<cu-custom bgColor="#fff" :isBack="true"><view slot="content" style="color: #000">搜索结果</view></cu-custom>
+		<search :text="keywords" @handleSearch="handleSearch"></search>
+		<div class="musicList">
+			<box-title title="单曲" buttonName="播放全部" iconName="kaishi2" @handlePlay="handlePlayAllMusic"></box-title>
+			<scroll-view scroll-y scroll-with-animation @scrolltolower="reachBottom()" :style="{ height: height, 'margin-top': '10px' }">
+				<view class="music-item flex" :class="{ active: item.id == playInfo.id }" v-for="(item, index) in musicList" :key="index" @click="handlePlayMusic(item)">
+					<image :src="item.al.picUrl + '?param=60y60'" mode="widthFix" class="music-img"></image>
+					<view class="music-info">
+						<view class="music-name text-overflow">{{ item.name }}</view>
+						<view class="music-singer text-overflow flex">
+							<span class="small-icon">{{ item.id % 2 == 0 ? 'SQ' : 'HD' }}</span>
+							{{ item.ar ? item.ar.map(item => item.name).join('/') : '' }} - {{ item.al.name }}
+						</view>
+					</view>
+				</view>
+				<view class="loading" v-if="status == 'loading' || status == 'notMore'">{{ status == 'notMore' ? '没有更多了' : '努力加载中...' }}</view>
+			</scroll-view>
+		</div>
+		<music-control v-if="playInfo.id"/>
+	</div>
+</template>
+
+<script>
+import search from '@/components/search/index.vue';
+import boxTitle from '@/components/boxTitle/index.vue';
+import { getImage, getName } from '@/utils/index.js';
+import { mapState } from 'vuex';
+export default {
+	data() {
+		return {
+			musicList: [],
+			offset: 0,
+			status: null,
+			keywords: '',
+			total: 0
+		};
+	},
+	components: { search, boxTitle },
+	computed: {
+		...mapState({
+			playInfo: state => state.playInfo
+		}),
+		height() {
+			let height = this.CustomBar / (uni.upx2px(this.CustomBar) / this.CustomBar) + 190;
+			if (this.playInfo.id) {
+				height += 110;
+			}
+			return `calc(100vh  - ${height}rpx)`;
+		}
+	},
+	onLoad(options) {
+		const { keywords } = options;
+		this.keywords = keywords;
+		this.getMusicList();
+	},
+
+	methods: {
+		//加载更多
+		reachBottom() {
+			this.status = 'loading';
+			if (this.offset >= this.total) {
+				this.status = 'notMore';
+				return;
+			}
+			this.offset += 30;
+			if (this.offset > this.total) {
+				this.offset = this.total;
+			}
+			this.getMusicList();
+		},
+
+		//点击查询
+		handleSearch(val) {
+			this.keywords = val;
+			this.status = null;
+			this.offset = 0;
+			this.total=0
+			this.musicList = [];
+			this.getMusicList();
+		},
+
+		getMusicList() {
+			const { keywords, offset } = this;
+			this.$api.getSearchList({ keywords, offset, type: 1 }).then(res => {
+				const list = res.result.songs || [];
+				this.musicList.push(...list);
+				this.total = res.result.songCount || 0;
+				this.status = null;
+			});
+		},
+
+		//播放全部
+		handlePlayAllMusic() {
+			const list = this.musicList.map(item => {
+				return {
+					src: '',
+					title: item.name,
+					singer: getName(item),
+					coverImgUrl: getImage(item),
+					id: item.id
+				};
+			});
+			this.$store.dispatch('playAllMUsic', list);
+		},
+
+		//点击播放
+		handlePlayMusic(val) {
+			if (this.playInfo.id == val.id) {
+				uni.navigateTo({
+					url: '../play/index'
+				});
+				return;
+			}
+			this.$store.dispatch('playMusic', {
+				src: '',
+				title: val.name,
+				singer: getName(val),
+				coverImgUrl: getImage(val),
+				id: val.id
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.musicList {
+	width: 100%;
+	position: relative;
+	margin-top: 20rpx;
+	.music-item {
+		height: 70px;
+		box-sizing: border-box;
+		padding: 0 20px;
+		align-items: center;
+		margin-bottom: 10px;
+		&:last-of-type {
+			margin-bottom: 0;
+		}
+		&.active {
+			.music-info {
+				.music-name,
+				.small-icon,
+				.music-singer {
+					color: #f84e51 !important;
+				}
+			}
+		}
+		.music-img {
+			width: 58px;
+			border-radius: 6px;
+		}
+		.music-info {
+			margin-left: 15px;
+			max-width: calc(100% - 80px);
+			.music-name {
+				font-size: 30rpx;
+				margin-bottom: 7px;
+				color: #000;
+			}
+			.music-singer {
+				color: rgba(0, 0, 0, 0.5);
+				font-size: 24rpx;
+				align-items: center;
+				width: 100%;
+				.small-icon {
+					margin-right: 6px;
+					transform: scale(0.9);
+					color: rgba(0, 0, 0, 0.5);
+					font-size: 12px;
+					padding: 1px 3px;
+					border: 1px solid rgba(0, 0, 0, 0.2);
+					border-radius: 4px;
+				}
+			}
+		}
+	}
+}
+</style>

+ 233 - 0
sleep/pages/singer/index.vue

@@ -0,0 +1,233 @@
+<template>
+	<view class="singer-container">
+		<cu-custom bgColor="#fff" :isBack="true"><view slot="content" style="color: #000">热门歌手</view></cu-custom>
+		<scroll-view scroll-y class="indexes" :scroll-into-view="'indexes-' + listCurID" :style="[{ height: height }]" :scroll-with-animation="true" :enable-back-to-top="true">
+			<block v-for="(item, index) in list" :key="index">
+				<view :class="'indexItem-' + item.name" :id="'indexes-' + item.name" :data-index="item.name">
+					<view class="padding">{{ item.name }}</view>
+					<view class="cu-list page-background menu-avatar no-padding">
+						<view class="cu-item bg-white" v-for="val in item.value" :key="val.id" @click="searchDetailSinger(val)">
+							<view class="cu-avatar round lg" :style="'background-image:url(' + val.picUrl + '?param=100y100)'"></view>
+							<view class="content">
+								<view class="singer-name text-overflow">{{ val.name }}</view>
+								<view class="singer-count" v-if="val.musicSize">{{ val.musicSize }}首</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</block>
+		</scroll-view>
+		<view class="indexBar" :style="[{ height: 'calc(' + height + ' - 50px)' }]">
+			<view class="indexBar-box" @touchstart="tStart" @touchend="tEnd" @touchmove.stop="tMove">
+				<view
+					class="indexBar-item"
+					:class="{ active: item.name === listCur }"
+					v-for="(item, index) in list"
+					:key="index"
+					:id="index"
+					@touchstart="getCur"
+					@touchend="setCur"
+				>
+					{{ item.name }}
+				</view>
+			</view>
+		</view>
+		<!--选择显示-->
+		<view v-show="!hidden" class="indexToast">{{ listCur }}</view>
+		<music-control v-if="playInfo.id" />
+	</view>
+</template>
+
+<script>
+import { getGroupByPinyin } from '../../plugins/pinyin/utils.js';
+export default {
+	data() {
+		return {
+			hidden: true,
+			listCurID: '',
+			list: [],
+			listCur: ''
+		};
+	},
+	computed: {
+		height() {
+			let height = this.CustomBar / (uni.upx2px(this.CustomBar) / this.CustomBar);
+			if (this.playInfo.id) {
+				height += 110;
+			}
+			return `calc(100vh  - ${height}rpx)`;
+		},
+		playInfo() {
+			return this.$store.state.playInfo;
+		}
+	},
+	created() {
+		this.getSingerList();
+	},
+	onReady() {
+		let that = this;
+		uni.createSelectorQuery()
+			.select('.indexBar-box')
+			.boundingClientRect(function(res) {
+				that.boxTop = res.top;
+			})
+			.exec();
+		uni.createSelectorQuery()
+			.select('.indexes')
+			.boundingClientRect(function(res) {
+				that.barTop = res.top;
+			})
+			.exec();
+	},
+	methods: {
+		async getSingerList() {
+			const data = await this.$api.getSingerList();
+			let list = data.list.artists;
+
+			this.list = getGroupByPinyin(list);
+			this.listCur = this.list[0];
+		},
+		searchDetailSinger(val) {
+			uni.navigateTo({
+				url: '../singerPlayList/index?id=' + val.id
+			});
+		},
+		//获取文字信息
+		getCur(e) {
+			this.hidden = false;
+			this.listCur = this.list[e.target.id].name;
+		},
+		setCur(e) {
+			this.hidden = true;
+		},
+		//滑动选择Item
+		tMove(e) {
+			let y = e.touches[0].clientY,
+				offsettop = this.boxTop,
+				that = this;
+			//判断选择区域,只有在选择区才会生效
+			if (y > offsettop) {
+				let num = parseInt((y - offsettop) / 20);
+				this.listCur = that.list[num].name;
+			}
+		},
+		//触发全部开始选择
+		tStart() {
+			this.hidden = false;
+		},
+		//触发结束选择
+		tEnd() {
+			this.hidden = true;
+			this.listCurID = this.listCur;
+		},
+		indexSelect(e) {
+			let that = this;
+			let barHeight = this.barHeight;
+			let list = this.list;
+			let scrollY = Math.ceil((list.length * e.detail.y) / barHeight);
+			for (let i = 0; i < list.length; i++) {
+				if (scrollY < i + 1) {
+					that.listCur = list[i].name;
+					that.movableY = i * 20;
+					return false;
+				}
+			}
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.singer-container {
+	width: 100%;
+	height: 100%;
+	position: relative;
+	.indexes {
+		position: relative;
+		.cu-item {
+			margin-bottom: 1px;
+			.singer-name {
+				font-size: 30rpx;
+				color: #000;
+			}
+			.singer-count {
+				margin-top: 2px;
+				color: rgba(0, 0, 0, 0.5);
+				font-size: 24rpx;
+			}
+		}
+
+		.cu-list.menu-avatar > .cu-item:after {
+			display: none !important;
+		}
+	}
+	.indexBar {
+		position: fixed;
+		right: 0px;
+		padding: 20upx 20upx 20upx 60upx;
+		display: flex;
+		top: 50%;
+		transform: translateY(-50%);
+		align-items: center;
+		.indexBar-box {
+			width: 40upx;
+			height: auto;
+			background: #fff;
+			display: flex;
+			flex-direction: column;
+			box-shadow: 0 0 20upx rgba(0, 0, 0, 0.1);
+			border-radius: 10upx;
+			.indexBar-item {
+				flex: 1;
+				width: 40upx;
+				height: 40upx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				font-size: 24upx;
+				color: rgba(0, 0, 0, 0.5);
+				margin-top: 3px;
+				&:last-of-type {
+					margin-bottom: 3px;
+				}
+				&.active {
+					color: rgb(248, 78, 81) !important;
+					font-weight: 600;
+					font-size: 28upx;
+				}
+			}
+			movable-view.indexBar-item {
+				width: 40upx;
+				height: 40upx;
+				z-index: 9;
+				position: relative;
+			}
+			movable-view.indexBar-item::before {
+				content: '';
+				display: block;
+				position: absolute;
+				left: 0;
+				top: 10upx;
+				height: 20upx;
+				width: 4upx;
+				background-color: #f37b1d;
+			}
+		}
+	}
+	.indexToast {
+		position: fixed;
+		top: 0;
+		right: 80upx;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.5);
+		width: 100upx;
+		height: 100upx;
+		border-radius: 10upx;
+		margin: auto;
+		color: #fff;
+		line-height: 100upx;
+		text-align: center;
+		font-size: 48upx;
+	}
+}
+</style>

+ 283 - 0
sleep/pages/singerPlayList/index.vue

@@ -0,0 +1,283 @@
+<template>
+	<view class="singerInfo-container">
+		<view class="vague-wrapper bg-img bg-mask flex align-center" :style="{ 'background-image': 'url(' + singerInfo.cover + ')' }">
+			<cu-custom class="head-title" :isBack="true" bgColor="unset"></cu-custom>
+			<view class="day-recommend-info" :style="{ 'padding-top': '40px'}">
+				<view class="description-wrapper">
+					<view class="title">{{ singerInfo.name }}</view>
+					<view class="tags flex">
+						<view class="cu-capsule round" style="margin-right: 20px;">
+							<view class="cu-tag tag-text">歌曲</view>
+							<view class="cu-tag ">{{ singerInfo.musicSize }}</view>
+						</view>
+						<view class="cu-capsule round ">
+							<view class="cu-tag tag-text">MV</view>
+							<view class="cu-tag ">{{ singerInfo.mvSize }}</view>
+						</view>
+					</view>
+					<view class="description" v-if="singerInfo.briefDesc">简介:{{ singerInfo.briefDesc }}</view>
+				</view>
+			</view>
+		</view>
+		<view class="recommend-main" :class="{ hasPlayInfo: playInfo.id }">
+			<view class="recommend-list">
+				<view class="music-title" @click="handlePlayAllMusic">
+					<text class="iconfont icon-kaishi3 basic-icon-color playIcon"></text>
+					全部播放
+					<text class="light-text">(共{{ total }}首)</text>
+				</view>
+				<scroll-view scroll-y scroll-with-animation style="height: calc(100% - 55px)" @scrolltolower="reachBottom()">
+					<view class="music-item flex" :class="{ active: item.id == playInfo.id }" v-for="(item, index) in musicList" :key="item.id" @click="handlePlayMusic(item)">
+						<view class="index">{{ index + 1 }}</view>
+						<view class="music-info">
+							<view class="music-name text-overflow">{{ item.name }}</view>
+							<view class="music-singer text-overflow flex">
+								<span class="small-icon">{{ item.id % 2 == 0 ? 'SQ' : 'HD' }}</span>
+								{{ item.ar ? item.ar.map(item => item.name).join('/') : '' }}
+							</view>
+						</view>
+					</view>
+				</scroll-view>
+			</view>
+		</view>
+		<music-control v-if="playInfo.id" />
+	</view>
+</template>
+
+<script>
+import { getImage, getName } from '@/utils/index.js';
+import { mapState } from 'vuex';
+export default {
+	data() {
+		return {
+			singerInfo: {},
+			musicList: [],
+			status: null,
+			total: 0,
+			offset: 0,
+			limit: 30,
+			id: ''
+		};
+	},
+	onLoad(val) {
+		this.id = val.id;
+		this.getDetail();
+		this.getSingerSong();
+	},
+	computed: mapState({
+		playInfo: state => state.playInfo
+	}),
+	methods: {
+		async getDetail() {
+			const { id } = this;
+			const { data } = await this.$api.getSingerInfo({ id });
+			this.singerInfo = data.artist || {};
+		},
+
+		async getSingerSong() {
+			uni.showLoading({
+				title: '加载中...'
+			});
+			const { id, offset, limit } = this;
+			const list = await this.$api.getSingerAllMusic({ id, order: 'hot', offset, limit });
+			this.total = list.total || 0;
+			this.musicList.push(...list.songs);
+			this.status = null;
+			uni.hideLoading();
+		},
+
+		//加载更多
+		reachBottom() {
+			this.status = 'loading';
+			if (this.offset >= this.total) {
+				this.status = 'notMore';
+				return;
+			}
+			this.offset += 50;
+			if (this.offset > this.total) {
+				this.offset = this.total;
+			}
+			this.getSingerSong();
+		},
+		//点击播放
+		handlePlayMusic(val) {
+			if (this.playInfo.id == val.id) {
+				uni.navigateTo({
+					url: '../play/index'
+				});
+				return;
+			}
+			this.$store.dispatch('playMusic', {
+				src: '',
+				title: val.name,
+				singer: getName(val),
+				coverImgUrl: this.singerInfo.cover + '?param=300y300',
+				id: val.id
+			});
+		},
+		handlePlayAllMusic() {
+			const list = this.musicList.map(item => {
+				return {
+					src: '',
+					title: item.name,
+					singer: getName(item),
+					coverImgUrl:this.singerInfo.cover + '?param=300y300' ,
+					id: item.id
+				};
+			});
+			this.$store.dispatch('playAllMUsic', list);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.singerInfo-container {
+	width: 100%;
+	height: 100%;
+	.vague-wrapper {
+		height: 30%;
+		width: 100%;
+		position: relative;
+		.head-title {
+			color: #fff;
+			position: absolute;
+			top: 0;
+			width: 100%;
+			z-index: 9999;
+		}
+		.day-recommend-info {
+			box-sizing: border-box;
+			height: 100%;
+			display: flex;
+			align-items: center;
+			// justify-content: center;
+			width: 100%;
+			.bgImg {
+				width: 200rpx;
+				border-radius: 8px;
+				margin-right: 20px;
+			}
+			.description-wrapper {
+				box-sizing: border-box;
+				padding: 0 15px;
+				.title {
+					font-size: 40rpx;
+					color: #fff;
+				}
+				.tags {
+					margin: 15px 0;
+					.tag-text {
+						background-color: rgb(248, 78, 81);
+						color: #fff;
+					}
+				}
+				.description {
+					font-size: 24rpx;
+					color: #e1d7f0;
+					margin-top: 17rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					display: -webkit-box;
+					-webkit-box-orient: vertical;
+					-webkit-line-clamp: 2;
+					line-height: 18px;
+				}
+			}
+		}
+	}
+	.recommend-main {
+		height: 70%;
+		width: 100%;
+		position: relative;
+		&.hasPlayInfo {
+			height: calc(70% - 110rpx);
+		}
+		.recommend-list {
+			position: absolute;
+			top: -5%;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: #fff;
+			border-top-left-radius: 26px;
+			border-top-right-radius: 26px;
+			.music-title {
+				padding-left: 20px;
+				margin: 15px 0;
+				box-sizing: border-box;
+				color: #000;
+				font-size: 32rpx;
+				font-weight: 600;
+				.playIcon {
+					margin-right: 8px;
+					font-size: 40rpx;
+				}
+				.light-text {
+					font-size: 24rpx;
+					margin-left: 6px;
+					color: rgba(0, 0, 0, 0.5);
+				}
+			}
+			.music-item {
+				height: 70px;
+				box-sizing: border-box;
+				padding: 0 20px;
+				align-items: center;
+				margin-bottom: 10px;
+				&:last-of-type {
+					margin-bottom: 0;
+				}
+				&.active {
+					background-image: linear-gradient(to right, rgba(247, 73, 79, 0.1), rgba(247, 73, 79, 0.05));
+					.music-info {
+						.music-name,
+						.small-icon,
+						.music-singer {
+							color: #f84e51 !important;
+						}
+						&::before {
+							content: '';
+							width: 4px;
+							height: 65px;
+							background-image: linear-gradient(to bottom, rgb(253, 117, 102), rgb(247, 73, 79));
+							position: absolute;
+							left: 0px;
+							top: 3px;
+						}
+					}
+				}
+				.index {
+					color: #000;
+					margin-right: 3px;
+				}
+				.music-info {
+					margin-right: 15px;
+					margin-left: 15px;
+					width: calc(100vw - 135px);
+					text-align: left;
+					.music-name {
+						font-size: 30rpx;
+						margin-bottom: 7px;
+						color: #000;
+					}
+					.music-singer {
+						color: rgba(0, 0, 0, 0.5);
+						font-size: 24rpx;
+						align-items: center;
+						.small-icon {
+							margin-right: 6px;
+							transform: scale(0.9);
+							color: rgba(0, 0, 0, 0.5);
+							font-size: 12px;
+							padding: 1px 3px;
+							border: 1px solid rgba(0, 0, 0, 0.2);
+							border-radius: 4px;
+						}
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 276 - 0
sleep/pages/songDetail/index.vue

@@ -0,0 +1,276 @@
+<template>
+	<view class="song-detail-container">
+		<view class="vague-wrapper  flex align-center">
+			<image src="../../static/background.png" class="background-img"></image>
+			<view class="absolve-wrapper">
+			<cu-custom class="head-title" :isBack="true" bgColor="unset"><block slot="content">歌单详情</block></cu-custom>
+			<view class="day-recommend-info" :style="{ 'padding-top': CustomBar + 'px' }">
+				<image :src="bgImg" mode="widthFix" class="bgImg"></image>
+				<view class="song-info">
+					<view class="name text-overflow">{{ currentPlayList.name }}</view>
+					<view class="content" v-if="currentPlayList.creator">
+						<image :src="currentPlayList.creator.avatarUrl + '?param45y45'" mode="" class="avatar"></image>
+						{{ currentPlayList.creator.nickname }}
+					</view>
+					<view class="description" v-if="currentPlayList.description">简介:{{ currentPlayList.description }}</view>
+				</view>
+			</view>
+		</view>
+		</view>
+		<view class="recommend-main" :class="{ hasPlayInfo: playInfo.id }">
+			<view class="recommend-list">
+				<view class="music-title" @click="handlePlayAllMusic">
+					<text class="iconfont icon-kaishi3 basic-icon-color playIcon"></text>
+					全部播放
+					<text class="light-text">(共{{ musicList.length }}首)</text>
+				</view>
+				<scroll-view scroll-y scroll-with-animation style="height: calc(100% - 55px)">
+					<view class="music-item flex" :class="{ active: item.id == playInfo.id }" v-for="(item, index) in musicList" :key="item.id" @click="handlePlayMusic(item)">
+						<image :src="item.al.picUrl + '?param=60y60'" mode="widthFix" class="music-img"></image>
+						<view class="music-info">
+							<view class="music-name text-overflow">{{ item.name }}</view>
+							<view class="music-singer text-overflow flex">
+								<span class="small-icon">{{ item.id % 2 == 0 ? 'SQ' : 'HD' }}</span>
+								{{ item.ar ? item.ar.map(item => item.name).join('/') : '' }}
+							</view>
+						</view>
+					</view>
+				</scroll-view>
+			</view>
+		</view>
+		<music-control v-if="playInfo.id" />
+	</view>
+</template>
+
+<script>
+import { getImage, getName } from '@/utils/index.js';
+import { mapState } from 'vuex';
+export default {
+	data() {
+		return {
+			currentPlayList: {},
+			musicList: [],
+			bgImg: '',
+			CustomBar:this.CustomBar-15
+		};
+	},
+	onLoad(val) {
+		this.getPlayDetail(val.id);
+	},
+	computed: mapState({
+		playInfo: state => state.playInfo
+	}),
+	methods: {
+		async getPlayDetail(id) {
+			uni.showLoading({
+				title: '加载中...'
+			});
+			const data = await this.$api.getPlayListData({ id });
+			this.currentPlayList = data.playlist || [];
+			this.bgImg = this.currentPlayList.coverImgUrl + 'param?300y300';
+			const arr = data.playlist.trackIds.map(item => item.id).join(',');
+			const list = await this.$api.getPlayDetail({ ids: arr });
+			this.musicList = list.songs || [];
+			uni.hideLoading();
+		},
+		//点击播放
+		handlePlayMusic(val) {
+			if (this.playInfo.id == val.id) {
+				uni.navigateTo({
+					url: '../play/index'
+				});
+				return;
+			}
+			this.$store.dispatch('playMusic', {
+				src: '',
+				title: val.name,
+				singer: getName(val),
+				coverImgUrl: getImage(val),
+				id: val.id
+			});
+		},
+
+		//播放全部
+		handlePlayAllMusic() {
+			const list = this.musicList.map(item => {
+				return {
+					src: '',
+					title: item.name,
+					singer: getName(item),
+					coverImgUrl: getImage(item),
+					id: item.id
+				};
+			});
+			this.$store.dispatch('playAllMUsic', list);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.song-detail-container {
+	width: 100%;
+	height: 100%;
+	.vague-wrapper {
+		height: 30%;
+		width: 100%;
+		position: relative;
+		.background-img{
+		   width: 100%;
+		   height: 100%;
+		}
+		.absolve-wrapper{
+			position: absolute;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			top: 0;
+			width: 100%;
+			height: 100%;
+			background-color: rgba(0,0,0,0.5);
+		}
+		.head-title {
+			color: #fff;
+			position: absolute;
+			top: 0;
+			width: 100%;
+			z-index: 9999;
+		}
+		.day-recommend-info {
+			box-sizing: border-box;
+			height: 100%;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			width: 100%;
+			.bgImg {
+				width: 200rpx;
+				border-radius: 8px;
+				margin-right: 20px;
+			}
+			.song-info {
+				margin-bottom: 12rpx;
+				color: #fff;
+				max-width: calc(100% - 175px);
+				.name {
+					font-size: 32rpx;
+				}
+				.content {
+					margin: 15rpx 0;
+					color: #fff;
+					.avatar {
+						width: 25px;
+						height: 25px;
+						border-radius: 50%;
+						display: inline-block;
+						vertical-align: middle;
+						margin-right: 6px;
+					}
+				}
+				.description {
+					font-size: 24rpx;
+					color: #e1d7f0;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					display: -webkit-box;
+					-webkit-box-orient: vertical;
+					-webkit-line-clamp: 2;
+					line-height: 18px;
+				}
+			}
+		}
+	}
+	.recommend-main {
+		height: 70%;
+		width: 100%;
+		position: relative;
+		&.hasPlayInfo {
+			height: calc(70% - 110rpx);
+		}
+		.recommend-list {
+			position: absolute;
+			top: -5%;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: #fff;
+			border-top-left-radius: 26px;
+			border-top-right-radius: 26px;
+			.music-title {
+				padding-left: 20px;
+				margin: 15px 0;
+				box-sizing: border-box;
+				color: #000;
+				font-size: 32rpx;
+				font-weight: 600;
+				.playIcon {
+					margin-right: 8px;
+					font-size: 40rpx;
+				}
+				.light-text {
+					font-size: 24rpx;
+					margin-left: 6px;
+					color: rgba(0, 0, 0, 0.5);
+				}
+			}
+			.music-item {
+				height: 70px;
+				box-sizing: border-box;
+				padding: 0 20px;
+				align-items: center;
+				margin-bottom: 10px;
+				width: 100%;
+				&:last-of-type {
+					margin-bottom: 0;
+				}
+				&.active {
+					background-image: linear-gradient(to right, rgba(247, 73, 79, 0.1), rgba(247, 73, 79, 0.05));
+					.music-info {
+						.music-name,
+						.small-icon,
+						.music-singer {
+							color: #f84e51 !important;
+						}
+						&::before {
+							content: '';
+							width: 4px;
+							height: 65px;
+							background-image: linear-gradient(to bottom, rgb(253, 117, 102), rgb(247, 73, 79));
+							position: absolute;
+							left: 0px;
+							top: 3px;
+						}
+					}
+				}
+				.music-img {
+					width: 58px;
+					border-radius: 6px;
+				}
+				.music-info {
+					margin-left: 15px;
+					max-width: calc(100% - 75px);
+					.music-name {
+						font-size: 30rpx;
+						margin-bottom: 7px;
+						color: #000;
+					}
+					.music-singer {
+						color: rgba(0, 0, 0, 0.5);
+						font-size: 24rpx;
+						align-items: center;
+						.small-icon {
+							margin-right: 6px;
+							transform: scale(0.9);
+							color: rgba(0, 0, 0, 0.5);
+							font-size: 12px;
+							padding: 1px 3px;
+							border: 1px solid rgba(0, 0, 0, 0.2);
+							border-radius: 4px;
+						}
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 266 - 0
sleep/pages/songList/index.vue

@@ -0,0 +1,266 @@
+<template>
+	<div class="playList-container">
+		<cu-custom bgColor="#fff" :isBack="true"><view slot="content" style="color: #000">歌单</view></cu-custom>
+		<scroll-view scroll-x class="bg-white nav" scroll-with-animation :scroll-left="scrollLeft">
+			<view class="cu-item" :class="item.name == currentName ? 'basic-icon-color cur' : ''" v-for="(item, index) in tagList" :key="index" @click="tabSelect(item, index)">
+				{{ item.name }}
+			</view>
+		</scroll-view>
+		<scroll-view scroll-y scroll-with-animation @scrolltolower="reachBottom()" :style="{ height: height, 'margin-top': '20rpx' }">
+			<box-title title="热门歌单" style="margin-top: 10px;"></box-title>
+			<view class="tower-swiper" @touchmove="TowerMove" @touchstart="TowerStart" @touchend="TowerEnd">
+				<view
+					class="tower-item"
+					:class="item.zIndex == 1 ? 'none' : ''"
+					v-for="(item, index) in swiperList"
+					:key="index"
+					:style="[{ '--index': item.zIndex, '--left': item.mLeft }]"
+					:data-direction="direction"
+					@click="toPlayListDetail(item)"
+				>
+					<view class="swiper-item">
+						<image :src="item.coverImgUrl + '?param=200y200'" mode="aspectFill" class="swiper-item-img">
+							<view class="title" v-if="item.mLeft == 0">{{ item.name }}</view>
+						</image>
+					</view>
+				</view>
+			</view>
+			<box-title title="歌单列表" style="margin-top: 30px;"></box-title>
+			<div class="musicList">
+				<view class="list-item" v-for="item in recommendList" :key="item.id">
+					<view class="hotCount" v-if="item.playCount > 0 || item.playcount > 0">
+						<text class="iconfont icon-kaishi" style="margin-right: 3.5px;"></text>
+						<text>{{ playCount(item.playCount || item.playcount) }}</text>
+					</view>
+					<view class="bg-img flex align-end list-item-image" :style="'background-image:url(' + item.coverImgUrl + '?param=230y230)'" @click="toPlayListDetail(item)">
+						<view class="bg-shadeBottom padding-top-xl radioName">{{ item.name }}</view>
+					</view>
+					<view class="list-item-text">{{ item.name }}</view>
+				</view>
+				<view class="loading" v-if="status == 'loading' || status == 'notMore'">{{ status == 'notMore' ? '没有更多了' : '努力加载中...' }}</view>
+			</div>
+		</scroll-view>
+		<music-control v-if="playInfo.id" />
+	</div>
+</template>
+
+<script>
+import { filterPlayCount } from '@/utils/index.js';
+export default {
+	data() {
+		return {
+			recommendList: [],
+			direction: '',
+			swiperList: [],
+			currentName: '全部',
+			scrollLeft: 0,
+			tagList: [],
+			status: null,
+			total: 0,
+			offset: 0
+		};
+	},
+	created() {
+		this.getSongTagList();
+		this.getRecommendData();
+	},
+	computed: {
+		height() {
+			let height = this.CustomBar / (uni.upx2px(this.CustomBar) / this.CustomBar)
+			if (this.playInfo.id) {
+				height += 110;
+			}
+			return `calc(100%  - ${height}rpx)`;
+		},
+		playInfo() {
+			return this.$store.state.playInfo;
+		}
+	},
+	methods: {
+		//获取歌单分类
+		async getSongTagList() {
+			const data = await this.$api.getSongTagList();
+			let list = data.sub || [];
+			if (list.length > 0) {
+				list = list.slice(0, 10);
+			}
+			this.tagList = [{ name: '全部' }, ...list];
+		},
+
+		//加载更多
+		reachBottom() {
+			this.status = 'loading';
+			if (this.offset >= this.total) {
+				this.status = 'notMore';
+				return;
+			}
+			this.offset += 30;
+			if (this.offset > this.total) {
+				this.offset = this.total;
+			}
+
+			this.getRecommendData(true);
+		},
+
+		//获取对应歌单
+		async getRecommendData(bool) {
+			const cat = this.currentName;
+			const offset = this.offset;
+			const data = await this.$api.getSongList({ cat, offset, limit: 30, order: 'hot' });
+			this.total = data.total || 0;
+			let list = data.playlists || [];
+			if (bool) {
+				this.recommendList.push(...list);
+				this.status = null;
+			} else {
+				let swiperList = list.slice(0, 6);
+				swiperList.forEach((item, index) => {
+					item.zIndex = parseInt(swiperList.length / 2) + 1 - Math.abs(index - parseInt(swiperList.length / 2));
+					item.mLeft = index - parseInt(swiperList.length / 2);
+				});
+				this.swiperList = swiperList;
+				this.recommendList = list.slice(6);
+			}
+		},
+
+		playCount(val) {
+			return filterPlayCount(val);
+		},
+
+		tabSelect(val, index) {
+			this.currentName = val.name;
+			this.scrollLeft = (index - 1) * 60;
+			this.offset = 0;
+			this.total = 0;
+			this.swiperList = [];
+			this.recommendList = [];
+			this.getRecommendData();
+		},
+		// towerSwiper触摸开始
+		TowerStart(e) {
+			this.towerStart = e.touches[0].pageX;
+		},
+		// towerSwiper计算方向
+		TowerMove(e) {
+			this.direction = e.touches[0].pageX - this.towerStart > 0 ? 'right' : 'left';
+		},
+		// towerSwiper计算滚动
+		TowerEnd(e) {
+			let direction = this.direction;
+			let list = this.swiperList;
+			if (direction == 'right') {
+				let mLeft = list[0].mLeft;
+				let zIndex = list[0].zIndex;
+				for (let i = 1; i < this.swiperList.length; i++) {
+					this.swiperList[i - 1].mLeft = this.swiperList[i].mLeft;
+					this.swiperList[i - 1].zIndex = this.swiperList[i].zIndex;
+				}
+				this.swiperList[list.length - 1].mLeft = mLeft;
+				this.swiperList[list.length - 1].zIndex = zIndex;
+			} else {
+				let mLeft = list[list.length - 1].mLeft;
+				let zIndex = list[list.length - 1].zIndex;
+				for (let i = this.swiperList.length - 1; i > 0; i--) {
+					this.swiperList[i].mLeft = this.swiperList[i - 1].mLeft;
+					this.swiperList[i].zIndex = this.swiperList[i - 1].zIndex;
+				}
+				this.swiperList[0].mLeft = mLeft;
+				this.swiperList[0].zIndex = zIndex;
+			}
+			this.direction = '';
+			this.swiperList = this.swiperList;
+		},
+
+		toPlayListDetail(val) {
+			uni.navigateTo({
+				url: '../songDetail/index?id=' + val.id
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.playList-container {
+	width: 100%;
+	height: 100%;
+	.tower-swiper {
+		height: 400rpx !important;
+		overflow: unset;
+		.tower-item {
+			width: 330rpx;
+			transform: scale(calc(0.5 + var(--index) / 10));
+			margin-left: calc(var(--left) * 100upx - 150upx);
+			z-index: var(--index);
+			.swiper-item {
+				.swiper-item-img {
+					border-radius: 6px;
+				}
+				.title {
+					text-align: center;
+					margin-top: 10px;
+					position: absolute;
+					color: rgba(0, 0, 0, 0.5);
+					font-size: 14px;
+				}
+			}
+		}
+	}
+	.musicList {
+		padding-left: 15px;
+		width: 100%;
+		position: relative;
+		box-sizing: border-box;
+		margin-top: 15px;
+		.list-item {
+			width: calc(100% / 3 - 15px);
+			margin-right: 15px;
+			margin-bottom: 7px;
+			display: inline-block;
+			overflow: hidden;
+			position: relative;
+
+			.hotCount {
+				position: absolute;
+				width: 100%;
+				height: 50px;
+				padding: 3px 6px 0 0;
+				text-align: right;
+				color: #fff;
+				z-index: 10;
+				font-size: 12px;
+				background-image: linear-gradient(rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0));
+				border-radius: 6px;
+			}
+
+			.list-item-image {
+				height: 115px;
+				border-radius: 6px;
+			}
+
+			.list-item-text {
+				height: 35px;
+				width: 100%;
+				padding-top: 2px;
+				white-space: normal;
+				color: rgba(0, 0, 0, 0.5);
+				font-size: 12px;
+				display: -webkit-box;
+				-webkit-box-orient: vertical;
+				-webkit-line-clamp: 2;
+				overflow: hidden;
+			}
+
+			.radioName {
+				width: 100%;
+				color: #fff;
+				font-size: 12px;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				padding: 0 0 8px 8px;
+			}
+		}
+	}
+}
+</style>

+ 13 - 0
sleep/plugins/audio/index.js

@@ -0,0 +1,13 @@
+import ZAudio from "./zaudio.js"
+import {getCache} from '@/utils/cache.js'
+
+let audio = new ZAudio({
+	continuePlay: false, //续播
+	autoPlay: false, //自动播放 部分浏览器不支持
+});
+
+audio.setAudio(getCache('PLAY_LIST'))
+
+module.exports={
+  audio
+}

+ 74 - 0
sleep/plugins/audio/util.js

@@ -0,0 +1,74 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.EventBus = exports.throttle = exports.formatSeconds = void 0;
+function formatSeconds(seconds) {
+    var result = typeof seconds === "string" ? parseFloat(seconds) : seconds;
+    if (isNaN(result))
+        return "";
+    let h = Math.floor(result / 3600) < 10
+        ? "0" + Math.floor(result / 3600)
+        : Math.floor(result / 3600);
+    let m = Math.floor((result / 60) % 60) < 10
+        ? "0" + Math.floor((result / 60) % 60)
+        : Math.floor((result / 60) % 60) + h * 60;
+    let s = Math.floor(result % 60) < 10
+        ? "0" + Math.floor(result % 60)
+        : Math.floor(result % 60);
+    return `${m}:${s}`;
+}
+exports.formatSeconds = formatSeconds;
+function throttle(fn, wait) {
+    let previous = 0;
+    return function (...arg) {
+        let context = this;
+        let now = Date.now();
+        //每隔一段时间执行一次;
+        if (now - previous > wait) {
+            fn.apply(context, arg);
+            previous = now;
+        }
+    };
+}
+exports.throttle = throttle;
+class EventBus {
+    constructor() {
+        this._events = new Map();
+    }
+    on(event, action, fn) {
+        let arr = this._events.get(event);
+        let hasAction = arr
+            ? arr.findIndex((i) => i.action == action)
+            : -1;
+        if (hasAction > -1) {
+            return;
+        }
+        this._events.set(event, [
+            ...(this._events.get(event) || []),
+            {
+                action,
+                fn,
+            },
+        ]);
+    }
+    has(event) {
+        return this._events.has(event);
+    }
+    emit(event, data) {
+        if (!this.has(event)) {
+            return;
+        }
+        let arr = this._events.get(event);
+        arr.forEach((i) => {
+            i.fn(data);
+        });
+    }
+    off(event, action) {
+        if (!this.has(event)) {
+            return;
+        }
+        let arr = this._events.get(event);
+        let newdata = arr.filter((i) => i.action !== action);
+        this._events.set(event, [...newdata]);
+    }
+}
+exports.EventBus = EventBus;

+ 597 - 0
sleep/plugins/audio/zaudio.js

@@ -0,0 +1,597 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var zaudioCbName;
+(function (zaudioCbName) {
+    zaudioCbName["onWaiting"] = "waiting";
+    zaudioCbName["onError"] = "error";
+    zaudioCbName["onTimeUpdate"] = "playing";
+    zaudioCbName["onCanplay"] = "canPlay";
+    zaudioCbName["onPause"] = "pause";
+    zaudioCbName["onEnded"] = "ended";
+    zaudioCbName["setAudio"] = "setAudio";
+    zaudioCbName["updateAudio"] = "updateAudio";
+    zaudioCbName["seek"] = "seek";
+    zaudioCbName["onStop"] = "stop";
+    zaudioCbName["syncStateOn"] = "syncStateOn";
+})(zaudioCbName || (zaudioCbName = {}));
+let zaudioCbNameArr = [];
+for (const key in zaudioCbName) {
+    if (Object.prototype.hasOwnProperty.call(zaudioCbName, key)) {
+        const item = zaudioCbName[key];
+        zaudioCbNameArr.push(item);
+    }
+}
+const util_1 = require("./util");
+/**
+ * ZAudio类
+ * @class ZAudio
+ * @constructor
+ * @param    {String}    defaultCover    音频默认封面
+ * @param    {Boolean}   continuePlay    继续播放,错误播放或结束播放后执行
+ * @param    {Boolean}   autoPlay        自动播放,部分浏览器不支持
+ * @property {Number}         renderIndex     当前渲染索引
+ * @property {<audioinfo>}      renderinfo      当前渲染数据
+ * @property {Array<audio>}   audiolist       音频列表数组
+ * @property {<audioinfo>}      playinfo        当前播放数据
+ * @property {Boolean}        paused          音频暂停状态
+ * @property {Number}         playIndex       当前播放索引
+ * @property {Boolean}        renderIsPlay    渲染与播放是否一致
+ *
+ * @method on(event, action, fn)       回调函数注册业务事件
+ * @method off(event, action)          回调函数中卸载业务事件
+ * @method setRender(data)             指定音频, 渲染到zaudio组件
+ * @method syncRender()    						 同步并渲染当前的播放状态
+ * @method operate(index)       			 播放或暂停指定索引的音频
+ * @method setAudio(list)		   				 覆盖音频列表
+ * @method updateAudio(list)   				 添加音频列表
+ * @method stop()          						 强制暂停当前播放音频
+ * @method stepPlay(count)      				快进快退
+ * @method syncStateOn(action, cb)       	注册一个用于同步获取当前播放状态的事件
+ * @method syncStateOff(action)     		卸载用于同步获取当前播放状态的事件
+ *
+ *
+ * **/
+class ZAudio extends util_1.EventBus {
+    constructor(options) {
+        super();
+        this.loading = false;
+        this.renderIndex = 0;
+        this.audiolist = [];
+        this.renderinfo = {
+            current: "00:00",
+            duration: "00:00",
+            duration_value: 0,
+            current_value: 0,
+            src: "",
+            title: "",
+            singer: "",
+            coverImgUrl: "",
+        };
+        this.playinfo = {
+            current: "00:00",
+            duration: "00:00",
+            duration_value: 0,
+            current_value: 0,
+            src: "",
+            title: "",
+            singer: "",
+            coverImgUrl: "",
+        };
+        this.paused = true;
+        this.uPause = false;
+        this.autoPlay = false;
+        this.defaultCover = "";
+        this.continuePlay = true;
+        //fix: 防抖触发音频播放中事件
+        this.throttlePlaying = util_1.throttle(() => {
+            this.emit(zaudioCbName.onTimeUpdate, this.playinfo);
+            this.syncStateEmit();
+        }, 1000);
+        let { defaultCover, autoPlay, continuePlay } = options;
+        this.defaultCover = defaultCover;
+        this.autoPlay = autoPlay;
+        this.continuePlay = continuePlay;
+        this.init();
+    }
+    init() {
+        // #ifndef H5
+        var audioCtx = uni.getBackgroundAudioManager();
+        // #endif
+        // #ifdef H5
+        var audioCtx = uni.createInnerAudioContext();
+        audioCtx.autoplay = this.autoPlay;
+        // #endif
+        this.audioCtx = audioCtx;
+        this.audioCtx.onWaiting(this.onWaitingHandler.bind(this));
+        this.audioCtx.onCanplay(this.onCanplayHandler.bind(this));
+        this.audioCtx.onPlay(this.onPlayHandler.bind(this));
+        this.audioCtx.onPause(this.onPauseHandler.bind(this));
+        this.audioCtx.onStop(this.onStopHandler.bind(this));
+        this.audioCtx.onEnded(this.onEndedHandler.bind(this));
+        this.audioCtx.onTimeUpdate(this.onTimeUpdateHandler.bind(this));
+        this.audioCtx.onError(this.onErrorHandler.bind(this));
+        //fix: 修复iOS原生音频切换不起作用
+        //  #ifdef APP-PLUS
+        if (uni.getSystemInfoSync().platform == "ios") {
+            const bgMusic = plus.audio.createPlayer();
+            bgMusic.addEventListener("prev", () => {
+                this.changeplay(-1);
+            });
+            bgMusic.addEventListener("next", () => {
+                this.changeplay(1);
+            });
+        }
+        // #endif
+        // #ifndef H5
+        setTimeout(() => {
+            if (this.autoPlay) {
+                this.operate();
+            }
+        }, 500);
+        // #endif
+        this.appCheckReplay();
+    }
+    //检测on off的参数
+    checkEventParams(event, action, fn) {
+        if (zaudioCbNameArr.indexOf(event) < 0) {
+            console.error(`参数${event}错误, 必须为${zaudioCbNameArr.join(" | ")}中某一项`);
+            return false;
+        }
+        if (typeof action !== "string" && typeof action !== "symbol") {
+            console.error(`参数${action}错误, 参数必须为string或symbol类型`);
+            return false;
+        }
+        if (fn && typeof fn !== "function") {
+            console.error("fn参数错误");
+            return false;
+        }
+        return true;
+    }
+    /**
+     * @description 回调中卸载业务事件
+     * @param {<zaudioCbName>}   event     回调名称枚举值
+     * @param {Sting|Symbol}         action    业务函数名,用于区分不同业务
+     * @returns undefined
+     * **/
+    off(event, action) {
+        if (!this.checkEventParams(event, action))
+            return;
+        super.off(event, action);
+    }
+    /**
+     * @description 回调中注册业务事件
+     * @param {<zaudioCbName>}        event     回调名称枚举值
+     * @param {Sting|Symbol}              action    业务函数名,用于区分不同业务
+     * @param {function(object|string|number|undefined):undefined}      fn      业务函数, 参数或为音频状态
+     * @returns undefined
+     * **/
+    on(event, action, fn) {
+        if (!this.checkEventParams(event, action))
+            return;
+        super.on(event, action, fn);
+    }
+    /**
+     * @description 订阅触发音频回调
+     * @param {<zaudioCbName>}        event      回调名称枚举值,具体看zaudioCbName
+     * @param {object|string|number|undefined}     data        订阅触发回调时,传的音频属性
+     * @returns undefined
+     * **/
+    emit(event, data) {
+        super.emit(event, data);
+    }
+    commit(action, data) {
+        typeof this[action] === "function" && this[action](data);
+    }
+    onWaitingHandler() {
+        this.commit("setLoading", true);
+        this.emit(zaudioCbName.onWaiting, true);
+        this.syncStateEmit();
+    }
+    onCanplayHandler() {
+        this.emit(zaudioCbName.onCanplay, this.playinfo);
+        this.commit("setLoading", false);
+        this.syncStateEmit();
+    }
+    onPlayHandler() {
+        // #ifdef APP-PLUS
+        this.commit("setPlayinfo", {
+            duration: util_1.formatSeconds(this.audioCtx.duration),
+            duration_value: this.audioCtx.duration,
+        });
+        // #endif
+        this.commit("setPause", false);
+        this.commit("setUnnormalPause", false);
+    }
+    onPauseHandler() {
+        this.commit("setPause", true);
+        this.emit(zaudioCbName.onPause);
+        this.syncStateEmit();
+    }
+    onStopHandler() {
+        this.commit("setPause", true);
+        this.emit(zaudioCbName.onStop);
+        this.syncStateEmit();
+    }
+    onEndedHandler() {
+        this.commit("setPause", true);
+        this.audioCtx.startTime = 0;
+        this.commit("setPlayinfo", {
+            current: "00:00",
+            current_value: 0,
+            src: "",
+        });
+        this.emit(zaudioCbName.onEnded);
+        this.syncStateEmit();
+        //续播
+        if (this.continuePlay) {
+            this.changeplay(1);
+        }
+        else {
+            let nextkey = this.getNextKey(1);
+            this.commit("setRender", nextkey);
+        }
+    }
+    onTimeUpdateHandler() {
+        if (this.renderIsPlay) {
+            //fix: 解决播放进度大于总进度问题
+            let currentTime = this.audioCtx.currentTime > this.audioCtx.duration
+                ? this.audioCtx.duration
+                : this.audioCtx.currentTime;
+            this.commit("setPlayinfo", {
+                current: util_1.formatSeconds(currentTime),
+                current_value: currentTime,
+            });
+            // #ifndef APP-PLUS
+            //fix: 解决小程序与h5无法获取总进度的问题
+            if (this.audioCtx.duration != this.playinfo.duration_value) {
+                this.commit("setPlayinfo", {
+                    duration: util_1.formatSeconds(this.audioCtx.duration),
+                    duration_value: this.audioCtx.duration,
+                });
+            }
+            // #endif
+        }
+        this.throttlePlaying();
+    }
+    onErrorHandler() {
+        this.commit("setPause", true);
+        this.commit("setRender", {
+            src: "",
+            title: "",
+            singer: "",
+            coverImgUrl: "",
+        });
+        this.commit("setPlayinfo", {
+            current: "00:00",
+            current_value: 0,
+            duration: "00:00",
+            duration_value: 0,
+            title: "",
+            src: "",
+        });
+        this.emit(zaudioCbName.onError);
+        this.syncStateEmit();
+        if (this.continuePlay) {
+            this.changeplay(1);
+        }
+    }
+    /**
+     * @description 实时渲染当前状态
+     * @returns undefined
+     * **/
+    syncRender() {
+        this.setRender(this.playIndex);
+    }
+    /**
+     * @description 注册一个实时获取ZAudio属性的方法
+     * @param {String}        action      自定义业务名
+     * @param {Funtion}     fn        实时获取ZAudio属性回调
+     * @returns undefined
+     * **/
+    syncStateOn(action, fn) {
+        typeof fn === "function" && this.on(zaudioCbName.syncStateOn, action, fn);
+    }
+    /**
+     * @description 卸载实时获取ZAudio属性的方法
+     * @param {String}        action      自定义业务名
+     * @returns undefined
+     * **/
+    syncStateOff(action) {
+        this.off(zaudioCbName.syncStateOn, action);
+    }
+    /**
+     * @description 订阅实时获取ZAudio属性的方法
+     * @returns undefined
+     * **/
+    syncStateEmit() {
+        this.emit(zaudioCbName.syncStateOn, {
+            renderIndex: this.renderIndex,
+            audiolist: this.audiolist,
+            renderinfo: this.renderinfo,
+            playinfo: this.playinfo,
+            paused: this.paused,
+            playIndex: this.playIndex,
+            renderIsPlay: this.renderIsPlay,
+            loading: this.loading,
+        });
+    }
+    /**
+     * @description 跳转播放
+     * @param {Number}        value      跳转位置
+     * @returns undefined
+     * **/
+    seek(value) {
+        let val = value > this.audioCtx.duration ? this.audioCtx.duration : value;
+        this.audioCtx.seek(val);
+        this.commit("setPlayinfo", {
+            current: util_1.formatSeconds(val),
+            current_value: val,
+        });
+        // setTimeout(() => {
+        //   this.emit(zaudioCbName.seek, this.playinfo.current);
+        // }, 0);
+        this.emit(zaudioCbName.seek, this.playinfo.current);
+    }
+    /**
+     * @description 快进
+     * @param {Number}        value      跳转位置
+     * @returns undefined
+     * **/
+    stepPlay(value) {
+        if (this.renderIsPlay) {
+            let pos = this.playinfo.current_value + value;
+            this.seek(pos);
+        }
+    }
+    /**
+     * @description 获取下一首歌曲索引(用于渲染和播放)
+     * @param {Number}        count     切换数量
+     * @returns number
+     * **/
+    getNextKey(count) {
+        let nextkey = this.renderIndex;
+        nextkey += count;
+        nextkey =
+            nextkey < 0
+                ? this.audiolist.length - 1
+                : nextkey > this.audiolist.length - 1
+                    ? 0
+                    : nextkey;
+        return nextkey;
+    }
+    /**
+     * @description 切歌
+     * @param {Number}        count      数量
+     * @returns undefined
+     * **/
+    changeplay(count) {
+        let nextkey = this.getNextKey(count);
+        this.commit("setPause", true);
+        this.operate(nextkey);
+    }
+    /**
+     * @description 手动播放或暂停, 并渲染对应的数据
+     * @param {Number|String|<audioInfo>|undefined}        key      索引或音频对象
+     * @returns undefined
+     * **/
+    operate(key) {
+        key !== undefined && this.commit("setRender", key);
+        this.operation();
+    }
+    /**
+     * @description 强制暂停播放
+     * @returns undefined
+     * **/
+    stop() {
+        this.audioCtx.pause();
+        this.commit("setPause", true);
+        this.commit("setUnnormalPause", true);
+        this.emit(zaudioCbName.onStop);
+    }
+    //播放,暂停事件判断,
+    //播放数据与渲染数据相同时: 播放->暂停, 暂停->播放
+    //播放数据与渲染数据不相同时: 播放渲染音频
+    operation() {
+        return __awaiter(this, void 0, void 0, function* () {
+            const { duration, current, duration_value, current_value, src} = this.playinfo;
+            const { src: renderSrc, title: renderTitle, singer: renderSinger, coverImgUrl: renderCoverImgUrl, } = this.renderinfo;
+            let renderIsPlay = this.renderIsPlay;
+            let paused = this.paused;
+            if (!renderIsPlay) {
+                //渲染与播放地址 不同
+                this.audioCtx.src = renderSrc;
+                this.audioCtx.title = renderTitle;
+                this.audioCtx.singer = renderSinger;
+                this.audioCtx.coverImgUrl = renderCoverImgUrl || this.defaultCover;
+                this.audioCtx.startTime = 0;
+                this.audioCtx.seek(0);
+                this.audioCtx.play();
+                this.commit("setPause", false);
+                this.commit("setPlayinfo", {
+                    src: renderSrc,
+                    title: renderTitle,
+                    singer: renderSinger,
+                    coverImgUrl: renderCoverImgUrl,
+                });
+            }
+            else {
+                if (paused) {
+                    //渲染与播放地址相同
+                    this.audioCtx.play();
+                    this.audioCtx.startTime = current_value;
+                    // this.audioCtx.seek(current_value);
+                    this.commit("setPause", false);
+                    this.commit("setPlayinfo", {
+                        src: renderSrc,
+                        title: renderTitle,
+                        singer: renderSinger,
+                        coverImgUrl: renderCoverImgUrl,
+                    });
+                }
+                else {
+                    this.audioCtx.pause();
+                    this.commit("setPause", true);
+                    this.commit("setUnnormalPause", true);
+                }
+            }
+        });
+    }
+    /**
+     * @description 覆盖音频
+     * @param {Array<audio>} data 音频数组
+     * @returns undefined
+     * **/
+    setAudio(data) {
+        this.audiolist = [...data];
+        this.emit(zaudioCbName.setAudio, this.audiolist);
+        this.syncStateEmit();
+    }
+    /**
+     * @description 添加音频
+     * @param {Array<audio>} data 音频数组
+     * @returns undefined
+     * **/
+    updateAudio(data) {
+        this.audiolist.push(...data);
+        this.emit(zaudioCbName.updateAudio, this.audiolist);
+        this.syncStateEmit();
+    }
+    /**
+     * @description 设置当前播放信息
+     * @param {<audioInfo>} data 音频对象
+     * @returns undefined
+     * **/
+    setPlayinfo(data) {
+        for (let i in data) {
+            this.playinfo[i] = data[i];
+        }
+    }
+    /**
+     * @description 设置暂停状态
+     * @param {boolean} data 布尔值
+     * @returns undefined
+     * **/
+    setPause(data) {
+        this.paused = data;
+    }
+    /**
+     * @description 设置loading
+     * @param {boolean} data 布尔值
+     * @returns undefined
+     * **/
+    setLoading(data) {
+        this.loading = data;
+    }
+    /**
+     * @description 设置通话时暂停状态
+     * @param {boolean} data 布尔值
+     * @returns undefined
+     * **/
+    setUnnormalPause(data) {
+        this.uPause = data;
+    }
+    /**
+     * @description 设置渲染
+     * @param {number | string | audioInfo} data 索引或渲染信息
+     * @returns undefined
+     * **/
+    setRender(data) {
+        if (this.audiolist.length == 0)
+            return;
+        if (typeof data === "number" || typeof data === "string") {
+            this.renderIndex = typeof data === "string" ? parseInt(data) : data;
+            this.renderinfo = {
+                src: this.audiolist[this.renderIndex].src,
+                title: this.audiolist[this.renderIndex].title,
+                singer: this.audiolist[this.renderIndex].singer,
+                coverImgUrl: this.audiolist[this.renderIndex].coverImgUrl,
+                current: "00:00",
+                duration: "00:00",
+                current_value: 0,
+                duration_value: 100,
+            };
+        }
+        else {
+            this.renderinfo = data;
+            let renderIndex = this.audiolist.findIndex((i) => i.src == data.src);
+            if (renderIndex >= 0) {
+                this.renderIndex = renderIndex;
+            }
+        }
+        this.syncStateEmit();
+    }
+    //当前索引
+    get playIndex() {
+        let index = this.audiolist.findIndex((i) => i.src == this.playinfo.src);
+        return index <= 0 ? 0 : index;
+    }
+    //渲染与播放是否一致
+    get renderIsPlay() {
+        return this.renderinfo.src == this.playinfo.src;
+    }
+    //app端判断电话来电后, 音频意外中断之后的继续播放
+    appCheckReplay() {
+        let _t = this;
+        // #ifdef APP-PLUS
+        try {
+            if (uni.getSystemInfoSync().platform == "android") {
+                var main = plus.android.runtimeMainActivity();
+                var Context = plus.android.importClass("android.content.Context");
+                var telephonyManager = plus.android.importClass("android.telephony.TelephonyManager");
+                var telephonyManager = plus.android
+                    .runtimeMainActivity()
+                    .getSystemService(Context.TELEPHONY_SERVICE);
+                var receiver = plus.android.implements("io.dcloud.android.content.BroadcastReceiver", {
+                    onReceive: function (intent) {
+                        //实现onReceiver回调函数
+                        plus.android.importClass(intent);
+                        var telephonyManager = plus.android.importClass("android.telephony.TelephonyManager");
+                        var telephonyManager = plus.android
+                            .runtimeMainActivity()
+                            .getSystemService(Context.TELEPHONY_SERVICE);
+                        var phonetype = telephonyManager.getCallState();
+                        var phoneNumber = intent.getStringExtra(telephonyManager.EXTRA_INCOMING_NUMBER);
+                        if (phonetype == 0 && !_t.uPause) {
+                            _t.audioCtx.play();
+                        }
+                    },
+                });
+                var IntentFilter = plus.android.importClass("android.content.IntentFilter");
+                var filter = new IntentFilter();
+                filter.addAction(telephonyManager.ACTION_PHONE_STATE_CHANGED); //监听开关
+                main.registerReceiver(receiver, filter); //注册监听
+            }
+            else if (uni.getSystemInfoSync().platform == "ios") {
+                var callstatus = false;
+                var CTCall = plus.ios.importClass("CTCall");
+                var CTCallCenter = plus.ios.importClass("CTCallCenter");
+                var center = new CTCallCenter();
+                center.init();
+                center.setCallEventr(function (ctCall) {
+                    callstatus = !callstatus;
+                    if (!callstatus && !_t.uPause) {
+                        _t.audioCtx.play();
+                    }
+                    else {
+                        _t.audioCtx.pause();
+                    }
+                });
+            }
+        }
+        catch (err) {
+            console.warn(err);
+        }
+        // #endif
+    }
+}
+exports.default = ZAudio;
+ZAudio.version = "2.2.51";

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 403 - 0
sleep/plugins/pinyin/pinyin.js


+ 77 - 0
sleep/plugins/pinyin/toPinyin.js

@@ -0,0 +1,77 @@
+/*
+ * @Autor: lieft@qq.com
+ * @Date: 1970-01-01 08:00:00
+ * @LastEditors: hlb
+ * @LastEditTime: 2020-11-25 17:48:41
+ * @description: 转拼音 
+ */
+import pinyin from "./pinyin";
+
+class ToPinyin {
+    static instance;
+
+    constructor() {
+
+    }
+
+    static getInstance() {
+        if (!this.instance) {
+            this.instance = new ToPinyin();
+        }
+        return this.instance;
+    }
+
+    // 转首字母 this.chineseToInitials(this.chineseToPinYin('你好'))  this.chineseToInitials('NiHao')
+    chineseToInitials(word){
+        let SX = '';
+        for (var i = 0; i < word.length; i++) {
+          var c = word.charAt(i);
+          if (/^[A-Z]+$/.test(c)) {
+             SX += c;
+          }
+        }
+        return SX
+    }
+
+    // 转拼音
+    chineseToPinYin(l1) {
+        var l2 = l1.length;
+        var I1 = '';
+        var reg = new RegExp('[a-zA-Z0-9]');
+        for (var i = 0; i < l2; i++) {
+            var val = l1.substr(i, 1);
+            var name = this.arraySearch(val, pinyin);
+            if (reg.test(val)) {
+                I1 += val;
+            } else if (name !== false) {
+                I1 += name;
+            }
+        }
+        I1 = I1.replace(/ /g, '-');
+        while (I1.indexOf('--') > 0) {
+            I1 = I1.replace('--', '-');
+        }
+        return I1;
+    }
+
+    arraySearch(l1, l2) {
+        for (var name in pinyin) {
+            if (pinyin[name].indexOf(l1) !== -1) {
+                return this.ucfirst(name);
+            }
+        }
+        return false;
+    }
+
+    ucfirst(l1) {
+        if (l1.length > 0) {
+            var first = l1.substr(0, 1).toUpperCase();
+            var spare = l1.substr(1, l1.length);
+            return first + spare;
+        }
+    }
+   
+
+}
+const toPinyin = ToPinyin.getInstance()
+export default toPinyin;

+ 40 - 0
sleep/plugins/pinyin/utils.js

@@ -0,0 +1,40 @@
+import toPinyin from "./toPinyin";
+
+/**
+ * 根据拼音首字母筛选排序分组
+ * @param {Array} arr 原数组
+ * @param {String} key 原数组需要筛选的字段
+ * @returns {Array} 返回一个[{name: A,value: []}] 格式的二维数组
+ */
+export function getGroupByPinyin(arr, key = 'name') {
+    if(!arr) return
+    
+    // 获取A-Z字母数组
+    let keys = [...Array(26).keys()].map((i) => String.fromCharCode(i + 65));
+    
+    arr = arr.map((n) => ({
+        ...n,
+        py: toPinyin.chineseToInitials(
+            toPinyin.chineseToPinYin(n[key].substr(0, 1))
+        ),
+    }));
+
+    let group = [];
+    for (const i of keys) {
+        // 新数组一级结构,可自行修改
+        let item = {
+            name: i,
+            value: [],
+        };
+        for (const j of arr) {
+            if (j.py === i) {
+                item.value.push(j);
+            }
+        }
+        if (item.value.length > 0) {
+            item.value.sort((a, b) => a[key].localeCompare(b[key]));
+            group.push(item);
+        }
+    }
+    return group;
+}

BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173118.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173402.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173412.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173428.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173448.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173510.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173526.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117173539.png


BIN
sleep/preview/╬ó╨┼═╝╞¼_20220117174715.png


BIN
sleep/static/.DS_Store


BIN
sleep/static/NO1.png


BIN
sleep/static/NO2.png


BIN
sleep/static/NO3.png


BIN
sleep/static/background.png


BIN
sleep/static/home/homeBg2.png


BIN
sleep/static/home/homeBgHeart.png


BIN
sleep/static/home/homeHead.png


BIN
sleep/static/home/homeIconAdd.png


BIN
sleep/static/home/homeIconAlert.png


BIN
sleep/static/home/homeIconBreath.png


BIN
sleep/static/home/homeIconChange.png


BIN
sleep/static/home/homeIconGO.png


BIN
sleep/static/home/homeIconGift.png


BIN
sleep/static/home/homeIconHeart.png


BIN
sleep/static/home/homeIconPower.png


BIN
sleep/static/home/homeIconSet.png


BIN
sleep/static/home/homeIconWifi.png


BIN
sleep/static/home/homeReportBg.png


BIN
sleep/static/loginBg.png


BIN
sleep/static/logo.png


BIN
sleep/static/mine/myAnnouncement.png


BIN
sleep/static/mine/myAvatar.png


BIN
sleep/static/mine/myHelp.png


BIN
sleep/static/mine/myIconAdd.png


BIN
sleep/static/mine/myIconAlert.png


BIN
sleep/static/mine/myIconBuy.png


BIN
sleep/static/mine/myIconDefault.png


BIN
sleep/static/mine/myIconEdit.png


BIN
sleep/static/mine/myIconLeft.png


BIN
sleep/static/mine/myIconLink.png


BIN
sleep/static/mine/myIconMoney.png


BIN
sleep/static/mine/myIconNotDefault.png


BIN
sleep/static/mine/myIconPhone.png


BIN
sleep/static/mine/myIconPower.png


BIN
sleep/static/mine/myIconPromt.png


BIN
sleep/static/mine/myIconSetting.png


BIN
sleep/static/mine/myOrder.png


BIN
sleep/static/mine/myProduct.png


BIN
sleep/static/mine/mySecret.png


+ 0 - 0
sleep/static/mine/myState.png


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä