效果图
在歌曲宝里面下载音乐文件和歌词
增加了上一曲,播放,下一曲的功能,之前只能通过搜索来进行换曲,还增加了背景图片,可以在添加歌曲时上传我们喜欢的图片,这样在不同的音乐界面背景图也不一样,末尾我又修改了一下可以自动播放,添加了播放列表,音量调节
第一步
在自己的vue项目里面创建一个页面,并在路由里面配置路由(要发送请求和用到element-ui)所以vue项目要安装router,axios和element-ui(vue2)或者element-plus(vue3)
npm install axios
npm install router
//vue为2版本
npm install element-ui
//vue为3版本
npm install element-plus
第二步
MusicView.vue页面
<script>
import {ref, onMounted, watch, reactive, onUnmounted} from 'vue';
import axios from "axios";
const name = ref('')
export default {
setup() {
const lrcData = ref([]);
const audio = ref(null);
const ul = ref(null);
const container = ref(null);
const musicName = ref('')
const dialogFormVisible = ref(false)
const isPlay = ref(false)
const selectedFile = ref(null);
const originalTitle = ref(''); // 原始标题
const title = ref(originalTitle);
const musicList = ref([])
const imgUrl = ref("");
let conm = 0;
const form = reactive({
musicName: '',
text: '',
file: '',
imgUrl: ''
})
// form 三个属性分别为 歌曲名称,歌词,mp3文件
//添加对话框状态
const isDialog = () => {
dialogFormVisible.value = true
}
//上一曲
const previousSong = () => {
conm = (conm - 1 + musicList.value.length) % musicList.value.length;
name.value = musicList.value[conm]
fetchLyrics();
name.value="";
isPlay.value=false
conm--;
}
//下一曲
const nextSong = () => {
conm = (conm + 1) % musicList.value.length;
name.value = musicList.value[conm]
fetchLyrics();
name.value="";
isPlay.value=false
conm++;
}
//播放与暂停
const Play = () => {
if (isPlay.value){
audio.value.pause();
}else {
audio.value.play();
}
isPlay.value=!isPlay.value
console.log(isPlay.value)
}
// 更新播放状态
const updatePlayStatus = (event) => {
isPlay.value = event.type === 'play'; // 根据事件类型更新 isPlay
};
const musicLists =async () => {
await axios.get("http://localhost:8080/user/musicList").then(res=>{
musicList.value = res.data.data
}).catch(res=>{
console.log(res.error)
})
}
const musicAdd =async () => {
console.log(form)
if (form.musicName!==null && form.text!==null){
await axios.post("http://localhost:8080/user/musicAdd",{musicName: form.musicName,text: form.text,file: form.file,imgUrl: form.imgUrl}).then(res=>{
if (res.data.code===0){
form.file = '';
form.musicName = '';
form.text = '';
form.imgUrl = ""
dialogFormVisible.value = false;
}
}).catch(res=>{
console.log(res.error)
})
}
}
const uploadFile = () => {
}
const handleFileChange = async (event) => {
// 获取用户选择的文件
const file = event.raw;
if (file && file.type === 'audio/mpeg') {
selectedFile.value = file;
}
if (selectedFile.value===null){
return;
}
let formData = new FormData;
formData.append('file', selectedFile.value)
await axios.post('http://localhost:8080/user/musicFileUpload', formData, {
headers: {'Content-Type': 'multipart/form-data'}
}).then(res => {
if (res.data.code === 0) {
form.file = res.data.data;
}
}).catch(res => {
console.log(res.error)
});
};
const handleChange = async (event) => {
// 获取用户选择的文件
const file = event.raw;
if (selectedFile.value===null){
return;
}
let formData = new FormData;
formData.append('file', file)
await axios.post('http://localhost:8080/user/musicFileUpload', formData, {
headers: {'Content-Type': 'multipart/form-data'}
}).then(res => {
if (res.data.code === 0) {
form.imgUrl = res.data.data;
}
}).catch(res => {
console.log(res.error)
});
};
const findIndex = () => {
const curTime = audio.value.currentTime;
for (let i = 0; i < lrcData.value.length; i++) {
if (curTime < lrcData.value[i].time) {
return i > 0 ? i - 1 : -1; // 确保索引不会小于0
}
}
return lrcData.value.length - 1; // 播放到最后一句
};
// 创建歌词 DOM 元素
const createLrcElements = () => {
// 清空原有歌词元素
ul.value.innerHTML = '';
lrcData.value.forEach(item => {
const li = document.createElement('li');
li.textContent = item.text;
ul.value.appendChild(li);
});
};
// 设置歌词滚动效果
const setOffset = () => {
const index = findIndex();
const containerHeight = container.value.clientHeight;
const liHeight = ul.value.children[0]?.clientHeight || 0; // 使用可选链防止报错
const maxOffset = Math.max(ul.value.clientHeight - containerHeight, 0);
let offset = liHeight * index + liHeight / 2 - containerHeight / 2;
offset = Math.max(0, Math.min(offset, maxOffset)); // 确保 offset 在合法范围内
ul.value.style.transform = `translateY(-${offset}px)`;
// 设置高亮当前歌词
const activeLi = ul.value.querySelector('.active');
if (activeLi) {
activeLi.classList.remove('active');
}
const li = ul.value.children[index];
if (li) {
li.classList.add('active');
}
};
let con = 1; // 控制移动字符的索引
let intervalId; // 用于存储setInterval的ID
const titleButton = () => {
// 清除之前的定时器
clearInterval(intervalId);
// 每2秒更新一次标题
intervalId = setInterval(() => {
// 逐个字符移动
title.value = originalTitle.value.substring(con) + originalTitle.value.substring(0, con);
con = (con + 1) % originalTitle.value.length;
}, 1200);
};
// 创建一个获取歌词的异步函数
const fetchLyrics = async () => {
try {
console.log(name.value)
// 修改为你的 API 地址
const data = await axios.post('http://localhost:8080/user/song', {name: name.value});
const lyri = data.data.data
musicName.value = lyri.songUrl
originalTitle.value = lyri.title+" - "+lyri.artist+" ";
console.log(title.value)
if (lyri.imgUrl){
imgUrl.value = lyri.imgUrl
}
// 假设你的歌词是一个包含时间戳和歌词的数组
// 示例:[{ time: 0, text: '第一行歌词' }, { time: 5, text: '第二行歌词' }]
lrcData.value = lyri.list.map((lyric) => ({
time: lyric.time,
text: lyric.text,
}));
createLrcElements(); // 更新 DOM 元素
setOffset();
} catch (error) {
console.error("获取歌词失败:", error); // 处理错误情况
}
};
onMounted(() => {
fetchLyrics();
titleButton();
musicLists();
audio.value.addEventListener('timeupdate', setOffset);
audio.value.addEventListener('play', updatePlayStatus);
audio.value.addEventListener('pause', updatePlayStatus);
});
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId);
}
audio.value.removeEventListener('play', updatePlayStatus);
audio.value.removeEventListener('pause', updatePlayStatus);
});
watch(musicName, () => {
if (audio.value) {
audio.value.load(); // 确保加载新音频源
}
});
return {
audio,
ul,
container,
name,
dialogFormVisible,
musicName,
fetchLyrics,
musicLists,
isDialog,
titleButton,
form,
title,
imgUrl,
isPlay,
Play,
previousSong,
nextSong,
musicAdd,
handleFileChange,
uploadFile,
handleChange
};
}
}
</script>
<template>
<div class="body" :style="{ background: `url(${imgUrl})`,backgroundSize: 'cover'}">
<br>
<div class="button">
<el-input class="el-input" @keyup.enter="fetchLyrics" placeholder="输入歌曲名称" v-model="name" size="small"
style="width: 200px"/>
<el-button type="primary" style="margin-left: 10px" size="mini" icon="el-icon-search" @click="fetchLyrics" round>
搜索
</el-button>
<el-button type="success" style="margin-left: 10px" icon="el-icon-plus" size="mini" @click="isDialog" round>添加
</el-button>
</div>
<el-dialog title="添加歌曲" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="歌曲名称" label-width="120px">
<el-input v-model="form.musicName" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="歌词" label-width="120px">
<el-input type="textarea" rows="10" show-word-limit maxlength="5000" v-model="form.text"
autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="mp3文件" label-width="120px">
<el-upload
class="upload-demo custom-upload"
drag
:http-request="uploadFile"
limit="1"
accept=".mp3"
multiple
:on-change="handleFileChange">
<div class="el-upload__text">
选择文件或 <em>拖拽上传</em>
</div>
</el-upload>
</el-form-item>
<el-form-item label="歌曲背景" label-width="120px">
<el-upload
class="upload-demo custom-upload"
drag
:http-request="uploadFile"
limit="1"
accept=".jpg,.png"
multiple
:on-change="handleChange">
<div class="el-upload__text">
选择文件或 <em>拖拽上传</em>
</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="musicAdd">确 定</el-button>
</div>
</el-dialog>
<div class="text-title">{{ title }}</div>
<div class="container" ref="container">
<ul class="lrc-list" ref="ul"></ul>
</div>
<div class="button">
<audio ref="audio" :src="musicName" type="audio/mpeg" controls/>
<button @click="previousSong" class="buttons"><i class="el-icon-arrow-left"></i>上一曲</button>
<button v-if="isPlay" @click="Play" class="buttonss"><i class="el-icon-video-pause"></i></button>
<button v-else @click="Play" class="buttonss"><i class="el-icon-video-play"></i></button>
<button @click="nextSong" class="buttons">下一曲<i class="el-icon-arrow-right"></i></button>
</div>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
}
.button {
display: flex;
justify-content: center;
}
.buttons{
width: 60px;
height: 25px;
background-color: white;
border-radius: 12px;
border: none;
font-size: 11px;
align-items: center;
color: darkslategrey;
margin-top: 42px;
margin-left: 10px;
}
.buttonss{
width: 25px;
height: 25px;
background-color: white;
border-radius: 50%;
border: none;
font-size: 20px;
align-items: center;
color: darkslategrey;
margin-top: 42px;
margin-left: 10px;
}
.body {
color: #666;
height: 100vh;
text-align: center;
}
.text-title{
color: white;
font-size: 20px;
padding-top: 20px;
}
audio {
width: 450px;
margin: 30px 0;
}
.container {
margin-top: 2%;
height: 420px;
overflow: hidden;
}
.container ul {
transition: 0.6s;
list-style: none;
}
.container li {
height: 30px;
line-height: 30px;
transition: 0.2s;
}
.container li.active {
color: sandybrown;
transform: scale(1.2);
}
</style>
第三步
后端代码
记得将后端代码中resources文件夹里面的sql文件在Navicat上运行
管华尔/vue音乐播放器页面后端 - 码云 - 开源中国 (gitee.com)
第四步
数据库表样式
最后的版本
<script>
import {ref, onMounted, watch, reactive, onUnmounted} from 'vue';
import axios from "axios";
const name = ref('')
export default {
setup() {
const lrcData = ref([]);
const audio = ref(null);
const ul = ref(null);
const container = ref(null);
const musicName = ref('')
const dialogFormVisible = ref(false)
const isVolume = ref(false)
const isPlay = ref(false)
const selectedFile = ref(null);
const originalTitle = ref(''); // 原始标题
const title = ref(originalTitle);
const musicList = ref([])
const volumeSize = ref(50)
const imgUrl = ref("");
let conm = 0;
const form = reactive({
musicName: '',
text: '',
file: '',
imgUrl: ''
})
// form 三个属性分别为 歌曲名称,歌词,mp3文件
//添加对话框状态
const isDialog = () => {
dialogFormVisible.value = true
}
const tablePaly = (data) => {
name.value = data.name
fetchLyrics();
name.value="";
isPlay.value = false
setTimeout(()=>{
Play();
},100)
}
//音量调节
const musicVolume = (val) => {
audio.value.volume = val/100;
return val+"%";
}
//上一曲
const previousSong = () => {
conm = (conm - 1 + musicList.value.length) % musicList.value.length;
name.value = musicList.value[conm].name
fetchLyrics();
name.value = "";
isPlay.value = false
setTimeout(()=>{
Play();
},100)
conm--;
}
//下一曲
const nextSong = () => {
console.log("dsfdfsd")
conm = (conm + 1) % musicList.value.length;
name.value = musicList.value[conm].name
fetchLyrics();
name.value = "";
isPlay.value = false
setTimeout(()=>{
Play();
},100)
conm++;
}
//播放与暂停
const Play = () => {
if (isPlay.value) {
audio.value.pause();
} else {
audio.value.play();
}
isPlay.value = !isPlay.value
console.log(isPlay.value)
}
// 更新播放状态
const updatePlayStatus = (event) => {
isPlay.value = event.type === 'play'; // 根据事件类型更新 isPlay
};
const musicLists = async () => {
await axios.get("http://localhost:8080/user/musicList").then(res => {
musicList.value = res.data.data
}).catch(res => {
console.log(res.error)
})
}
const musicAdd = async () => {
console.log(form)
if (form.musicName !== null && form.text !== null) {
await axios.post("http://localhost:8080/user/musicAdd", {
musicName: form.musicName,
text: form.text,
file: form.file,
imgUrl: form.imgUrl
}).then(res => {
if (res.data.code === 0) {
form.file = '';
form.musicName = '';
form.text = '';
form.imgUrl = ""
dialogFormVisible.value = false;
musicLists();
}
}).catch(res => {
console.log(res.error)
})
}
}
const uploadFile = () => {
}
const handleFileChange = async (event) => {
// 获取用户选择的文件
const file = event.raw;
if (file && file.type === 'audio/mpeg') {
selectedFile.value = file;
}
if (selectedFile.value === null) {
return;
}
let formData = new FormData;
formData.append('file', selectedFile.value)
await axios.post('http://localhost:8080/user/musicFileUpload', formData, {
headers: {'Content-Type': 'multipart/form-data'}
}).then(res => {
if (res.data.code === 0) {
form.file = res.data.data;
}
}).catch(res => {
console.log(res.error)
});
};
const handleChange = async (event) => {
// 获取用户选择的文件
const file = event.raw;
if (selectedFile.value === null) {
return;
}
let formData = new FormData;
formData.append('file', file)
await axios.post('http://localhost:8080/user/musicFileUpload', formData, {
headers: {'Content-Type': 'multipart/form-data'}
}).then(res => {
if (res.data.code === 0) {
form.imgUrl = res.data.data;
}
}).catch(res => {
console.log(res.error)
});
};
const findIndex = () => {
const curTime = audio.value.currentTime;
for (let i = 0; i < lrcData.value.length; i++) {
if (curTime < lrcData.value[i].time) {
return i > 0 ? i - 1 : -1; // 确保索引不会小于0
}
}
return lrcData.value.length - 1; // 播放到最后一句
};
// 创建歌词 DOM 元素
const createLrcElements = () => {
// 清空原有歌词元素
ul.value.innerHTML = '';
lrcData.value.forEach(item => {
const li = document.createElement('li');
li.textContent = item.text;
ul.value.appendChild(li);
});
};
// 设置歌词滚动效果
const setOffset = () => {
const index = findIndex();
const containerHeight = container.value.clientHeight;
const liHeight = ul.value.children[0]?.clientHeight || 0; // 使用可选链防止报错
const maxOffset = Math.max(ul.value.clientHeight - containerHeight, 0);
let offset = liHeight * index + liHeight / 2 - containerHeight / 2;
offset = Math.max(0, Math.min(offset, maxOffset)); // 确保 offset 在合法范围内
ul.value.style.transform = `translateY(-${offset}px)`;
// 设置高亮当前歌词
const activeLi = ul.value.querySelector('.active');
if (activeLi) {
activeLi.classList.remove('active');
}
const li = ul.value.children[index];
if (li) {
li.classList.add('active');
}
};
let con = 1; // 控制移动字符的索引
let intervalId; // 用于存储setInterval的ID
const titleButton = () => {
// 清除之前的定时器
clearInterval(intervalId);
// 每2秒更新一次标题
intervalId = setInterval(() => {
// 逐个字符移动
title.value = originalTitle.value.substring(con) + originalTitle.value.substring(0, con);
con = (con + 1) % originalTitle.value.length;
}, 1200);
};
// 创建一个获取歌词的异步函数
const fetchLyrics = async () => {
try {
console.log(name.value)
// 修改为你的 API 地址
const data = await axios.post('http://localhost:8080/user/song', {name: name.value});
const lyri = data.data.data
musicName.value = lyri.songUrl
originalTitle.value = lyri.title + " - " + lyri.artist + " ";
if (lyri.imgUrl) {
imgUrl.value = lyri.imgUrl
}
// 假设你的歌词是一个包含时间戳和歌词的数组
// 示例:[{ time: 0, text: '第一行歌词' }, { time: 5, text: '第二行歌词' }]
lrcData.value = lyri.list.map((lyric) => ({
time: lyric.time,
text: lyric.text,
}));
createLrcElements(); // 更新 DOM 元素
setOffset();
} catch (error) {
console.error("获取歌词失败:", error); // 处理错误情况
}
};
onMounted(() => {
fetchLyrics();
titleButton();
musicLists();
audio.value.addEventListener('timeupdate', setOffset);
audio.value.addEventListener('play', updatePlayStatus);
audio.value.addEventListener('pause', updatePlayStatus);
audio.value.addEventListener('ended', nextSong);
});
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId);
}
audio.value.removeEventListener('play', updatePlayStatus);
audio.value.removeEventListener('pause', updatePlayStatus);
audio.value.removeEventListener('ended', nextSong); // 移除这个事件监听器
});
watch(musicName, () => {
if (audio.value) {
audio.value.load(); // 确保加载新音频源
}
});
return {
audio,
ul,
container,
name,
dialogFormVisible,
musicName,
fetchLyrics,
musicLists,
isDialog,
titleButton,
form,
title,
imgUrl,
isPlay,
musicList,
Play,
previousSong,
nextSong,
musicAdd,
tablePaly,
volumeSize,
isVolume,
musicVolume,
handleFileChange,
uploadFile,
handleChange
};
}
}
</script>
<template>
<div class="body" :style="{ background: `url(${imgUrl})`,backgroundSize: 'cover'}">
<br>
<div class="button">
<el-input class="el-input" @keyup.enter="fetchLyrics" placeholder="输入歌曲名称" v-model="name" size="small"
style="width: 200px"/>
<el-button type="primary" style="margin-left: 10px" size="mini" icon="el-icon-search" @click="fetchLyrics" round>
搜索
</el-button>
<el-button type="success" style="margin-left: 10px" icon="el-icon-plus" size="mini" @click="isDialog" round>添加
</el-button>
</div>
<el-dialog title="添加歌曲" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="歌曲名称" label-width="120px">
<el-input v-model="form.musicName" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="歌词" label-width="120px">
<el-input type="textarea" rows="10" show-word-limit maxlength="5000" v-model="form.text"
autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="mp3文件" label-width="120px">
<el-upload
class="upload-demo custom-upload"
drag
:http-request="uploadFile"
limit="1"
accept=".mp3"
multiple
:on-change="handleFileChange">
<div class="el-upload__text">
选择文件或 <em>拖拽上传</em>
</div>
</el-upload>
</el-form-item>
<el-form-item label="歌曲背景" label-width="120px">
<el-upload
class="upload-demo custom-upload"
drag
:http-request="uploadFile"
limit="1"
accept=".jpg,.png"
multiple
:on-change="handleChange">
<div class="el-upload__text">
选择文件或 <em>拖拽上传</em>
</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="musicAdd">确 定</el-button>
</div>
</el-dialog>
<div class="text-title">{{ title }}</div>
<div class="container" ref="container">
<ul class="lrc-list" ref="ul"></ul>
</div>
<div class="button">
<audio ref="audio" :src="musicName" type="audio/mpeg" controls/>
<button @click="previousSong" class="buttons"><i class="el-icon-arrow-left"></i>上一曲</button>
<button v-if="isPlay" @click="Play" class="buttonss"><i class="el-icon-video-pause"></i></button>
<button v-else @click="Play" class="buttonss"><i class="el-icon-video-play"></i></button>
<button @click="nextSong" class="buttons">下一曲<i class="el-icon-arrow-right"></i></button>
<el-tooltip effect="light" placement="top">
<div slot="content">
<el-slider
v-if="isVolume"
v-model="volumeSize"
vertical
:format-tooltip="musicVolume"
height="200px"
>
</el-slider>
</div>
<button @click="isVolume=!isVolume" class="buttonss">
<svg t="1727351771803" style="width: 25px;height: 25px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4273" width="200" height="200"><path d="M128 420.576v200.864h149.12l175.456 140.064V284.288l-169.792 136.288H128z m132.256-64l204.288-163.968a32 32 0 0 1 52.032 24.96v610.432a32 32 0 0 1-51.968 24.992l-209.92-167.552H96a32 32 0 0 1-32-32v-264.864a32 32 0 0 1 32-32h164.256zM670.784 720.128a32 32 0 0 1-44.832-45.664 214.08 214.08 0 0 0 64.32-153.312 213.92 213.92 0 0 0-55.776-144.448 32 32 0 1 1 47.36-43.04 277.92 277.92 0 0 1 72.416 187.488 278.08 278.08 0 0 1-83.488 198.976zM822.912 858.88a32 32 0 1 1-45.888-44.608A419.008 419.008 0 0 0 896 521.152c0-108.704-41.376-210.848-114.432-288.384a32 32 0 0 1 46.592-43.872c84.16 89.28 131.84 207.04 131.84 332.256 0 127.84-49.76 247.904-137.088 337.728z" fill="#2c2c2c" p-id="4274"></path></svg>
</button>
</el-tooltip>
<el-popover
placement="top"
width="400"
class="dialog"
trigger="click">
<el-table
:data="musicList"
:show-header="false"
height="400px"
:border="false"
style="width: 100%">
<el-table-column
prop="id"
label="id"
width="60">
</el-table-column>
<el-table-column prop="name" label="歌曲名" width="300">
<template slot-scope="scope">
<el-button @click="tablePaly(scope.row)" :plain="true" class="no-click-effect" >{{scope.row.name}}</el-button>
</template>
</el-table-column>
</el-table>
<button slot="reference" class="buttonss"><i class="el-icon-s-unfold"></i></button>
</el-popover>
</div>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
}
.no-click-effect {
border: none !important; /* 去掉边框 */
outline: none !important; /* 去掉焦点样式 */
}
/* 去掉点击时的阴影效果 */
.no-click-effect:hover,
.no-click-effect:focus {
box-shadow: none !important;
}
/* 去掉点击后背景颜色变化 */
.no-click-effect:active {
background-color: transparent !important; /* 设为透明或想要的背景颜色 */
}
.dialog .el-popover{
background-color: #f2f2f2;
border-top-left-radius: 15px; /* 左上角圆角 */
border-top-right-radius: 15px; /* 右上角圆角 */
}
.button {
display: flex;
margin-top: 80px;
justify-content: center;
}
.buttons {
width: 60px;
height: 25px;
background-color: white;
border-radius: 12px;
border: none;
font-size: 11px;
align-items: center;
color: darkslategrey;
margin-top: 42px;
margin-left: 10px;
}
.buttonss {
width: 25px;
height: 25px;
border-radius: 50%;
background: white;
border: none;
font-size: 20px;
align-items: center;
color: darkslategrey;
margin-top: 42px;
margin-left: 10px;
}
.body {
color: #666;
height: 100vh;
text-align: center;
}
.text-title {
color: white;
font-size: 20px;
padding-top: 20px;
}
audio {
width: 450px;
margin: 30px 0;
}
.container {
margin-top: 2%;
height: 420px;
overflow: hidden;
}
.container ul {
transition: 0.6s;
list-style: none;
}
.container li {
height: 30px;
line-height: 30px;
transition: 0.2s;
}
.container li.active {
color: sandybrown;
transform: scale(1.2);
}
</style>