1.底部导航的相关配置
- 配置tabBar
{
...
"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"
},
...
]
}
}
- tabBar的显示和隐藏
uni.hideTabBar({
animation:true
})
uni.showTabBar({
animation:true
})
2.头部区域的相关配置
- 获取设置头头部高度(头部自适应)
<template>
<view>
<view :style="{height:statusHeight+'px'}"></view>
</view>
</template>
<script lang="ts" setup>
const statusHeight=uni.getSystemInfoSync().statusBarHeight
</script>
- 关闭uni头部导航
{
...
"globalStyle": {
...
"navigationStyle": "custom"
}
}
3.页面跳转
- uni.navigateTo(OBJECT),uni.navigateBack(OBJECT)
uni.navigateTo({
url: 'test?id=1&name=uniapp'
});
- uni.redirectTo(OBJECT),uni.reLaunch(OBJECT),uni.switchTab(OBJECT)
- 页面传参
export default {
onLoad: function (option) {
console.log(option.id);
console.log(option.name);
}
}
4.生命周期API
- 应用的生命周期
onLaunch(){},
onShow(){},
onHide(){},
onError(){}
- 页面的生命周期
onLoad() {},
onShow() {},
onReady() {},
onHide(){},
onUnload(){}
5.下拉刷新onPullDownRefresh
- 开启下拉刷新
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app",
"enablePullDownRefresh": true
}
}
],
....
}
- 下拉刷新简单使用
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的粘性布局
- 添加内置滚动组件
<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>
- 设置container为滚动区
.container{
width: 100%;
height: calc(100vh - 70px);
overflow: hidden;
scroll-view{
height: 100%;
}
}
7.uniapp请求的封装
- 封装请求
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);
}
})
})
}
- 简单的使用
import {ajax} from './ajax.js'
export const getTopListAPI=()=>
ajax({
url:'/toplist',
method:'GET',
})
8.触底加载的实现
- 使用uniapp内置组件滚动区
<scroll-view scroll-y="true" @scrolltolower="Loading">
<commonList :books="books"/>
<view style="text-align: center;">{{text}}...</view>
</scroll-view>
- 书写实现的函数
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.实现音频播放组件的音频播放页面
- 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
state.durationTime = 300
})
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
})
music.onTimeUpdate(() => {
state.currentTime = music.currentTime
})
},
DESTRUCTION() {
music.offPlay()
music.offPause()
music.offStop()
music.offEnde()
music.offTimeUpdate()
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 = 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.state.currentTime=+value
}
},
ChangeNightFlag(context){
context.state.nightFlag=!context.state.nightFlag
}
},
}
- 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)
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;
}
}
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
}
- 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()
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;
bottom: 8rpx;
bottom: 110rpx;
bottom: 8rpx;
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>
- 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{
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动画的实现
- 简单应用
<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>
- 封装动画的hooks
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()
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()
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()
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
}
}
- 使用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
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.小说阅读页的实现
- 基本的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
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: [],
})
onLoad((option) => {
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) => {
let newVal = e.detail.value
brightNess.value = newVal
uni.setScreenBrightness({
value: newVal * 8 / 100
})
}
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()
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()
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()
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
}
}
- 基本组件
<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=()=>{
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>
- 基本页面部局
<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;
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=()=>{
typeFont.value?hideBottomFont():showBottomFont( -180 * uni.getSystemInfoSync().windowWidth / 750)
typeFont.value=!typeFont.value
}
const setMore=()=>{
console.log(1);
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%;
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>