1,364
社区成员




· pages文件夹存放页面
· static内的文件不会进行编译,不要放js文件,可放到common中(注意体积限制)
· unpackage文件夹存放打包的文件
· components文件夹存放各种组件
· App.vue代表应用,包括应用层的生命周期方法,全局样式等
· pages.json整个应用的页面集合,第一项为启动页,可配置页面路由及样式和标题
· manifest.json应用配置,包括图标配置、启动界面配置、权限配置及其他开发配置
· main.js应用入口文件
<template>
<view :class="[displayInfo,'skeleton']" :animation="animationData" v-if="show">
<div
class="skeleton-avatar"
v-if="avatar"
:style="{
width:imgsize,
height:imgsize,
borderRadius:imgarc,
}"
/>
<view class="skeleton-content">
<view v-if="title" class="skeleton-content-title" :style="titleInfo" />
<view v-for="(item,key) in rowDataInfo" :key="key" class="skeleton-content-row" :style="{width:rowInfo(key)}" />
</view>
</view>
</template>
<script>
var animation = uni.createAnimation({
duration: 1000,
timingFunction: "linear"
});
export default {
data() {
return {
animationData: {},
imgType: ["circular", "square"], //圆形----方形
displayType: ["vertical", "horizontal"], //垂直----水平
show: true //实际意义上的隐藏
};
},
props: {
// 是否显示--(明面上的意思)
loading: {
type: Boolean,
default: true
},
// 是否显示标题
title: {
type: Boolean,
default: true
},
// 是否显示头像
avatar: {
type: Boolean,
default: true
},
// 头像大小
avatarSize: {
type: Number | String,
default: 100
},
// 头像形状-圆形-方形
isarc: {
type: String,
default: "square" //圆形
},
// title宽度
titleSize: {
type: Number | String,
default: '50%'
},
// 标题sytle
titleStyle: {
type: Object,
default: {}
},
// 几行
row: {
type: Number,
default: 1
},
// 几行信息
rowData: {
type: Array | String,
default: "80%"
},
// 显示类型-垂直-水平
display: {
type: String,
default: "horizontal" //默认水平
}
},
created() {
this.animationData = animation;
},
watch: {
loading(loading) {
if (!loading) {
animation.opacity(0).step();
this.animationData = animation.export();
const time = setTimeout(() => {
this.show = false;
clearTimeout(time);
}, 1000);
}
}
},
computed: {
// 循环体
rowDataInfo() {
let rowArr = [];
for (let index = 0; index < this.row; index++) {
rowArr.push(index);
}
return rowArr;
},
// tile修改字符串
titleInfo() {
let titlData = "";
for (const key in this.titleStyle) {
titlData += `${key}:${this.titleStyle[key]};`;
}
return titlData;
},
// 头像大小
imgsize() {
switch (typeof this.avatarSize) {
case "number":
return `${uni.upx2px(this.avatarSize)}px`;
break;
default:
return `${uni.upx2px(parseFloat(this.avatarSize))}px`;
break;
}
},
// title宽度
titlwidth() {
switch (typeof this.titleSize) {
case "number":
return `${uni.upx2px(this.titleSize)}px`;
break;
default:
return `${uni.upx2px(parseFloat(this.titleSize))}px`;
}
},
// 圆形
imgarc() {
if (this.imgType.includes(this.isarc)) {
if (this.isarc == "square") {
return "0%";
}
return "50%";
}
console.error(`输入错误${this.isarc}`);
return "0%";
},
// 显示类型
displayInfo() {
if (this.displayType.includes(this.display)) {
return this.display;
}
console.error(`输入错误${this.display}`);
return "horizontal";
}
},
methods: {
rowInfo(key) {
const rowTypeof = typeof this.rowData;
switch (rowTypeof) {
case 'string':
return this.rowData
break;
case 'object':
// 如果没有就默认80%
if(!this.rowData[0]){
return '80%';
}
if(!this.rowData[key]){
key = 0;
}
return (this.rowData[key].indexOf('%')>-1)?this.rowData[key]:`${uni.upx2px(parseFloat(this.rowData[key]))}px`;
break;
}
}
}
};
</script>
index首页
List列表页
Player播放页
search搜索页
<template>
<view class="content">
<uamhead :title="title"></uamhead>
<!-- <image class="logo" src="/static/tt.png"></image>
<view class="text-area">
<text class="title">{{title}}</text>
</view> -->
<scroll-view scroll-y="true" >
<view>
<m-for-skeleton
:avatarSize="200"
:row="3"
:title="false"
:loading="loading"
isarc="square"
:titleStyle="{}"
v-for="(item,key) in 4"
:key="key">
</m-for-skeleton>
</view>
<view class="index-list" v-for="(item,index) in playlist" :key="index" @click="handleToList(item.id)">
<view class="index-list-item">
<view class="index-list-img">
<image :src="item.coverImgUrl" mode=""></image>
<text>{{item.updateFrequency}}</text>
</view>
<view class="index-list-text">
<view v-for="(musicItem,index) in item.tracks" :key="index">
{{index+1}}.{{musicItem.first}}-{{musicItem.second}}
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { topList } from '../../common/api.js'
// const toplistdata=require('@/static/toplist.json')
import mForSkeleton from "@/components/m-for-skeleton/m-for-skeleton";
import uamhead from "../../components/uamhead/uamhead.vue"
export default {
components: {
mForSkeleton
},
data() {
return {
playlist:[],
title: 'uni-app-aa',
loading: true
}
},
onLoad() {
// this.playlist= toplistdata;
topList().then((res)=>{
if(res.length){
setTimeout(()=>{
this.playlist = res;
this.loading=false
},2000);
console.log(res)
}
});
},
methods: {
handleToList(id){
uni.navigateTo({
url:'/pages/list/list?listid='+id
})
},
navPlayer(id) {
uni.navigateTo({
url: '/pages/search/search?listid=' + id
})
// hide(){
// this.loading=false;
// }
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
.index-list{
margin:0 30rpx;
}
.index-list-item{
display: flex;
margin-bottom: 35rpx;
}
.index-list-img{
width:212rpx;
height:212rpx;
margin-right:20rpx;
border-radius: 15rpx;
overflow: hidden;
position: relative;
}
.index-list-img image{
width:100%;
height:100%;
}
.index-list-img text{
position: absolute;
font-size:22rpx;
color:white;
bottom: 15rpx;
left:15rpx;
}
.index-list-text{
flex:1;
font-size:24rpx;
line-height: 68rpx;
}
</style>
<template>
<view>
<uamhead :title="title"></uamhead>
<view class="list-head">
<view class="list-head-img">
<image :src="playlist.coverImgUrl" mode=""></image>
<text class="iconfont iconyousanjiao">{{ playlist.playCount }}</text>
</view>
<view class="list-head-text">
<view>{{ playlist.name }}</view>
<view>{{ playlist.description }}</view>
</view>
</view>
<view>
<view v-show="isShow" class="list-music-title">
<text class="iconfont iconbofang1"></text>
<text>播放全部</text>
<text>(共{{ playlist.trackCount }}首)</text>
</view>
<view class="list-music">
<view class="list-music-item" v-for="(item,index) in playlist.tracks" :key="item.id" @tap="navPlayer(item.id)">
<view class="list-music-top">{{ index + 1 }}</view>
<view class="list-music-song">
<view>{{ item.name }}</view>
<view>
<image v-if=" privileges[index].flag > 60 && privileges[index].flag < 70" src="../../static/dujia.png" mode=""></image>
<image v-if="privileges[index].maxbr == 999000" src="../../static/sq.png" mode=""></image>
{{ item.ar[0].name }} - {{ item.name }}
</view>
</view>
<text class="iconfont iconbofang"></text>
</view>
</view>
</view>
</view>
</template>
<script>
import {list} from '../../common/api.js'
export default {
data() {
return {
title:"榜单列表",
playlist : {
coverImgUrl : '',
trackCount : '',
creator : ''
},
privileges : [],
isShow : false
}
},
onLoad(options){
let listid=options.listid;
list(listid).then((res)=>{
if(res.data.code=='200'){
this.title=res.data.playlist.name
this.playlist=res.data.playlist
this.privileges = res.data.privileges
this.isShow=true
}
}
)},
methods: {
navPlayer(id){
console.log(id)
uni.navigateTo({
url:'/pages/player/player?playerid='+id
})
},
}
}
</script>
<style>
.list-head{
display: flex;
margin:30rpx;
}
.list-head-img{
width:260rpx;
height:260rpx;
border-radius: 15rpx;
margin-right:40rpx;
overflow: hidden;
position: relative;
}
.list-head-img image{
width:100%;
height:100%;
}
.list-head-img text{
position: absolute;
font-size:26rpx;
color:white;
right:8rpx;
top:8rpx;
}
.list-head-text{
flex:1;
font-size:20rpx;
color:#c3d1e3;
}
.list-head-text image{
width:52rpx;
height:52rpx;
border-radius: 50%;
}
.list-music{
background:white;
border-radius: 50rpx;
overflow: hidden;
margin-top:45rpx;
}
.list-music-item{
display: flex;
margin:0 30rpx 70rpx 44rpx;
align-items: center;
}
.list-music-top{
width:56rpx;
font-size:24rpx;
color:#979797;
}
.list-music-song{
flex:1;
line-height: 40rpx;
}
.list-music-title{
height:58rpx;
line-height: 58rpx;
margin:30rpx 30rpx 70rpx 30rpx;
}
.list-music-song image{
width:34rpx;
height:22rpx;
margin-right:10rpx;
}
</style>
<template>
<view class="player-head">
<uamhead :title="title"></uamhead>
<view class="song">{{song.artist}}:{{song.name}}</view>
<view class="player">
<image :src="song.picUrl" :class="{ 'run' : isplayrotate }" mode=""></image>
<text class="iconfont iconpause" @tap="noPlaying" v-if="isplayrotate"></text>
<text class="iconfont iconbofang" @tap="playing" v-else></text>
<view></view>
</view>
<scroll-view class="lyric" scroll-y="true">
<view class="wrap" :style="{ transform : 'translateY(' + - (lyricIndex - 1) * 84 +'rpx)'}">
<view class="index-item" :class="{active: lyricIndex == index}" v-for="(item,index) in song.lyric"
:key="index">
{{item.lyric}}
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import {
songDetail,
songLyric,
songUrl,
} from '../../common/api.js'
//创建innerAudioContext实例
const innerAudioContext = uni.createInnerAudioContext();
export default {
data() {
return {
title: '黑胶唱片',
song: {
id: '',
name: '',
artist: '',
picUrl: '',
songUrl: '',
lyric: '',
},
isplayrotate: false,
lyricIndex: 0,
}
},
onLoad(options) {
let playerid = options.playerid
//歌曲id
songDetail(playerid).then(res => {
let s = res.data.songs[0]
this.song.name = s.name
this.song.id = s.id
this.song.artist = s.ar[0].name
this.song.picUrl = s.al.picUrl
//播放音乐url
songUrl(this.song.id).then(res => {
this.song.songUrl = res.data.data[0].url
console.log(this.song.songUrl)
})
// 歌词
songLyric(playerid).then(res => {
// this.song.lyric=res.data.lrc.lyric;
let lyric = res.data.lrc.lyric;
let result = [];
let re = /\[([^\]]+)\]([^[]+)/g;
lyric.replace(re, ($0, $1, $2) => {
result.push({
time: this.formatTimeToSec($1),lyric: $2});
});
this.song.lyric = result;
})
console.log(res)
})
},
methods: {
playing() {
innerAudioContext.autoplay = true
innerAudioContext.src = this.song.songUrl;
innerAudioContext.onPlay(() => {
// innerAudioContext.play()
this.listenLyricIndex()
this.isplayrotate = true,
console.log("开始播放")
})
this.innerAudioContext = innerAudioContext
},
noPlaying() {
innerAudioContext.pause()
this.isplayrotate = false;
console.log("停止播放")
innerAudioContext.onPause(() => { //暂停时调用的方法
innerAudioContext.startTime = innerAudioContext.currentTime
//startTime 设置开始时间 currentTime 暂停时的秒数 官方文档都有写
})
},
formatTimeToSec(time) {
var arr = time.split(':');
return (parseFloat(arr[0]) * 60 + parseFloat(arr[1])).toFixed(2);
},
listenLyricIndex() {
clearInterval(this.timer);
this.timer = setInterval(() => {
for (var i = 0; i < this.song.lyric.length; i++) {
if (this.song.lyric[this.song.lyric.length - 1].time < this.innerAudioContext
.currentTime) {
this.lyricIndex = this.song.lyric.length - 1;
break;
}
if (this.song.lyric[i].time < this.innerAudioContext.currentTime && this.song.lyric[i + 1]
.time > this.innerAudioContext.currentTime) {
this.lyricIndex = i;
}
}
})
}
}
}
</script>
<style>
.player-head{
margin: 0 auto;
text-align: center;
}
.song{
margin: 20px;
}
.player{
width:580rpx;
height:580rpx;
background:url(~@/static/disc.png);
background-size:cover;
margin:210rpx auto 44rpx auto;
position: relative;
z-index: 2;
}
.player image{
width:380rpx;
height:380rpx;
border-radius: 50%;
position: absolute;
left:0;
top:0;
right:0;
bottom:0;
margin:auto;
z-index: 3;
}
.player text{
width:100rpx;
height:100rpx;
font-size:100rpx;
position: absolute;
left:0;
top:0;
right:0;
bottom:0;
margin:auto;
color:white;
z-index: 4;
}
.player view{
position: absolute;
width:170rpx;
height:266rpx;
position: absolute;
left:60rpx;
right:0;
margin:auto;
top:-170rpx;
background:url(~@/static/needle.png);
background-size:cover;
}
.player image{
width:380rpx;
height:380rpx;
border-radius: 50%;
position: absolute;
left:0;
top:0;
right:0;
bottom:0;
margin:auto;
z-index: 3;
animation:10s linear infinite move;
animation-play-state: paused;
}
@keyframes move{
from{
transform : rotate(0deg);
}
to{
transform : rotate(360deg);
}
}
.player .run{
animation-play-state: running;
}
.lyric{
height: 250px;
line-height: 90px;
font-size: 32rpx;
color: #949495;
text-align: center;
overflow: hidden;
}
.active{
color: #ffaa7f;
}
.lyric .wrap{
transition: .5s;
}
.lyric .item{
height: 82rpx;
}
</style>
<template>
<view class="search">
<uamhead :title="title"></uamhead>
<view class="container">
<scroll-view scroll-y="true">
<view class="search-search">
<text class="iconfont iconsearch"></text>
<input type="text" placeholder="搜索歌曲" v-model="searchWord" @confirm="handleToSearch" @input="handleToSuggest" />
<text v-show="searchType == 2" @tap="handleToClose" class="iconfont iconguanbi"></text>
</view>
<block v-if="searchType == 1">
<view class="search-history">
<view class="search-history-head">
<text>历史记录</text>
<text class="iconfont iconlajitong" @tap="handleToClear"></text>
</view>
<view class="search-history-list">
<view v-for="(item,index) in historyList" :key="index" @tap="handleToWord(item)">{{ item }}</view>
</view>
</view>
<view class="search-hot">
<view class="search-hot-title">热搜榜</view>
<view class="search-hot-item" v-for="(item,index) in searchHot" :key="index"
@tap="handleToWord(item.searchWord)">
<view class="search-hot-top">{{ index + 1 }}</view>
<view class="search-hot-word">
<view>
{{ item.searchWord }}
<image :src="item.iconType ? item.iconUrl : ''" mode="aspectFit"></image>
</view>
<view>{{ item.content }}</view>
</view>
</view>
</view>
</block>
<block v-else-if="searchType == 2">
<view class="search-result">
<view class="search-result-item" v-for="(item,index) in searchList" :key="index"
@tap="handleToDetail(item.id)">
<view class="search-result-word">
<view>{{ item.name }}</view>
<view>{{ item.artists.name }} - {{ item.album.name }}</view>
</view>
<text class="iconfont iconbofang"></text>
</view>
</view>
</block>
<block v-else-if="searchType == 3">
<view class="search-suggest">
<view class="search-suggest-title">搜索"{{ this.searchWord }}"</view>
<view class="search-suggest-item" v-for="(item,index) in suggestList" :key="index"
@tap="handleToWord(item.keyword)">
<text class="iconfont iconsearch"></text>
{{ item.keyword }}
</view>
</view>
</block>
</scroll-view>
</view>
</view>
</template>
<script>
// 导入组件
import mForSkeleton from "@/components/m-for-skeleton/m-for-skeleton";
import {
searchHot,
searchWord,
searchSuggest
} from '../../common/api.js'
import '../../common/iconfont.css'
export default {
data() {
return {
title:'搜索页',
searchHot: [],
searchWord: '',
historyList: [],
searchType: 1,
searchList: [],
suggestList: []
}
},
onLoad(options) {
let seId = options.songId
searchHot(seId).then((res) => {
if (res.data.code == '200') {
this.searchHot = res.data.data;
}
});
uni.getStorage({
key: 'searchHistory',
success: (res) => {
this.historyList = res.data;
}
});
},
methods: {
handleToSearch() {
this.historyList.unshift(this.searchWord);
this.historyList = [...new Set(this.historyList)];
if (this.historyList.length > 10) {
this.historyList.length = 10;
}
uni.setStorage({
key: 'searchHistory',
data: this.historyList
});
this.getSearchList(this.searchWord);
},
handleToClear() {
uni.removeStorage({
key: 'searchHistory',
success: () => {
this.historyList = [];
}
});
},
getSearchList(word) {
searchWord(word).then((res) => {
if (res.data.code == '200') {
this.searchList = res.data.result.songs;
this.searchType = 2;
}
});
},
handleToClose() {
this.searchWord = '';
this.searchType = 1;
},
handleToSuggest(ev) {
let value = ev.detail.value;
if (!value) {
this.searchType = 1;
return;
}
searchSuggest(value).then((res) => {
if (res.data.code == '200') {
this.suggestList = res.data.result.allMatch;
this.searchType = 3;
}
});
},
handleToWord(word) {
this.searchWord = word;
this.handleToSearch();
},
handleToDetail(songId) {
uni.navigateTo({
url: '/pages/player/player?songId=' + songId
});
}
}
}
</script>
<style scoped>
.search-search {
display: flex;
background: #f7f7f7;
height: 73rpx;
margin: 28rpx 30rpx 30rpx 30rpx;
border-radius: 50rpx;
align-items: center;
}
.search-search text {
margin: 0 27rpx;
}
.search-search input {
font-size: 26rpx;
flex: 1;
}
.search-history {
margin: 0 30rpx;
font-size: 26rpx;
}
.search-history-head {
display: flex;
justify-content: space-between;
}
.search-history-list {
display: flex;
margin-top: 36rpx;
flex-wrap: wrap;
}
.search-history-list view {
padding: 20rpx 40rpx;
background: #f7f7f7;
border-radius: 50rpx;
margin-right: 30rpx;
margin-bottom: 20rpx;
}
.search-hot {
margin: 30rpx 30rpx;
font-size: 26rpx;
color: #bebebe;
}
.search-hot-item {
display: flex;
align-items: center;
margin-top: 40rpx;
}
.search-hot-top {
width: 60rpx;
color: #fb222;
font-size: 34rpx;
}
.search-hot-word {
flex: 1;
}
.search-hot-word view:nth-child(1) {
font-size: rpx;
color: black;
}
.search-hot-word image {
width: 48rpx;
height: 22rpx;
}
.search-result {
border-top: 2rpx #e5e5e5 solid;
padding: 30rpx;
}
.search-result-item {
display: flex;
align-items: center;
border-bottom: 2rpx #e5e5e5 solid;
padding-bottom: 30rpx;
margin-bottom: 30rpx;
}
.search-result-item text {
font-size: 50rpx;
}
.search-result-word {
flex: 1;
}
.search-result-word view:nth-child(1) {
font-size: 28rpx;
color: #3e6694;
}
.search-result-word view:nth-child(2) {
font-size: 26rpx;
}
.search-suggest {
border-top: 2rpx #e5e5e5 solid;
padding: 30rpx;
font-size: 26rpx;
}
.search-suggest-title {
color: #537caa;
margin-bottom: 40rpx;
}
.search-suggest-item {
color: #666666;
margin-bottom: 70rpx;
}
.search-suggest-item text {
color: #c2c2c2;
font-size: 26rpx;
margin-right: 26rpx;
}
</style>
<view class="index-search" @tap="navPlayer">
<text class="iconfont iconsearch"></text>
<input type="text" placeholder="搜索歌曲">
</view>