uniApp的基本教程

1.底部导航的相关配置

  1. 配置tabBar
// pages.json,图标必须为图片底部导航的图片文件要放入static
{
    
    
	...
	"tabBar": {
    
    
		"selectedColor":"red", // 选中颜色
		"backgroundColor": "#f3f7fc", 
		"borderStyle": "black",
		"fontSize": "12px",
		"list":[
			{
    
    
				"pagePath": "pages/index/index",
				"text": "首页",
				"iconPath": "common/images/tabbar/index.png",
				"selectedIconPath": "common/images/tabbar/indexed.png"
			},
		...
		]
	}
}
  1. tabBar的显示和隐藏
uni.hideTabBar({
    
    
			 animation:true
	})
	
	uni.showTabBar({
    
    
		animation:true
	})

2.头部区域的相关配置

  1. 获取设置头头部高度(头部自适应)
<template>
	<view>
		<view :style="{height:statusHeight+'px'}"></view>
	</view>
</template>

<script lang="ts" setup>
const statusHeight=uni.getSystemInfoSync().statusBarHeight
</script>
  1. 关闭uni头部导航
{
    
    
...
	"globalStyle": {
    
    
		...
		// 关闭头部标签
		"navigationStyle": "custom"
	}
}

3.页面跳转

  1. uni.navigateTo(OBJECT),uni.navigateBack(OBJECT)
uni.navigateTo({
    
    
	url: 'test?id=1&name=uniapp'
});
  1. uni.redirectTo(OBJECT),uni.reLaunch(OBJECT),uni.switchTab(OBJECT)
// uni.redirectTo:关闭当前页面,跳转到应用内的某个页面。
// uni.reLaunch:关闭所有界面,跳转到应用内的某个页面。
// uni.switchTab:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
  1. 页面传参
export default {
    
    
	onLoad: function (option) {
    
     //option为object类型,会序列化上个页面传递的参数
		console.log(option.id); //打印出上个页面传递的参数。
		console.log(option.name); //打印出上个页面传递的参数。
	}
}

4.生命周期API

  1. 应用的生命周期
// 只能在App.vue里监听应用的生命周期
// onLaunch()相当于vue中的mounted 初始化触发
onLaunch(){
    
    },
// ,后台进入前台
onShow(){
    
    },
// 前台进入后台
onHide(){
    
    },
// 报错后触发
onError(){
    
    }
  1. 页面的生命周期
// 监听页面加载
onLoad() {
    
    },
// 监听页面显示,每次出现在屏幕上触发
onShow() {
    
    },
// 监听页面初次渲染完成
onReady() {
    
    },
// 监听页面隐藏
onHide(){
    
    },
// 监听页面卸载
onUnload(){
    
    }

5.下拉刷新onPullDownRefresh

  1. 开启下拉刷新
// 在 pages.json 里,找到的当前页面的pages节点,并在 style 选项中开启 enablePullDownRefresh。
{
    
    
    "pages": [
        {
    
    
        	"path": "pages/index/index",
        	"style": {
    
    
        		"navigationBarTitleText": "uni-app",
        		"enablePullDownRefresh": true
        	}
        }
    ],
    ....
}

  1. 下拉刷新简单使用
export default {
    
    
	data() {
    
    
		return {
    
    
			text: 'uni-app'
		}
	},
	onLoad: function (options) {
    
    
		setTimeout(function () {
    
    
			console.log('start pulldown');
		}, 1000);
		// 开始刷新
		uni.startPullDownRefresh();
	},
	onPullDownRefresh() {
    
    
		console.log('refresh');
		setTimeout(function () {
    
    
			// 停止刷新
			uni.stopPullDownRefresh();
		}, 1000);
	}
}

6.uniapp的粘性布局

  1. 添加内置滚动组件
<template>
	<view>
		<Musichead title="网易云音乐" :icon="false"></Musichead>
		<view class="container">
			<scroll-view scroll-y="true">
				<view v-for="item in 100">111</view>	
			</scroll-view>
		</view>
	</view>
</template>
  1. 设置container为滚动区
.container{
    
    
		width: 100%;
		height: calc(100vh - 70px);
		overflow: hidden;
		scroll-view{
    
    
			height: 100%;
		}
	}

7.uniapp请求的封装

  1. 封装请求
const baseUrl="http://localhost:3000"

// 封装请求函数
export const ajax=(options={
     
     })=>{
    
    
	return new Promise((resolve,reject)=>{
    
    
		uni.request({
    
    
				url: baseUrl + options.url,
				method: options.method,
				data: options.data,
				success: (response) => {
    
    
					return resolve(response.data)
				},
				fail: (fail) => {
    
    
					console.log('fail',fail)
					return reject(fail);
				}
			})
	})
}
  1. 简单的使用
import {
    
    ajax} from './ajax.js'

export const getTopListAPI=()=>
ajax({
    
    
	url:'/toplist',
	method:'GET',
})

8.触底加载的实现

  1. 使用uniapp内置组件滚动区
<scroll-view scroll-y="true" @scrolltolower="Loading">
				<commonList :books="books"/>
				<view style="text-align: center;">{
    
    {
    
    text}}...</view>
</scroll-view>
  1. 书写实现的函数
// 触底加载
const Loading= async ()=>{
    
    
	text.value="加载中"
	const data:any=await ajax({
    
    
			url:'/readList',
			method:'GET'
		}) 
	if(data.books){
    
    
		text.value="上拉加载显示更多"
		books.value=[...books.value,...data.books]
		return
	}
	text.value="没有更多了"
}

9.实现音频播放组件的音频播放页面

  1. vuex中audio模块
import {
    
     ajax } from '@/common/ajax'
import obj from './music'

let music, timer;
export const audio = {
    
    
	namespaced: true,
	state: () => {
    
    
		return {
    
    
			playStatus: false, // 当前播放状态
			audioList: [],// 音频列表
			indexPlay: 0,// 当前歌曲标识
			durationTime: 300,// 总时长
			currentTime: 0,// 当前时长
			nightFlag:true, // 是否是白天
		}
	},
	mutations: {
    
    
		INIT(state, value) {
    
    
			state.audioList = value
		},
		// 监听事件
		ADDAUDIOEVENT(state,dispatch) {
    
    
			music.onCanplay(()=>{
    
    
				state.durationTime = music.duration	
			   
				// #ifdef MP-WEIXIN
				 state.durationTime = 300
				// #endif
			})
			// 音频播放事件	
			music.onPlay(() => {
    
    	
				console.log('播放')
			})
			// 音频暂停事件
			music.onPause(() => {
    
    
				console.log('暂停')
			})
			// 音频停止事件	
			music.onStop(() => {
    
    
				console.log('停止')
			})
			// 音频自然播放结束事件	
			music.onEnded(() => {
    
    
				console.log('结束')
				dispatch('changeIndex',state.indexPlay+1)
			})
			// 音频播放错误事件
			music.onError((res) => {
    
    
				state.playStatus=false
				// dispatch('changeIndex',state.indexPlay+1)
			})
			// 音频播放进度更新事件
			music.onTimeUpdate(() => {
    
    
				state.currentTime = music.currentTime
			})
		},
		// 取消监听
		DESTRUCTION() {
    
    
			// 取消监听 onPlay 
			music.offPlay()
			// 取消监听 onPause 
			music.offPause()
			// 取消监听 onStop 
			music.offStop()
			// 取消监听 onEnded 
			music.offEnde()
			// 取消监听 onTimeUpdate 
			music.offTimeUpdate()
			// 取消监听 onError 
			music.offError()
		},
		// 播放事件
		ADUDIOPLAY(state) {
    
    			
			music.play()
			state.playStatus = true
		},
		// 暂停
		ADUDIOPAUSE(state) {
    
    
			music.pause()
			state.playStatus = false
		},
		// 停止
		AUDIOSTOP() {
    
    
			music.stop()
		},
		// 改变播放标识
		CHANGEINDEX(state, value: number) {
    
    
			state.indexPlay = value
		},
	    // 音频跳转
		ADUDIOSEEK(state,value:number){
    
    
			music.seek(+value)
		}
	
	},
	actions: {
    
    
		// 初始化
		async init(context) {
    
    
			if (music) return;
			music = uni.createInnerAudioContext();
			// 获取资源
			// const list:any = await ajax({ url: '/musicResourecs', method: 'GET' })
			const list = obj
			list ? context.commit('INIT', list.musicResourecs) : ''
			music.src =context.state.audioList[context.state.indexPlay].src	
			context.commit('ADDAUDIOEVENT',context.dispatch)
			console.log(list);
		},
		// 播放和暂停
		playOrPause(context) {
    
    
			context.state.playStatus
				? context.commit('ADUDIOPAUSE')
				: context.commit('ADUDIOPLAY')
		},
		// 切换歌曲
		changeIndex(context, value: number) {
    
    
			if (value < 0)  value =context.state.audioList.length - 1
			if (value >context.state.audioList.length - 1) value = 0
			// 先停止
			context.commit('AUDIOSTOP')
			// 切换歌曲
			context.commit('CHANGEINDEX', value)
			music.src =context.state.audioList[value].src
			// 播放音乐
			context.commit('ADUDIOPLAY')
		},
	    // 滑动
		sliderToplay(context,value){
    
    
			 context.commit('ADUDIOSEEK',value)
			 if(!context.state.playStatus){
    
    
			  	// 如果暂停下拖到就播放
			  	// context.commit('ADUDIOPLAY')
			  	context.state.currentTime=+value
			  	// music.startTime=context.state.currentTime		  	
			  }	
		},
		// 切换白天黑夜
		ChangeNightFlag(context){
    
    
		  context.state.nightFlag=!context.state.nightFlag
		}
	},
}
  1. audio的hooks内容
import {
    
     computed, Ref } from "vue";
import {
    
     useStore } from 'vuex'

// 获取时长信息
export const useTime = () => {
    
    
	const store = useStore()

	const formatTime = (time) => {
    
    
		let m = Math.floor(time / 60)
		let s = Math.floor(time % 60)
		// 补0
		const zero = (x: number) => ('0').repeat(2 - x.toString().length)
		return `${
      
      zero(m > 0 ? m : 1) + m}:${
      
      zero(s > 0 ? s : 1) + s}`
	}
	// 总时长
	const durationTime = computed(() => {
    
    
		console.log(store.state.audio.durationTime);
		const time = store.state.audio.durationTime
		return [formatTime(time), time]
	})
	// 当前时长
	const currentTime = computed(() => {
    
    
		const time = store.state.audio.currentTime
		return [formatTime(time), time]
	})
	return [durationTime, currentTime]
}

// 获取音乐的信息
export const useMusicInfo = (index: Ref<number>, list: any) => {
    
    
	return computed(() => {
    
    
		const info = list.value[index.value]
		return info ? [info.name, info.singer.name,info.singer.synopsis,info.id] : ['', '','','']
	})
}

// 处理音乐的信息
export const useSetMusic = () => {
    
    
	const store = useStore()
	// 播放的状态
	const flag = computed(() => {
    
    
		return store.state.audio.playStatus
	})
	// 播放音频
	const playOrPause = () => {
    
    
		store.dispatch('audio/playOrPause')
	}
	// 处理进度条
	const sliderToplay = (e) => {
    
    
		store.dispatch('audio/sliderToplay', e.detail.value)
	}
	return {
    
     flag, playOrPause, sliderToplay }
}

// 切换音乐
export const useSwitchMusic = () => {
    
    
	const store = useStore()
	// 第几首音乐
	const Index: Ref<number> = computed({
    
    
		set(value) {
    
    
			if (value < 0) value = audioList.value.length - 1
			if (value > audioList.value.length - 1) value = 0
			store.dispatch('audio/changeIndex', value)
		},
		get() {
    
    
			return store.state.audio.indexPlay
		}
	})
	// 切换音乐
	const changeIndex = (start) => {
    
    
		switch (start){
    
    
			case '+':Index.value++ 
				break;
			case '-':Index.value--
				break;
	       default: Index.value=start
		        break;
		}
		// start === '+' ? Index.value++ : Index.value--
	}
	// 音乐列表
	const audioList = computed(() => {
    
    
		return store.state.audio.audioList
	})
	return {
    
     Index, changeIndex, audioList }
}
// 切换夜间模式
export const useChnagenight=()=>{
    
    
  const store=useStore()
  const nightFlag=computed({
    
    
  set(){
    
    
	  store.dispatch('audio/ChangeNightFlag')  
  },
  get(){
    
    
	 	return store.state.audio.nightFlag  
  }
  })
  return nightFlag
}
  1. audio的组件中(简单的音频框架)
<template>
	<view class="audio"  v-if="audioList" @tap="toDetailpage">
		<!-- 进度部分 -->
		<view class="audio-slider">
			<!-- 歌曲总时长 -->
			<view>{
    
    {
    
    durationTime[0]}}</view>
			<view  @tap.stop>
				<slider block-size="13" activeColor="#e48267" backgroundColor="#eef2f3" :value="currentTime[1]" :max="durationTime[1]" @change="sliderToplay" @changing="sliderToplay" />
			</view>
			<!-- 当前时间 -->
			<view>{
    
    {
    
    currentTime[0]}}</view>
		</view>
		<view class="audio-info">
			<!-- 音频的相关信息 -->
			<view >
				<view v-show="MusicInfo[0]!==''">歌手-{
    
    {
    
    MusicInfo[0]}}</view>
				<view v-show="MusicInfo[1]!==''">歌曲-{
    
    {
    
    MusicInfo[1]}}</view>
			</view>
			<!-- 按键 -->
			<view @tap.stop>
				<global-icon iconId="icon-shangyishou" :iconSize="75" @tap="changeIndex('-')"></global-icon>
				<global-icon :iconId="!flag?'icon-ziyuan':'icon-bofang'" :iconSize="75" style="margin: 0 20rpx;"
					@tap="playOrPause" />
				<global-icon iconId="icon-xiayishou" :iconSize="75" @tap="changeIndex('+')"></global-icon>
			</view>
		</view>
	</view>
</template>

<script lang="ts" setup>
	import {
    
    useStore} from 'vuex'
	import {
    
    onBeforeUnmount,onMounted} from "vue";
    import {
    
     useTime,useMusicInfo,useSetMusic,useSwitchMusic} from '@/hooks/audio'

	const store = useStore()
	// 初始化音频
	const init = () => {
    
    
		store.dispatch('audio/init')
	}
	// 销毁音频
	const destory = () => {
    
    
		store.commit('audio/DESTRUCTION')
	}
	// 播放的状态 播放音频  处理进度条
	const {
    
    flag,playOrPause,sliderToplay}=useSetMusic()
	// 第几首音乐  切换音乐  音乐列表
	const {
    
    Index, changeIndex, audioList}=useSwitchMusic()
	// console.log(audioList);
	// 音乐的信息
	const MusicInfo=useMusicInfo(Index,audioList)
    // 音乐的时间
	const [durationTime,currentTime]=useTime()	
	
	// 跳转
	const toDetailpage=()=>{
    
    
		uni.navigateTo({
    
    
			url:'/pages/index/indexMusic/indexMusicDetail'
		})
	}
	onMounted(() => {
    
    
		init()
	})
	onBeforeUnmount(() => {
    
    
		destory()
	})
</script>

<style lang="scss" scoped>
	.audio {
    
    
		position: fixed;
		right: 0;
		/* #ifdef  APP-PLUS  */
		bottom: 8rpx;
		/* #endif  */
		/* #ifdef  H5  */
		bottom: 110rpx;
		/* #endif  */
		/* #ifdef  MP-WEIXIN  */
		bottom: 8rpx;
		/* #endif  */
		left: 0;
		z-index: 1030;
		height: 160rpx;
		margin: 0 20rpx;
		background-color: #d1ccc0;
		opacity: .9;
		border-radius: 20rpx;

		.audio-slider {
    
    
			justify-content: center;
			align-items: center;
			display: flex;
			color: #7a8388;
			font-size: 28rpx;
			height: 65rpx;

			view {
    
    
				&:nth-child(2) {
    
    
					width: 500rpx;
				}
			}
		}

		.audio-info {
    
    
			display: flex;
			justify-content: space-between;
			align-items: center;
			margin: 0 20rpx;

			view {
    
    
				&:nth-child(1) {
    
    
					font-size: 28rpx;
					color: #424651;
				}

				&:nth-child(2) {
    
    
					font-size: 28rpx;
					color: #424651;
				}
			}
		}
	}
</style>

  1. audio的组件中(音频详情)
<template>
	<view :class="!nightFlag?'nightTheme':''">
		<global-pageTitle >音乐详情</global-pageTitle>
		<view class="container">
			<scroll-view scroll-y="true">
				<!-- 歌曲信息 -->
				<view class="music-title">
				    <view>
				         <text>歌曲:</text>
				         <text>{
    
    {
    
    MusicInfo[0]}}</text>
					</view>
				    <view>
						   <text>歌手:</text>
						   <text>{
    
    {
    
    MusicInfo[1]}}</text>
					</view>
				</view>	
			    <!-- 歌曲图片 -->
				<view class="music-image">
			    	<image src="../../../static/music/music1.png" mode="widthFix"></image>
			    </view>
				<!-- 歌曲滚动条 -->
				<view class="music-slider">
					<!-- 歌曲总时长 -->
					<view>{
    
    {
    
    durationTime[0]}}</view>
					<view>
						<slider block-size="13" activeColor="#e48267" backgroundColor="#eef2f3" :value="currentTime[1]" :max="durationTime[1]" @change="sliderToplay" @changing="sliderToplay" />
					</view>
					<!-- 当前时间 -->
					<view>{
    
    {
    
    currentTime[0]}}</view>
				</view>
			    <!-- 按键部分  -->
			  	<view class="music-icon">
					<view class="music-icon-top">
						<view @tap="changeIndex('-')">
							<global-icon iconId="icon-shangyixiang" :iconSize="85"  />
						</view>
						<view @tap="playOrPause">
							<global-icon :iconId="!flag?'icon-bofang1':'icon-zanting'" :iconSize="80" />
						</view>
						<view @tap="changeIndex('+')">
						    <global-icon iconId="icon-xiayixiang" :iconSize="85"  />
						</view>
					</view>
					<view class="music-icon-bottom">
						<view @tap="listFlag=!listFlag">
							<global-icon :iconId="listFlag?'icon-icon--':'icon-liebiao'" :iconSize="60" />
							<text>播放列表</text>
						</view>
						<view @tap="collectFlag=!collectFlag">
							<global-icon :iconId="collectFlag?'icon-aixinfengxian':'icon-xihuan2'" :iconSize="60" />
							<text>收藏</text>
						</view>
						<view @tap="nightFlag=!nightFlag">
							<global-icon :iconId="nightFlag?'icon-yejianmoshi':'icon-yueliang'" :iconSize="60" />
							<text>夜间模式</text>
						</view>
					</view>
				</view>
			    <!-- 歌曲介绍 -->
				<view class="music-detail animated fadeInUp" v-if="listFlag">
			    	<view class="music-detail-introduce">
			    		<view>
			    		    <view>
			    		         <text>歌曲:</text>
			    		         <text>{
    
    {
    
    MusicInfo[0]}}</text>
			    			</view>
			    		    <view>
			    				   <text>歌手:</text>
			    				   <text>{
    
    {
    
    MusicInfo[1]}}</text>
			    			</view>
			    		</view>	
						<view class="music-detail-icon" @tap="showPopup">
							<global-icon iconId="icon-jieshao" :iconSize="60" />
						</view>
						
			    	</view>
					<view class="music-author-introduce">
						<view >
							歌手简介:
						</view>
						<view v-if="MusicInfo[2]!==''">
							{
    
    {
    
    MusicInfo[2]}}							
						</view>
					</view>
			    </view>
			    <!-- 播放列表 -->
				<view class="music-list animated fadeInUp" v-else>
					<view class="music-list-header">
						列表选择
					</view>
					<scroll-view scroll-y class="music-list-context">
						<template v-for="(item,index) in audioList" :key="item.id">
						   <view class="music-list-item" hover-class="active" @tap="changeIndex(index)">
							<text>{
    
    {
    
    item.name}}</text>
							<text>{
    
    {
    
    item.singer.name}}</text>
							<view >		
								<text v-show="Index==index">播放</text>
								<global-icon iconId="icon-bofangsanjiaoxing" :iconSize="40" v-show="Index==index"/>
							</view>
						   </view>
						</template>
					</scroll-view>
				</view>
			</scroll-view>
		</view>
		<global-popup ref="popup" :text="MusicInfo[2]"/>
	</view>
</template>

<script lang="ts" setup>
    import {
    
     useTime,useMusicInfo,useSetMusic,useSwitchMusic,useChnagenight} from '@/hooks/audio'
    import {
    
     ref} from 'vue'
   
    // 播放的状态 播放音频  处理进度条
	const {
    
    flag,playOrPause,sliderToplay}=useSetMusic()
	// 第几首音乐  切换音乐  音乐列表
	const {
    
    Index, changeIndex, audioList}=useSwitchMusic()
	// 音乐的信息
	const MusicInfo=useMusicInfo(Index,audioList)
    // 音乐的时间
	const [durationTime,currentTime]=useTime()
	// 列表显示与否        
	const listFlag=ref(true)
	// 列表按键
	const collectFlag=ref(true)
	// 黑夜模式
	const nightFlag=useChnagenight()
	// 弹出层
	const popup=ref(null)
	// 显示弹出层
	const showPopup=()=>{
    
    
		popup.value.show()
	}
</script>

<style lang="scss" scoped>
	
	@keyframes myfirst
		{
    
    
		    from {
    
    transform: scale(1.3);}
		}
	.active{
    
    
		background-color: #f8f9fa;
	}
	
	
	
.music-title{
    
    
	display: flex;
	flex-direction: column;
	align-items: center;
	margin: 23rpx 0;
	view{
    
    
		text{
    
    
			&:nth-child(1){
    
    
				font-size: 28rpx;
			}
			&:nth-child(2){
    
    
				font-weight: 700;
			}
		}
	}
}
.music-image{
    
    
	height: 420rpx;
	width: 85%;
	margin: 0 auto;
	overflow: hidden;
	border-radius: 35rpx;
	image{
    
    
		width: 100%;
		height: 100%;
	}
}
.music-slider{
    
    
	display: flex;
	justify-content: center;
	align-items: center;
	color: #7a8388;
	font-size: 28rpx;
	height: 65rpx;
	margin: 20rpx 0;
	view {
    
    
		&:nth-child(2) {
    
    
			width: 500rpx;
		}
	}
}
.music-icon{
    
    
	margin-top:20rpx;
.music-icon-top{
    
    
	display: flex;
   justify-content: center;
   view{
    
    
	  &:nth-child(2){
    
    
	  		 margin: 0 50rpx 0 60rpx;
	  		 
	  } 
	  &:active{
    
    
	  		animation-name: myfirst; // 动画名
	  		animation-duration: 1s;
	  		animation-fill-mode: both;
	  }
   }

}
.music-icon-bottom{
    
    
	display: flex;
	justify-content: center;
	view{
    
    
		display: flex;
		flex-direction: column;
		margin: 60rpx 35rpx;
		align-items: center;
		text{
    
    
			margin-top: 15rpx;
			font-size: 25rpx;
			color: rgba(0, 0, 0, 0.5);
			
		}
	}
}
}
.music-detail{
    
    
	// position: fixed;
	box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.3);	
    margin: 0 auto;
	height:240rpx;
	width: 95%;
	padding: 20rpx 15rpx;
	box-sizing: border-box;
	border-radius: 30rpx;
	.music-detail-introduce{
    
    
		display: flex;
		justify-content: space-between;
		view{
    
    
			&:nth-child(1){
    
    
			 text{
    
    
				&:nth-child(1){
    
    
					font-size: 28rpx;
				}
				&:nth-child(2){
    
    
					font-weight: 700;
				}
			 }	
			}
		}
		.music-detail-icon{
    
    
			display: flex;
			flex-direction: column;
			justify-content: center;
			align-items: center;
			font-size: 28rpx;
		}
	}
	.music-author-introduce{
    
    
		margin-top: 20rpx;
		font-size: 28rpx;
		view{
    
    
			&:nth-child(2){
    
    
				text-overflow: ellipsis;
				white-space: nowrap;
				overflow: hidden;
			}
		}
	}
}
.music-list{
    
    
	box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.3);
	margin: 0 auto;
	height:400rpx;
	width: 95%;
	padding: 20rpx 15rpx;
	padding-right: 40rpx;
	box-sizing: border-box;
	border-radius: 30rpx;
	.music-list-header{
    
    
		font-weight: 700;
		height: 50rpx;
		line-height: 50rpx;
	}
	.music-list-context{
    
    
		height: 330rpx;
		margin-left: 20rpx;
		overflow: hidden;
		.music-list-item{
    
    
			display: flex;
			align-items: center;
			height: 85rpx;
			text{
    
    
				text-overflow: ellipsis;
				&:nth-child(1){
    
    
					margin-left: 10rpx;
					flex: 1;
					font-size: 27rpx;
				}
				&:nth-child(2){
    
    
					flex: 1;
					font-size: 27rpx;
				}
			}
			view{
    
    		
				flex: 1;
				text{
    
    
					margin-right: 10rpx;
				}
			}
		}
	}
}
</style>

10.uniApp动画的实现

  1. 简单应用
<template>
	<view :animation="animationData">
	</view>
</template>

<script lang="ts" setup>

// 动画
const animationData = ref()
// 创建动画
const animation = uni.createAnimation()
//控制显示
const show=()=>{
      
      
   animation.translate3d(0, -100,0).step()
	animationData.value=animation.export()
}
// 控制隐藏 // 取消
const hide=()=>{
      
      
	
	animation.translate3d(0, 100 ,0).step() 
	animationData.value=animation.export()
}
</script>

  1. 封装动画的hooks
// 添加动画(动画是通过定位隐藏元素,而非v-if)
export const useAddanimate = (name:string) => {
    
    
	const height=getCurrentInstance().proxy.$height
	const obj={
    
    }
	// 防止引入同一动画对象
	const arrName=[
		"TopanimationData"+name,
		"Topanimation"+name,
		"showTop"+name,
		"hideTop"+name,
		
		"BottomanimationData"+name,
		"Bottomanimation"+name,
		"showBottom"+name,
		"hideBottom"+name,
		
		"LeftanimationData"+name,
		"Leftanimation"+name,
		"showLeft"+name,
		"hideLeft"+name,
	]
	
	// 添加动画
	obj[arrName[0]]= ref()
	// 创建动画
	obj[arrName[1]] = uni.createAnimation()
	// 上方显示动画(对应的定位写(-top)px)
	obj[arrName[2]] = (top: number) => {
    
    
		obj[arrName[1]].translate3d(0, top+height, 0).step()
		obj[arrName[0]].value = obj[arrName[1]].export()
	}
	// 上方隐藏动画
	obj[arrName[3]] = () => {
    
    
		obj[arrName[1]].translate3d(0, 0, 0).step()
		obj[arrName[0]].value = obj[arrName[1]].export()
	}
	
	// 添加动画
	obj[arrName[4]] = ref()
	// 创建动画
	obj[arrName[5]] = uni.createAnimation()
	// 下方显示动画(对应的定位写(bottom)px)
	obj[arrName[6]] = (bottom: number) => {
    
    
		obj[arrName[5]].translate3d(0, bottom, 0).step()
		obj[arrName[4]].value = obj[arrName[5]].export()
	}
	// 下方隐藏动画
	obj[arrName[7]] = () => {
    
    
		obj[arrName[5]].translate3d(0, 0, 0).step()
		obj[arrName[4]].value = obj[arrName[5]].export()
	}
	
	// 添加动画
	obj[arrName[8]]= ref()
	// 创建动画
	obj[arrName[9]] = uni.createAnimation()
	// 左边显示动画(对应的定位写(-top)px)
	obj[arrName[10]] = (left: number) => {
    
    
		obj[arrName[9]].translate3d(left, 0, 0).step()
		obj[arrName[8]].value = obj[arrName[9]].export()
	}
	// 左边隐藏动画
	obj[arrName[11]] = () => {
    
    
		obj[arrName[9]].translate3d(0, 0, 0).step()
		obj[arrName[8]].value = obj[arrName[9]].export()
	}
return 	{
    
    		
	 ...obj
	}
}

  1. 使用hooks
<template>
	<view >
		<view class="popup" :animation="BottomanimationDataPopup">
		     <view class="popup-list" @tap="cancelColl">
				 <global-icon iconId="icon-xingxing" iconColor="red"/>
			    {
   
   {text}}
	      	 </view>
		     <view class="popup-cancel" @tap="hide">
			  取消
		     </view>
		</view>
		<view class="masking" v-if="flagMask" @tap="hide"></view>
	</view>
</template>

<script lang="ts" setup>
import {
      
      ref} from 'vue'
import {
      
      useAddanimate } from '@/hooks/reading'

interface Props{
      
      
  text:string
}	
defineProps<Props>()
// 执行取消
const emit =defineEmits<{
      
      (e:'cancelColl'):void}>()
// 蒙层
const flagMask=ref(false)
// 添加动画
const {
      
      BottomanimationDataPopup,showBottomPopup,hideBottomPopup}=useAddanimate('Popup') as any

//控制显示
const show=()=>{
      
      
	flagMask.value=true
	// -200rpx转换成px
	showBottomPopup(-200* uni.getSystemInfoSync().windowWidth / 750)
	uni.hideTabBar({
      
      
		animation:true
	})
}
// 控制隐藏 // 取消
const hide=()=>{
      
      
	flagMask.value=false
	uni.showTabBar({
      
      
		animation:true
	})
	hideBottomPopup()
}
// 点击取消收藏
const cancelColl=(callback)=>{
      
      
	emit('cancelColl')
	hide()
}

defineExpose({
      
      
	show
})

</script>

<style lang="scss" scoped>
.masking{
      
      
	position: absolute;
	width: 100%;
	height: 100%;
	top:0;
	left:0;
	right: 0;
	border: 0;
	background-color: rgba(0, 0, 0, 0.3);
}
.popup{
      
      
	width: 100%;
	height: 200rpx;
	z-index:1;
	background-color: white;
	position: fixed;
	bottom: -200rpx;
	.popup-list{
      
      
		padding-left: 30rpx;
		line-height: 100rpx;
		border-bottom: 5rpx solid rgba(0, 0, 0, 0.1);
		font-size: 28rpx;
	}
	.popup-cancel{
      
      
		border-top: 5rpx solid rgba(0, 0, 0, 0.1);
		text-align: center;
		line-height: 100rpx;
		font-size: 34rpx;
	}
}
</style>

11.小说阅读页的实现

  1. 基本的hooks
import {
    
     getCurrentInstance, ref, reactive, computed } from 'vue'
import test from '@/common/test'
import {
    
     onLoad } from '@dcloudio/uni-app'
// 计算剩余高度()
export const useHeight = () => {
    
    
	const instance = getCurrentInstance()
	// 基本参数
	let screenWidth = uni.getSystemInfoSync().windowWidth, // 屏幕宽度
		screenHeight = uni.getSystemInfoSync().windowHeight; // 屏幕高度 

	const getSystemHeight = ({
     
      isRpx = true }) => isRpx ? Torpx(screenHeight) : screenHeight; // 获取屏幕高度
	// 基本方法	
	const Torpx = num => 750 * num / screenWidth// px转rpx
	// 计算高度
	const calHeight = ref(getSystemHeight(true) - Torpx(instance.proxy.$height))// 页面高度

	return calHeight
}

// 小说的信息
export const useBookInfo = () => {
    
    
	// 小说的信息
	const bookInfo = reactive({
    
    
		name: test.name, // 书名
		Index: 0, // 当前章节标识
		loadContent: [], // 已经加载的章节
		pageLength: 1, // 章节的长度
		chapterCatalog: [], // 章节的信息
	})
	// 获取章节id
	onLoad((option) => {
    
    
		// 标识对应id-1
		bookInfo.Index = +option.id - 1
		// 加载的章节
		const content = test.content.find((item) => {
    
    
			if (item.id === bookInfo.Index + 1) {
    
    
				return item
			}
		})
		bookInfo.chapterCatalog = test.chapterCatalog
		bookInfo.loadContent[bookInfo.Index] = {
    
     text: content.text, chapterCatalog: content.chapter }
		// 获取章节的长度
		bookInfo.pageLength = test.chapterCatalog.length

	})
	// 切换章节
	const switchPage = (e) => {
    
    
		// 如果对应的数组位置部存在数据
		if (!bookInfo.loadContent[e.detail.current]) {
    
    
			const content = test.content.find((item) => {
    
    
				if (item.id === e.detail.current + 1) {
    
    
					return item
				}
			})
			bookInfo.loadContent[e.detail.current] = {
    
     text: content.text, chapterCatalog: content.chapter }
		}
		bookInfo.Index = e.detail.current
	}
	return [bookInfo, switchPage]
}

// 修改字体
export const useSetFont = () => {
    
    
	// 字体(本地存储中获取值)
	const font = reactive({
    
    
		fontSize: uni.getStorageSync('fontSize') ? uni.getStorageSync('fontSize') : 20,
		fontSpacing: uni.getStorageSync('fontSpacing') ? uni.getStorageSync('fontSpacing') : 20,
	})
	// 防抖
	const timer: any = ref()
	// 改变字体大小
	const changefontSize = (e) => {
    
    
		clearTimeout(timer.value)
		timer.value = setTimeout(() => {
    
    
			font.fontSize = e.detail.value
			uni.setStorageSync('fontSize', font.fontSize);
		}, 300)

	}
	// 改变字体间距
	const changefontSpacing = (e) => {
    
    
		clearTimeout(timer.value)
		timer.value = setTimeout(() => {
    
    
			font.fontSpacing = e.detail.value

			uni.setStorageSync('fontSpacing', font.fontSpacing)
		}, 300)
	}
	return {
    
     font, changefontSize, changefontSpacing }
}

// 设置亮度
export const useSetbrightNess = () => {
    
    
	const brightNess = ref(0)
	// 获取亮度
	uni.getScreenBrightness({
    
    
		success: (val) => brightNess.value = Math.floor(val.value) / 8 * 100
	})
	// 设置亮度
	const setBrightNess = (e) => {
    
    
		// #ifdef APP
		let newVal = e.detail.value
		brightNess.value = newVal
		uni.setScreenBrightness({
    
    
			value: newVal * 8 / 100
		})
		// #endif
	}
	return [brightNess, setBrightNess]
}

// 设置主题
export const useSetTheme = (themes: any) => {
    
    
	// 主题标识
	const themeIndex = ref(uni.getStorageSync('themeIndex') ? +uni.getStorageSync('themeIndex') : 3)// 主题的标识
	// 更改主题标识
	const changeTheme = (id) => {
    
    

		let curIndex = themes.findIndex(item => item.id === id)
		console.log(curIndex, id);
		themeIndex.value = curIndex
		uni.setStorageSync('themeIndex', themeIndex.value)
	}
	// 当前主题
	const thisTheme = computed(() => {
    
    
		return themes[+themeIndex.value].id
	})
	return [thisTheme, changeTheme]
}


// 添加动画
export const useAddanimate = (name:string) => {
    
    
	const height=getCurrentInstance().proxy.$height
	const obj={
    
    }
	// 防止引入同一动画对象
	const arrName=[
		"TopanimationData"+name,
		"Topanimation"+name,
		"showTop"+name,
		"hideTop"+name,
		
		"BottomanimationData"+name,
		"Bottomanimation"+name,
		"showBottom"+name,
		"hideBottom"+name,
		
		"LeftanimationData"+name,
		"Leftanimation"+name,
		"showLeft"+name,
		"hideLeft"+name,
	]
	
	// 添加动画
	obj[arrName[0]]= ref()
	// 创建动画
	obj[arrName[1]] = uni.createAnimation()
	// 上方显示动画(对应的定位写(-top)px)
	obj[arrName[2]] = (top: number) => {
    
    
		obj[arrName[1]].translate3d(0, top+height, 0).step()
		obj[arrName[0]].value = obj[arrName[1]].export()
	}
	// 上方隐藏动画
	obj[arrName[3]] = () => {
    
    
		obj[arrName[1]].translate3d(0, 0, 0).step()
		obj[arrName[0]].value = obj[arrName[1]].export()
	}
	
	// 添加动画
	obj[arrName[4]] = ref()
	// 创建动画
	obj[arrName[5]] = uni.createAnimation()
	// 下方显示动画(对应的定位写(bottom)px)
	obj[arrName[6]] = (bottom: number) => {
    
    
		obj[arrName[5]].translate3d(0, bottom, 0).step()
		obj[arrName[4]].value = obj[arrName[5]].export()
	}
	// 下方隐藏动画
	obj[arrName[7]] = () => {
    
    
		obj[arrName[5]].translate3d(0, 0, 0).step()
		obj[arrName[4]].value = obj[arrName[5]].export()
	}
	
	// 添加动画
	obj[arrName[8]]= ref()
	// 创建动画
	obj[arrName[9]] = uni.createAnimation()
	// 左边显示动画(对应的定位写(-top)px)
	obj[arrName[10]] = (left: number) => {
    
    
		obj[arrName[9]].translate3d(left, 0, 0).step()
		obj[arrName[8]].value = obj[arrName[9]].export()
	}
	// 左边隐藏动画
	obj[arrName[11]] = () => {
    
    
		obj[arrName[9]].translate3d(0, 0, 0).step()
		obj[arrName[8]].value = obj[arrName[9]].export()
	}
return 	{
    
    		
	 ...obj
	}
}

  1. 基本组件
<template>
	<view class="left-list " :animation="LeftanimationDataleft"  :class="props.class" >
		 <slot></slot>		
	   </view>
	<view class="masking" v-if="flagMask" @tap="hide"></view>
</template>

<script lang="ts" setup>
	import {
    
    ref} from 'vue'
	import {
    
    useAddanimate} from '@/hooks/reading'
	interface Props{
    
    
		class:string
	}
	const {
    
    LeftanimationDataleft,
		showLeftleft,
		hideLeftleft,}=useAddanimate('left') as any
	const props=defineProps<Props>()
		
	// 控制蒙层
	const flagMask=ref(false)
	// 主显示
	const flag=ref(false)
	// 控制显示
	const show=()=>{
    
    
		// 右边隐藏为-400rpx转换成px  num * uni.getSystemInfoSync().windowWidth / 750
	    showLeftleft(400* uni.getSystemInfoSync().windowWidth / 750)
		flagMask.value=true
		flag.value=true
	}
	// 控制隐藏
	const hide=()=>{
    
    		
		hideLeftleft()
		flagMask.value=false
		flag.value=false
	}
defineExpose({
    
    
	show,hide
})
</script>

<style lang="scss" scoped>
 .left-list{
    
    
	 position:fixed;
	 top:0;
	 left:-400rpx;
	 height: 100%;
	 width: 400rpx;
	 background-color: white;
	 z-index: 2;
 }
 .masking{
    
    
 	position: absolute;
 	width: 100%;
 	height: 100%;
 	top:0;
 	left:0;
	z-index: 1;
 	right: 0;
 	border: 0;
 	background-color: rgba(0, 0, 0, 0.3);
 }
</style>

  1. 基本页面部局
<template>
	<view  :class="thisTheme">
		<view :style="{height:statusHeight+'px'}" class="cal" :class="thisTheme"></view>
		
		<!-- 设置开始 -->
		<!-- 设置头部部分 -->
		<view class="reading-head"  @tap="goback" :class="thisTheme" :animation="TopanimationData"  >
			<view class="reading-head-context" v-if="bookInfo.loadContent[bookInfo.Index]">
				<global-icon iconId="icon-jiantou-copy" style="margin: 0 20rpx;"/>
				<text>{
    
    {
    
    bookInfo.name}}</text>
				<text>章节:{
    
    {
    
    bookInfo.loadContent[bookInfo.Index].chapterCatalog}}</text>
			</view>
		</view>
		<!-- 设置底部部分 -->
		<view class="reading-bottom"  :class="thisTheme" :animation="BottomanimationData" >
			<view @tap="List.show()">
				<global-icon iconId="icon-xueyuan-mulu" :iconSize="55"></global-icon>
				<view>
					目录
				</view>
			</view>
			<view @tap="thisTheme==='nightTheme'?changeTheme('morningTheme'):changeTheme('nightTheme')">
				<global-icon iconId="icon-yanjing" :iconSize="55"></global-icon> 
				<view>
					夜间模式
				</view>
			</view>
			<view @tap="setFont">
				<global-icon iconId="icon-ziti1" :iconSize="55"></global-icon>
				<view>
					字体
				</view>
			</view>
			<view @tap="setMore">
				<global-icon iconId="icon-diqiuhuanqiu" :iconSize="55"></global-icon>
				<view>
					更多
				</view>
			</view>
		</view>
	    <!-- 阅读文本部分 -->
		<!-- 文本开始 -->
	    <swiper  :style="{height:`${calHeight}rpx`,fontSize:`${font.fontSize}rpx`,lineHeight:`${font.fontSpacing}rpx`}" :current="bookInfo.Index" @change="switchPage">
		    <swiper-item v-for="(item,index) in bookInfo.pageLength" >
		    <scroll-view scroll-y="true"  @tap="setFlag" :style="{height:`${calHeight}rpx`}">
			<view  class="reading-content" v-if="bookInfo.loadContent[index]">
				<rich-text :nodes="bookInfo.loadContent[index].text"></rich-text>
			</view>
	    	</scroll-view>
	  	</swiper-item>
	    </swiper>
	   <!-- 章节目录组件 -->
	   <global-leftList ref="List" :class="thisTheme">
		   <view class="list-head">
		   	章节目录
		   </view>
		   <scroll-view scroll-y="true" style="height: 95%;">
		   		<view class="list-item" v-for="(item,index) in bookInfo.chapterCatalog" :key="item.id" hover-class="active" :class="{active:index==bookInfo.Index}" @tap="tohapter(index)"> 		   					   
		   			{
    
    {
    
    item.title}}
		   		</view>
		   </scroll-view>
		  
	   </global-leftList>
	   <!-- 字体设置 v-if="typeFont" -->
	   <view class="font-set animated slideInUp"  :class="thisTheme"  :animation="BottomanimationDataFont">
        	<view class="font-setfont">字体:<slider min="20" max="50"  @changing="changefontSize"  :value="font.fontSize" style="width: 550rpx;" @change="" block-size="16" activeColor="#34495e" backgroundColor="#ecf1f0"></slider></view>
            <view class="font-setfont">间距:<slider min="20" max="100" @changing="changefontSpacing" :value="font.fontSpacing" style="width: 550rpx;" @change="" block-size="16" activeColor="#34495e" backgroundColor="#ecf1f0"/></view>
	   </view>
	   <!-- 更多部分设置 -->
	   <view class="more-set" :class="thisTheme" :animation="BottomanimationDataMore">
	    	<view class="more-setfont">亮度:<slider @changing="setBrightNess"  :value="brightNess" min="0" mix="100" style="width: 550rpx;"  block-size="16" activeColor="#34495e" backgroundColor="#ecf1f0"></slider></view>
	        <view class="more-theme" >
	       	   <template v-for="item in themes" :key="item.id">
				   <view class="more-theme-item"  @tap="changeTheme(item.id)">
				   	   <view :class="item.id" style="height: 80rpx;border-radius:20rpx ;"></view>
				    	<view >{
    
    {
    
    item.name}}</view>
				   </view>
	       	   </template>
	        </view>
	   </view>
	 </view>
</template>

<script lang="ts" setup>
import {
    
     getCurrentInstance,ref} from 'vue'
import {
    
    useHeight,useBookInfo,useSetFont,useSetbrightNess,useSetTheme,useAddanimate} from '@/hooks/reading'
// 开始底部头部的动画
const {
    
    TopanimationData,showTop,hideTop,BottomanimationData,showBottom,hideBottom}=useAddanimate('') as any
// 字体动画
const {
    
    BottomanimationDataFont,showBottomFont,hideBottomFont}=useAddanimate('Font') as any
// 更多部分动画
const {
    
    BottomanimationDataMore,showBottomMore,hideBottomMore}=useAddanimate('More') as any
// 控制更多的显示
const themeFlag=ref(false)
// 控制修改字体的显示
const typeFont=ref(false)
// 显示头部底部
const Flag=ref(false)
// 设置头部底部的显示和隐藏
const setFlag=()=>{
    
    
	switch (Flag.value){
    
    
		case true:hideBottom(),hideTop()
			break;
		// 下方隐藏为-200rpx转换成px  num * uni.getSystemInfoSync().windowWidth / 750;	
		default:showBottom(-200* uni.getSystemInfoSync().windowWidth / 750),showTop(100* uni.getSystemInfoSync().windowWidth / 750)
			break;
	}
		if(typeFont.value!==false){
    
    
			setFont()
		}
		themeFlag.value?setMore():''
	Flag.value=!Flag.value
}
// 控制字体的显示和隐藏
const setFont=()=>{
    
    
	// 下方隐藏为-180rpx转换成px  num * uni.getSystemInfoSync().windowWidth / 750;
	typeFont.value?hideBottomFont():showBottomFont( -180 * uni.getSystemInfoSync().windowWidth / 750)
	typeFont.value=!typeFont.value
}
// 设置更多
const setMore=()=>{
    
    
	console.log(1);
	// 下方隐藏为-250rpx转换成px  num * uni.getSystemInfoSync().windowWidth / 750;
	themeFlag.value?hideBottomMore():showBottomMore( -250 * uni.getSystemInfoSync().windowWidth / 750)
	themeFlag.value=!themeFlag.value
}
// 退出看书
const goback=()=>{
    
    
	uni.navigateTo({
    
    
		url:'/pages/index/indexBookDetail/indexBookDetail?id='+bookInfo.Index
	})
}
// 目录
const List=ref(null)
// 更据目录跳转章节
const tohapter=(index)=>{
    
    
	bookInfo.Index=index
	// 隐藏目录
	setFlag()
	List.value.hide()
}
// 主题
const themes=[
	{
    
    
						id: 'blueTheme',
						name: '天蓝'
					},
					{
    
    
						id: 'eyeHelpTheme',
						name: '护眼'
					},
					{
    
    
						id: 'lightGretTheme',
						name: '淡灰'
					},
					{
    
    
						id: 'morningTheme',
						name: '早晨'
					},
					{
    
    
						id: 'nightTheme',
						name: '夜间'
					}
				]
const instance = getCurrentInstance() 
// 状态栏的高度(不同场景)
const statusHeight=instance?.proxy?.$height
// 剩余高度
const calHeight= useHeight()
// 小说信息
const [bookInfo,switchPage]=useBookInfo()
// 设置字体
const {
    
    font,changefontSize,changefontSpacing}=useSetFont()
// 设置亮度
const [brightNess, setBrightNess]=useSetbrightNess()
// 设置主题
const [thisTheme,changeTheme]=useSetTheme(themes)

</script>

<style lang="scss" scoped>
	
	.active{
    
    
			background-color: rgba(0, 0, 0, 0.1);
		}
// 阅读文章的头部
.reading-head{
    
    
	position: fixed;
	top:-100rpx;
	z-index: 1;
	width: 100%;
	box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.15);
	padding-right:30rpx ;
	.reading-head-context{
    
    
		display: flex;
		align-items: center;
		height: 80rpx;	
	    text{
    
    
			&:nth-child(2){
    
    
			 font-size: 28rpx;
			}
			
			&:nth-child(3){
    
    
				flex: 1;
				margin: 0 40rpx;
				font-size: 23rpx;
				overflow: hidden;
				text-overflow: ellipsis;
				white-space: nowrap;
			}
		}
	}
}
// 阅读文章的底部
.reading-bottom{
    
    
	position: fixed;
	bottom: -200rpx;
	z-index: 1;
	height: 200rpx;
	width: 100%;
	// background-color:white;
	box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.15);
	display: flex;
	align-items: center;
	view{
    
    
		flex: 1;
		text-align: center;
		font-size: 28rpx;
	}
}

.reading-content{
    
    
	padding: 0 30rpx;

}
// 章节目录头
.list-head{
    
    
	text-align: center;
	margin-top: 20rpx;
	font-size: 38rpx;
}
.list-item{
    
    
	padding: 20rpx 20rpx;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;	
	border-bottom: 1px solid rgba(0, 0, 0, 0.3);
}
.font-set{
    
    
	z-index:1;
	height: 180rpx;
	position: fixed;
	bottom: -180rpx;
	width: 750rpx;

	box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.15);
	.font-setfont{
    
    
		display: flex;
		align-items: center;
		width: 750rpx;
		margin: 20rpx;
	}
}
.more-set{
    
    
	z-index:1;
	height: 250rpx;
	position: fixed;
	bottom: -250rpx;
	width: 750rpx;

	box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.15);
	.more-setfont{
    
    
		display: flex;
		align-items: center;
		width: 750rpx;
		margin: 20rpx;
	}
	.more-theme{
    
    
		display: flex;
		align-items: center;
		padding: 0 20rpx;
		.more-theme-item{
    
    
			flex: 1;
			margin-right:  25rpx;
			text-align: center;
		}	
	}
}
</style>

猜你喜欢

转载自blog.csdn.net/qq_45897636/article/details/127752080