Commit 819117c1 authored by jyx's avatar jyx

代码优化

parent c9610869
......@@ -18,10 +18,10 @@ function gotoBookSearchPage(searchType = ENUM_SEARCH_TYPE.WAREHOUSE, keyword) {
// 文章详情
function gotoBookContentPage(bookId, shortis) {
if (shortis && shortis == 1) {
gotoLongBookContentPage(bookId)
} else {
gotoShortBookContentPage(bookId)
if (shortis && shortis == 0) {
gotoShortBookContentPage(bookId)
} else {
gotoLongBookContentPage(bookId)
}
}
......
......@@ -3,7 +3,7 @@
<swiper :autoplay="true" :interval="2000" :duration="500">
<swiper-item @click="handleBanner(item)" v-for='(item, index) in bannerList' :key='index'
style="border-radius: 20rpx;">
<image class="banner-img" :src="item.carouselUrl" mode="scaleToFill"></image>
<image class="banner-img" :src="item.carouselUrl" mode="aspectFill"></image>
</swiper-item>
</swiper>
</view>
......@@ -51,11 +51,11 @@
<style lang="scss">
.banneritem {
margin: 30rpx 30rpx 0 30rpx;
margin: 20rpx 30rpx 0 30rpx;
// height: 310rpx;
.banner-img {
height: 310rpx;
height: 320rpx;
}
}
</style>
\ No newline at end of file
<template>
<scroll-view class="c-list" :style="[scrollStyle]" scroll-y :refresher-enabled='ableRefresh'
refresher-threshold='100' :enable-flex='true' :refresher-triggered='refreshTrigger' @refresherrefresh='refresh'
@scrolltolower='onLoadMore'>
refresher-default-style="black" refresher-threshold='100' :enable-flex='true'
:refresher-triggered='refreshTrigger' @refresherrefresh='refresh' @scrolltolower='onLoadMore'>
<view class="content" :style="[contentStyle]">
<template v-if='showCover'>
<slot name="cover"></slot>
......@@ -18,7 +18,7 @@
</template>
</view>
<uni-load-more v-if='ableLoadMore' :status="loadMoreStatus.value" :contentText="loadMoreContentText"
iconType="snow"></uni-load-more>
iconType="circle"></uni-load-more>
</scroll-view>
</template>
......@@ -279,7 +279,7 @@
}
},
/** customeRequst 不会使用的接口*/
requestData() {
requestData(noLoading) {
if (this.needLogin && !this.userInfo) {
this.loading = false;
this.closeTrigger();
......@@ -304,7 +304,9 @@
header: this.header || {},
callback: (success, data) => {
this.loading = false;
this.closeTrigger();
if (!noLoading) {
this.closeTrigger();
}
if (success) {
let rows = data.records;
let total = data.total;
......
......@@ -54,7 +54,7 @@
this.$nextTick(() => {
let ref = this.$refs.list;
if (ref) {
ref.onPullRefreshing();
ref.requestData(1);
}
})
},
......
......@@ -2,7 +2,7 @@
<view :style="[bgStyle]">
<c-empty v-if='showEmpty'></c-empty>
<template v-else>
<detail-content-flip :detail='bookData' :userInfo='userInfo' @tabVip2="tapVipPop"></detail-content-flip>
<detail-content :detail='bookData' :userInfo='userInfo' @tabVip2="tapVipPop"></detail-content>
<detail-bottom :detail='bookData' :userInfo='userInfo' @tapBottomItem='tapBottomItem'></detail-bottom>
<setting-pop :show='showSetting' @close='closePop'></setting-pop>
<catalogue-pop :detail='bookData' :show='showCatalogue' @close='closeCataPop'
......@@ -11,6 +11,9 @@
<bean-pop v-if="bookData.isUnlock==0 && userInfo.bookLegumes<bookData.bookLegumes" :show='showBean'
@close='closeBeanPop'></bean-pop> -->
<recommend-pop :show='showRecommend' @close='closeRecommendPop' :bookId="bookId"></recommend-pop>
<coin-popup v-if="bookData.isUnlock==0 && !isVip()" :show="showVip" @close="closeVipPop"
:vedioId="bookData.id" @paySuccess="paySuccess">
</coin-popup>
</template>
<!-- <c-login :isShareLink="true"></c-login> -->
<popup :show="showMoibleLogin" @close="showMoibleLogin=false">
......@@ -41,7 +44,6 @@
import DetailWarn from "./components/detail-warn.vue";
import DetailThumb from "./components/detail-thumb.vue";
import DetailContent from "./components/detail-content.vue";
import DetailContentFlip from "./components/detail-content-flip.vue"
import DetailBottom from "./components/detail-bottom.vue";
import DetailNewBuy from "./components/detail-new-buy.vue";
import config from "../../../config/index.js";
......@@ -64,8 +66,7 @@
noticeCollectionListChange,
startCountReadTime,
endCountReadTime,
getOpens,
getChapterinfoData
getOpens
} from "../../../common/services/index.js"
import {
saveStorage,
......@@ -77,7 +78,6 @@
DetailWarn,
DetailThumb,
DetailContent,
DetailContentFlip,
DetailNewBuy,
DetailBottom,
SettingPop,
......@@ -112,8 +112,6 @@
this.bookId = info.bookId;
})
}
this.mobileLoginLock = readStorage("KEY_NEED_PHONE")
},
onReady() {
// 监听样式变动
......@@ -216,8 +214,6 @@
console.log(e.detail.errMsg) // 回调信息(成功失败都会返回)
console.log(e.detail.errno) // 错误码(失败时返回)
this.showMoibleLogin = false
if (e.detail.code == undefined || e.detail.code == '') {
uni.showModal({
title: "提示",
......@@ -254,13 +250,13 @@
// this.showVipOpen = data.openVips
// })
getBookDetailData(bookId, (success, data) => {
setTimeout(() => {
uni.stopPullDownRefresh();
if (success) {
this.bookData = new BookDetail(data);
console.log('bookData=' + JSON.stringify(this.bookData));
}
}, 1000)
// setTimeout(() => {
uni.stopPullDownRefresh();
if (success) {
this.bookData = new BookDetail(data);
console.log('bookData=' + JSON.stringify(this.bookData));
}
// }, 300)
})
},
// 点击底部按钮
......@@ -305,19 +301,10 @@
},
// 展示充值VIP弹框
tapVipPop() {
if (this.mobileLoginLock) {
this.showMoibleLogin = true
return
}
this.showVip = true;
},
// 展示充值书豆弹框
tapBeanPop() {
if (this.mobileLoginLock) {
this.showMoibleLogin = true
return
}
this.showBean = true;
},
......@@ -332,10 +319,9 @@
// 关闭会员弹窗
closeVipPop(e) {
this.showVip = false;
// setTimeout(() => {
// this.showRecommend = true;
// }, 300);
setTimeout(() => {
this.showRecommend = true;
}, 300);
},
// 关闭书豆弹窗
closeBeanPop(e) {
......
<template>
<uni-popup ref='pop' type="left" :is-mask-click='false' :safe-area='false' @maskClick='tapMask'>
<scroll-view scroll-y style="background-color: white;">
<view class="setting-box">
<block v-for='(item, index) in catalogueList' :key='index' :item='item'>
<view @click="changeCatalogue(item.chapterNum)">
<view
style="width: 400rpx;max-width: 400rpx;display: flex;flex-direction: row;justify-content: space-between;align-items: center;">
<text class="text"
:style="{'width: 350rpx;color': item.chapterNum==currentCatalogue?'#f00':'#000'}">
{{item.chapterNum}}{{item.chapterName}}</text>
<image v-if="(index+1)>detail.freeNum" style="width: 30rpx;height: 30rpx;"
src="https://mints-pkg.oss-cn-beijing.aliyuncs.com/pkg/img/lock.png"></image>
<view @click="tapMask">
<scroll-view scroll-y style="background-color: white;width: 400rpx;max-width: 400rpx;" @click.stop="">
<view class="setting-box">
<block v-for='(item, index) in catalogueList' :key='index' :item='item'>
<view @click.stop="changeCatalogue(item.chapterNum)">
<view
style="display: flex;flex-direction: row;justify-content: space-between;align-items: center;">
<text class="text"
:style="{'width: 350rpx;color': item.chapterNum==currentCatalogue?'#f00':'#000'}">
{{item.chapterNum}}{{item.chapterName}}</text>
<image v-if="(index+1)>detail.freeNum" style="width: 30rpx;height: 30rpx;"
src="https://mints-pkg.oss-cn-beijing.aliyuncs.com/pkg/img/lock.png"></image>
</view>
<view style="background-color: gainsboro;height: 1rpx;width: 400rpx;margin: 20rpx 0;">
</view>
</view>
<view style="background-color: gainsboro;height: 1rpx;width: 400rpx;margin: 20rpx 0;"></view>
</view>
</block>
</view>
</scroll-view>
</block>
</view>
</scroll-view>
</view>
</uni-popup>
</template>
......@@ -107,7 +110,9 @@
id: this.detail.id,
catalogue: e
})
}
}
this.currentCatalogue = e
saveContentFormat(this.currentContentFormat);
this.close()
......@@ -126,7 +131,7 @@
display: flex;
flex-direction: column;
padding: 40rpx;
background: #fff;
// background: #fff;
height: 100vh;
.title {
......
<template>
<view class="detail-content">
<template>
<yingbing-ReadPage ref="page" :fontSize="fontSize" :bgColor="backgroundColor" style="height: 100vh;"
@loadmore="onLoadmore">
</yingbing-ReadPage>
</template>
</view>
</template>
<script>
import {
watchContentFormatChange,
removeContentFormatChangeWatch,
getChapterinfoData
} from "../services/index.js"
import cataloguePopVue from "./catalogue-pop.vue";
export default {
props: {
userInfo: {
type: Object,
default: function() {
return null
}
},
detail: {
type: Object,
default: function() {
return {}
}
}
},
data: function() {
return {
fontSize: '18',
backgroundColor: "#fff",
catalogue: 1,
}
},
mounted() {
// 监听样式变动
watchContentFormatChange((data) => {
let result = data.getFormatValue();
if (this.fontSize != result.fontSize) {
this.fontSize = result.fontSize.substring(0, 2)
}
if (this.backgroundColor != result.backgroundColor) {
this.backgroundColor = result.backgroundColor
}
let resultCatalogue = 1
for (let i in result.catalogueList) {
if (result.catalogueList[i].id == this.detail.id) {
resultCatalogue = result.catalogueList[i].catalogue
}
}
if (this.catalogue != resultCatalogue) {
this.catalogue = resultCatalogue
this.refreshChapterinfoData(this.catalogue)
}
}, this)
},
destroyed() {
removeContentFormatChangeWatch(this);
},
onReady() {
this.refreshChapterinfoData(this.catalogue)
},
methods: {
onLoadmore(chapter, callback) {
if (chapter >= this.detail.freeNum) {
this.tabVip()
return
}
getChapterinfoData(this.detail.id, chapter, (success, data) => {
if (success) {
this.catalogue = chapter
let mycontent = data.content.replace(/<(\/)?p>/g, '').replace(/<(\/)?sub>/g, '').replace(
/<(\/)?pre>/g, '')
.replace(/<(\/)?code>/g, '')
let content = {
chapter: chapter,
content: mycontent,
title: '第' + chapter + '章',
isStart: chapter == 1,
isEnd: chapter == this.detail.articleChapterList.length
}
callback('success', content)
}
})
},
// 文章数据刷新
refreshChapterinfoData(chapterId, callback) {
this.catalogue = chapterId
let id = this.detail.articleChapterList[chapterId].id
getChapterinfoData(this.detail.id, id, (success, data) => {
if (success) {
this.changeChapterinfo(chapterId, data.content)
}
})
},
changeChapterinfo(chapterId, content) {
this.catalogue = chapterId
let mycontent = content.replace(/<(\/)?p>/g, '').replace(/<(\/)?sub>/g, '').replace(/<(\/)?pre>/g, '')
.replace(/<(\/)?code>/g, '')
let contents = [{
chapter: chapterId, //章节序号
content: mycontent,
title: '第' + chapterId + '章', //章节标题
isStart: chapterId == 1, //是否是第一章节
isEnd: false, //是否是最后一个章节
}]
const {
page
} = this.$refs;
page.change({
contents: contents,
currentChapter: chapterId, //从第二章节开始阅读 和章节chapter对应
start: 0 //从章节的第100个字开始阅读
})
},
tabVip() {
setTimeout(() => {
this.$emit('tabVip2')
}, 1000)
}
}
}
</script>
<style lang="scss" scoped>
.detail-content {
display: flex;
flex-direction: column;
.content-paragraph {
text-indent: 1em;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
line-height: 1.5em;
margin-bottom: 1em;
color: #333;
}
.content-paragraph:last-child {
margin-bottom: 0;
}
.limit-button-box {
text-indent: 0;
height: 120rpx;
line-height: 120rpx;
text-align: center;
color: goldenrod;
font-size: 28rpx;
}
}
</style>
\ No newline at end of file
<template>
<view class="detail-content">
<template v-if='showVIPContent'>
<view class="content-paragraph" :style="[contentStyle]" v-for='(item, index) in contentSources'
:key='index'>
{{item}}
</view>
</template>
<template v-else>
<view class="content-paragraph" :style="[contentStyle]" v-for='(item, index) in preContentSources'
:key="index">
{{item}}
</view>
<view class="limit-button-box" @click="tapVip">
{{limitButtonTitle}}
</view>
</template>
</view>
</template>
<script>
import {
watchContentFormatChange,
removeContentFormatChangeWatch
} from "../services/index.js"
import {
showLoginView
} from "../../../../common/services/userServices.js"
import {
gotoVIPApplyPage
} from "../../../../common/services/page-route.js"
import User from "../../../../common/models/User.js";
export default {
props: {
userInfo: {
type: Object,
default: function() {
return null
}
},
detail: {
type: Object,
default: function() {
return {}
}
}
},
data: function() {
return {
user: null,
fontSize: `18px`,
PRECENT_VALUE:40
}
},
mounted() {
watchContentFormatChange((data) => {
let result = data.getFormatValue();
if (this.fontSize != result.fontSize) {
this.fontSize = result.fontSize
}
}, this)
},
destroyed() {
removeContentFormatChangeWatch(this);
},
watch: {
userInfo: {
handler: function(n) {
if (n) {
this.user = new User(n)
} else {
this.user = null
}
},
deep: true,
immediate: true
}
},
computed: {
contentSources: function() {
return this.detail && this.detail.content ? this.detail.content : [];
},
preContentSources: function() {
let result = this.detail && this.detail.content ? [...this.detail.content] : [];
let length = result.length;
if (length >= 2) {
if(this.detail.lockRate!=null&&this.detail.lockRate>0){
this.PRECENT_VALUE=this.detail.lockRate;
}
console.log('this.PRECENT_VALUE='+this.PRECENT_VALUE);
let precent = this.PRECENT_VALUE / 100;
let preLength = Math.floor(length * precent);
return result.splice(0, preLength);
} else {
return result;
}
},
limitButtonTitle: function() {
let result = `~剩余${100-this.PRECENT_VALUE}%内容`;
if (!this.user) {
result = `${result},点击登录后继续阅读`;
} else if (!this.user.isVip()) {
result = `${result}为会员章节内容,请前往购买会员`
}
result = `${result}~`;
return result;
},
contentStyle: function() {
return {
fontSize: this.fontSize
}
},
showVIPContent: function() {
return (this.user && this.user.isVip()) || this.detail.isUnlock
}
},
methods: {
tapLimitButton() {
if (!this.user) {
showLoginView()
} else if (!this.user.isVip()) {
gotoVIPApplyPage()
}
},
tapVip() {
let isIOS = wx.getSystemInfoSync().platform;
if (isIOS === 'ios') {
uni.showToast({
title: '暂不支持IOS系统',
icon: 'none'
})
return
}
// 展示充值VIP弹框
this.$emit('tapVip')
}
}
}
</script>
<style lang="scss" scoped>
.detail-content {
paading: 20rpx 30rpx;
margin: 20rpx 30rpx;
display: flex;
flex-direction: column;
.content-paragraph {
text-indent: 1em;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
line-height: 1.5em;
margin-bottom: 1em;
color: #333;
}
.content-paragraph:last-child {
margin-bottom: 0;
}
.limit-button-box {
text-indent: 0;
height: 120rpx;
line-height: 120rpx;
text-align: center;
color: goldenrod;
font-size: 28rpx;
}
}
<template>
<view class="pages">
<yingbing-text-reader ref="reader" :total-chapter="detail.articleChapterList.length" preloadable selectable
:background="background" :fontSize="fontSize" :lineGap="30" :color="color" :page-type="pageType"
:loadPageNum="1" @back="handleBack" @change="handleChange" @loadmore="handleLoadmore">
<template v-for="(item, index) in chapters" :slot="`vip:${item.index}`">
<view class="slots">{{item.index}}章节付费阅读提示页</view>
</template>
<!-- <template v-for="(item, index) in chapters" :slot="`end:${item.index}`">
<view class="slots">第{{item.index}}章节阅读完成提示页</view>
</template> -->
</yingbing-text-reader>
</view>
</template>
<script>
import {
watchContentFormatChange,
removeContentFormatChangeWatch,
getChapterinfoData
} from "../services/index.js"
export default {
props: {
userInfo: {
type: Object,
default: function() {
return null
}
},
detail: {
type: Object,
default: function() {
return {}
}
}
},
data() {
return {
chapters: [],
pageType: 'real',
split: '',
background: '#fff',
color: '#333',
fontSize: 18,
pageType: 'cover',
catalogue: 1,
isFisrt: true
}
},
onReady() {
let total = this.detail.articleChapterList.length
for (let i = 0; i < total; i++) this.chapters.push(this.getChapter(i + 1))
},
destroyed() {
removeContentFormatChangeWatch(this);
},
mounted() {
// 监听样式变动
watchContentFormatChange((data) => {
let result = data.getFormatValue();
if (this.fontSize != result.fontSize) {
this.fontSize = parseInt(result.fontSize.substring(0, 2))
}
if (this.background != result.backgroundColor) {
this.background = result.backgroundColor
}
let resultCatalogue = this.catalogue
for (let i in result.catalogueList) {
if (result.catalogueList[i].id == this.detail.id) {
resultCatalogue = result.catalogueList[i].catalogue
}
}
if (this.catalogue != resultCatalogue) {
this.catalogue = resultCatalogue
this.refreshChapterinfoData(this.catalogue)
}
}, this)
},
methods: {
// 文章数据刷新
refreshChapterinfoData(chapterId) {
let id = this.detail.articleChapterList[chapterId - 1].id
getChapterinfoData(this.detail.id, id, (success, data) => {
if (success) {
this.changeChapterinfo(chapterId, data.contentMd)
}
})
},
changeChapterinfo(chapterId, content) {
this.catalogue = chapterId
// let mycontent = content.replace(/<(\/)?p>/g, '').replace(/<(\/)?sub>/g, '').replace(/<(\/)?pre>/g, '')
// .replace(/<(\/)?code>/g, '')
if (this.isFisrt) {
// for (let i = 0; i < this.chapters.length; i++) {
// this.chapters[chapterId - 1].content = content
// }
this.chapters = [this.getChapter(chapterId, content)]
this.isFisrt = false
this.$refs.reader.init({
chapters: this.chapters,
title: this.detail.title,
current: chapterId,
start: 1
})
} else {
this.chapters.push(this.getChapter(chapterId, content))
// for (let i = 0; i < this.chapters.length; i++) {
// this.chapters[chapterId - 1].content = content
// }
this.$refs.reader.change({
chapters: this.chapters,
current: chapterId
})
}
},
handleChange(e) {
console.log('change', e);
},
handleLoadmore(current, callback) {
let id = this.detail.articleChapterList[current - 1].id
getChapterinfoData(this.detail.id, id, (success, data) => {
if (success) {
callback('success', this.getChapter(current, data.contentMd))
} else {
if (current < this.detail.freeNum + 1) {
callback('success', this.getChapter(current, ""))
}
}
})
},
getChapter(index, content) {
const showVip = index > this.detail.freeNum
return {
title: '第' + index + '章',
content: content,
frontSlots: showVip ? ['vip'] : [],
// backSlots: ['end'],
index: index,
isStart: index == 1,
isEnd: index == 10
}
}
}
}
</script>
<style>
.pages {
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
width: 100vw;
height: 100vh;
/* #endif */
}
.slots {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: blue;
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
/* #endif */
align-items: center;
justify-content: center;
}
.btns {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
position: fixed;
left: 0;
right: 0;
}
</style>
\ No newline at end of file
......@@ -33,6 +33,13 @@
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
}, {
"path": "pages/index",
"style": {
"navigationStyle": "default",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
}
],
// 分包配置
......
<template>
<template>
<view v-if="localActiviteFlag==1" class="body">
<view style="height: 92%;">
<bookshelf v-show="currentPage==0" ref="shelf" v-on:showEditBarH="showEditBarH" />
......@@ -156,7 +156,7 @@
} else if (this.currentPage == 3) {
this.$refs.user.show();
}
} else {
} else {
this.$refs.recommend2.show();
}
},
......
This diff is collapsed.
## 1.6.4(2024-07-30)
* 增加加载提示文字的属性配置
## 1.6.3(2024-07-29)
* 替换滚动模式上拉加载为触底加载,解决app端上拉加载无法触发的问题
## 1.6.2(2024-06-21)
* 修复yingbing-text-reader空格不显示的问题
* 修复yingbing-text-reader底部文本会被切割的问题
## 1.6.1(2024-05-28)
* 这是更新为重构插件,不想折腾的同学可以不用更新
* 兼容了vue3
* 将插件功能细分为了3个组件,具体看下面的文档
* 这个版本必须下载最新版的yingbing-flip插件才能使用,如果你想使用翻页阅读的话
## 1.6.0(2023-12-22)
* measureSize增加藏文字符测量
* 增加charSize属性单独定义字符尺寸
......@@ -36,113 +48,113 @@
## 1.4.6(2023-06-07)
* 新增自动翻页功能
* 优化页面刷新逻辑,减少报错
## 1.4.5(2023-05-27)
* 添加字符宽度自动测量功能,减少排版错误
* 新增measureSize属性,用于自定义字符宽度
## 1.4.4(2023-05-26)
* 翻页模式添加touchcancel事件
* 为兼容IOS空格计算宽度增加1px
## 1.4.3(2023-05-25)
* 新增fontFamily属性设置字体名称
* 新增fontFace属性添加自定义字体
## 1.4.2(2023-05-25)
* 修复IOS电量显示
## 1.4.1(2023-05-24)
* 修复单章节内容太短可能会引起得卡死和报错
## 1.4.0(2023-04-04)
* 优化翻页模式,减少闪烁问题
## 1.3.9(2023-04-03)
* 优化APP端电量获取,修复获取电量可能会造成闪退的问题
* 修复APP-NVUE翻页模式加载下一章节会闪烁的问题
## 1.3.8(2023-04-01)
* 修复APP-NVUE 向前翻页会闪烁的问题
* 删除一些不必要的代码文件
## 1.3.7(2023-04-01)
* APP-NVUE使用webview替代rich-text实现自定义页面
* APP-NVUE自定义页面支持添加自定义点击事件
* 稍微优化了一下翻页
## 1.3.6(2023-03-15)
* 修复在低端机上,分页会卡住的问题
## 1.3.5(2023-03-14)
* 优化整书模式的分页
## 1.3.4(2023-03-13)
* 修复APP-NVUE一些问题
## 1.3.3(2023-03-13)
* 优化滚动模式定位问题
## 1.3.2(2023-03-01)
* 修复整书模式 init初始化 无效果的 bug
## 1.3.1(2023-01-04)
* 修复直接点击上一页,文字不显示的bug
## 1.3.0(2022-12-20)
* 新增APP-NVUE和微信小程序支持
* 新增自定义插槽功能
* 整书模式已整合进入章节模式,表现效果和章节模式相同
* 新增页面顶部和底部的电池电量、章节、事件,页数显示
## 1.2.9(2022-09-06)
* 修复滑动过快造成翻页动画异常的bug
* 修复调用refresh方法报错的bug
* 修复翻页动画有时候方向判断错误的bug
* 滚动模式不再使用better-scroll插件,改用自己写的滚动组件
* 添加pageNext, pagePrev翻页方法
## 1.2.8(2022-06-08)
* 更改整书模式的翻页方法(上次更新忘记改了)
## 1.2.7(2022-06-04)
* 添加自定义页面功能(可用于付费章节、广告展示等)
* 更改触摸翻页方式,现在页面上任何地方都能触摸翻页
* 减小了翻页需要的距离
* 减少了翻页节流时间
## 1.2.6(2022-02-14)
* 修复向前点击翻页出现2种动画的bug
## 1.2.5(2021-10-19)
* 优化整书模式一些问题
## 1.2.4(2021-10-18)
* 修复滚动模式定位问题
## 1.2.3(2021-10-17)
* 添加点击区域配置,具体见下面得介绍
## 1.2.2(2021-10-17)
* 优化滚动模式
## 1.2.1(2021-10-15)
* 优化滚动模式逻辑
## 1.2.0(2021-10-15)
* 修复滚动模式下得问题
## 1.1.9(2021-10-15)
* 修复一些显示问题
* 优化了一下滚动模式滚动效果
## 1.1.8(2021-10-14)
* content对象中可以带上章节名称,方便取值
* 增加初始化loading效果
## 1.1.7(2021-10-14)
* 增加整书模式返回得页面信息
## 1.1.6(2021-10-14)
* 更改整书模式返回章节集合得参数
## 1.1.5(2021-10-14)
* 优化翻页得性能,同时最多显示3张页面
## 1.1.4(2021-10-13)
修复一些问题
## 1.1.3(2021-10-13)
* 修复一些问题
## 1.1.2(2021-10-13)
* 修复一些问题
## 1.1.1(2021-10-13)
* 修复了一些问题
## 1.1.0(2021-10-12)
* 此次更新为重构插件
* 首先感谢一下2位插件使用者提供的反馈
* 主要更新如下:
- 1、为请求事件增加了过渡效果,滚动模式下增加上拉和下拉操作
- 2、解决了一些逻辑问题,现在preload和loadmore事件触发更合理
- 3、其它都是些小更新,比如章节模式下currentChange事件增加了返回的页面信息
- 4、更改了参数结构,请注意阅读使用须知
## 1.0.5(2021-09-22)
* 修复上次更新引起无法翻到上一页的问题
## 1.0.4(2021-09-22)
* 修复横向翻页部分页面不绘制的bug
## 1.0.3(2021-09-05)
* 修复整书模式下阅读小说到最后面,下一页会从小说最前面再次排版的bug
## 1.0.2(2021-09-04)
* 修复预加载章节内容无效的问题
* 添加初始化触发currentChange事件
## 1.0.1(2021-08-29)
* 减少滚动模式下触底加载更多时的滚动距离
## 1.0.0(2021-08-28)
* 发布1.0.0版本
## 1.4.5(2023-05-27)
* 添加字符宽度自动测量功能,减少排版错误
* 新增measureSize属性,用于自定义字符宽度
## 1.4.4(2023-05-26)
* 翻页模式添加touchcancel事件
* 为兼容IOS空格计算宽度增加1px
## 1.4.3(2023-05-25)
* 新增fontFamily属性设置字体名称
* 新增fontFace属性添加自定义字体
## 1.4.2(2023-05-25)
* 修复IOS电量显示
## 1.4.1(2023-05-24)
* 修复单章节内容太短可能会引起得卡死和报错
## 1.4.0(2023-04-04)
* 优化翻页模式,减少闪烁问题
## 1.3.9(2023-04-03)
* 优化APP端电量获取,修复获取电量可能会造成闪退的问题
* 修复APP-NVUE翻页模式加载下一章节会闪烁的问题
## 1.3.8(2023-04-01)
* 修复APP-NVUE 向前翻页会闪烁的问题
* 删除一些不必要的代码文件
## 1.3.7(2023-04-01)
* APP-NVUE使用webview替代rich-text实现自定义页面
* APP-NVUE自定义页面支持添加自定义点击事件
* 稍微优化了一下翻页
## 1.3.6(2023-03-15)
* 修复在低端机上,分页会卡住的问题
## 1.3.5(2023-03-14)
* 优化整书模式的分页
## 1.3.4(2023-03-13)
* 修复APP-NVUE一些问题
## 1.3.3(2023-03-13)
* 优化滚动模式定位问题
## 1.3.2(2023-03-01)
* 修复整书模式 init初始化 无效果的 bug
## 1.3.1(2023-01-04)
* 修复直接点击上一页,文字不显示的bug
## 1.3.0(2022-12-20)
* 新增APP-NVUE和微信小程序支持
* 新增自定义插槽功能
* 整书模式已整合进入章节模式,表现效果和章节模式相同
* 新增页面顶部和底部的电池电量、章节、事件,页数显示
## 1.2.9(2022-09-06)
* 修复滑动过快造成翻页动画异常的bug
* 修复调用refresh方法报错的bug
* 修复翻页动画有时候方向判断错误的bug
* 滚动模式不再使用better-scroll插件,改用自己写的滚动组件
* 添加pageNext, pagePrev翻页方法
## 1.2.8(2022-06-08)
* 更改整书模式的翻页方法(上次更新忘记改了)
## 1.2.7(2022-06-04)
* 添加自定义页面功能(可用于付费章节、广告展示等)
* 更改触摸翻页方式,现在页面上任何地方都能触摸翻页
* 减小了翻页需要的距离
* 减少了翻页节流时间
## 1.2.6(2022-02-14)
* 修复向前点击翻页出现2种动画的bug
## 1.2.5(2021-10-19)
* 优化整书模式一些问题
## 1.2.4(2021-10-18)
* 修复滚动模式定位问题
## 1.2.3(2021-10-17)
* 添加点击区域配置,具体见下面得介绍
## 1.2.2(2021-10-17)
* 优化滚动模式
## 1.2.1(2021-10-15)
* 优化滚动模式逻辑
## 1.2.0(2021-10-15)
* 修复滚动模式下得问题
## 1.1.9(2021-10-15)
* 修复一些显示问题
* 优化了一下滚动模式滚动效果
## 1.1.8(2021-10-14)
* content对象中可以带上章节名称,方便取值
* 增加初始化loading效果
## 1.1.7(2021-10-14)
* 增加整书模式返回得页面信息
## 1.1.6(2021-10-14)
* 更改整书模式返回章节集合得参数
## 1.1.5(2021-10-14)
* 优化翻页得性能,同时最多显示3张页面
## 1.1.4(2021-10-13)
修复一些问题
## 1.1.3(2021-10-13)
* 修复一些问题
## 1.1.2(2021-10-13)
* 修复一些问题
## 1.1.1(2021-10-13)
* 修复了一些问题
## 1.1.0(2021-10-12)
* 此次更新为重构插件
* 首先感谢一下2位插件使用者提供的反馈
* 主要更新如下:
- 1、为请求事件增加了过渡效果,滚动模式下增加上拉和下拉操作
- 2、解决了一些逻辑问题,现在preload和loadmore事件触发更合理
- 3、其它都是些小更新,比如章节模式下currentChange事件增加了返回的页面信息
- 4、更改了参数结构,请注意阅读使用须知
## 1.0.5(2021-09-22)
* 修复上次更新引起无法翻到上一页的问题
## 1.0.4(2021-09-22)
* 修复横向翻页部分页面不绘制的bug
## 1.0.3(2021-09-05)
* 修复整书模式下阅读小说到最后面,下一页会从小说最前面再次排版的bug
## 1.0.2(2021-09-04)
* 修复预加载章节内容无效的问题
* 添加初始化触发currentChange事件
## 1.0.1(2021-08-29)
* 减少滚动模式下触底加载更多时的滚动距离
## 1.0.0(2021-08-28)
* 发布1.0.0版本
<template>
<view class="yingbing-battery">
<view class="yingbing-battery-wrapper" :style="{
'border-color': color
}">
<view class="yingbing-battery-content">
<view :style="{
flex: 1,
'background-color': color,
width: value + 'rpx'
}"></view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
color: {
type: String,
default: '#333'
}
},
data () {
return {
value: 54
}
},
// #ifdef APP-PLUS
beforeDestroy() {
if ( this.recevier ) {
plus.android.runtimeMainActivity().unregisterReceiver(this.recevier)
}
},
// #endif
created () {
this.getBattery()
},
methods: {
getBattery () {
// #ifdef H5
//window.navigator.getBattery只能在安全环境下(比如:https file:///url)使用,判断一下避免报错
window.navigator.getBattery && window.navigator.getBattery().then((res) => {
// 电池电量在0到1之间,因此我们将其乘以100得出百分比
this.value = res.level * 54
});
// #endif
// #ifdef MP-WEIXIN
wx.getBatteryInfo({
success: (res) => {
this.value = (res.level / 100) * 54
}
})
// #endif
// #ifdef APP-PLUS
uni.getSystemInfo({
success: (res) => {
if ( res.osName == 'android' ) {
const main = plus.android.runtimeMainActivity()
const Intent = plus.android.importClass('android.content.Intent');
this.recevier = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', { 
onReceive: (context, intent) => { 
       let action = intent.getAction(); 
       if (action == Intent.ACTION_BATTERY_CHANGED) { 
            let level = intent.getIntExtra("level", 0); //电量 B5教程网 
this.value = (level / 100) * 54
main.unregisterReceiver(this.recevier)//销毁注册广播
//let voltage = intent.getIntExtra("voltage", 0); //电池电压 
//let temperature = intent.getIntExtra("temperature", 0); //电池温度 
           //如需获取别的,在这里继续写,此代码只提供获取电量 
       } 
    } 
}); 
const filter = plus.android.newObject('android.content.IntentFilter', Intent.ACTION_BATTERY_CHANGED); 
main.registerReceiver(this.recevier, filter);
} else if ( res.osName == 'ios' ) {
const UIDevice = plus.ios.import("UIDevice");
const dev = UIDevice.currentDevice(); 
if (!dev.isBatteryMonitoringEnabled()) { 
    dev.setBatteryMonitoringEnabled(true); 
} 
let level = dev.batteryLevel();
this.value = level * 54
}
}
})
// #endif
}
}
}
</script>
<style scoped>
.yingbing-battery-wrapper {
width: 60rpx;
height: 24rpx;
border-width: 1px;
border-style: solid;
padding: 2rpx;
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
/* #endif */
}
.yingbing-battery-content {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
/* #endif */
}
<template>
<view class="yingbing-battery">
<view class="yingbing-battery-wrapper" :style="{
'border-color': color
}">
<view class="yingbing-battery-content">
<view class="yingbing-battery-content-value" :style="{
'background-color': color,
width: value + 'px'
}">
</view>
</view>
</view>
<view class="yingbing-battery-top" :style="{
'background-color': color
}"></view>
</view>
</template>
<script>
const max = 16
export default {
inject: ['getColor'],
computed: {
color () {
return this.getColor()
}
},
data () {
return {
value: max
}
},
// #ifdef APP-PLUS
beforeDestroy() {
if ( this.recevier ) {
plus.android.runtimeMainActivity().unregisterReceiver(this.recevier)
}
},
// #endif
created () {
this.getBattery()
},
methods: {
getBattery () {
// #ifdef H5
//window.navigator.getBattery只能在安全环境下(比如:https file:///url)使用,判断一下避免报错
window.navigator.getBattery && window.navigator.getBattery().then((res) => {
// 电池电量在0到1之间,因此我们将其乘以100得出百分比
this.value = res.level * max
});
// #endif
// #ifdef MP-WEIXIN
wx.getBatteryInfo({
success: (res) => {
this.value = (res.level / 100) * max
}
})
// #endif
// #ifdef APP-PLUS
uni.getSystemInfo({
success: (res) => {
if ( res.osName == 'android' ) {
const main = plus.android.runtimeMainActivity()
const Intent = plus.android.importClass('android.content.Intent');
this.recevier = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', { 
onReceive: (context, intent) => { 
       let action = intent.getAction(); 
       if (action == Intent.ACTION_BATTERY_CHANGED) { 
            let level = intent.getIntExtra("level", 0); //电量 B5教程网 
this.value = (level / 100) * max
main.unregisterReceiver(this.recevier)//销毁注册广播
//let voltage = intent.getIntExtra("voltage", 0); //电池电压 
//let temperature = intent.getIntExtra("temperature", 0); //电池温度 
           //如需获取别的,在这里继续写,此代码只提供获取电量 
       } 
    } 
}); 
const filter = plus.android.newObject('android.content.IntentFilter', Intent.ACTION_BATTERY_CHANGED); 
main.registerReceiver(this.recevier, filter);
} else if ( res.osName == 'ios' ) {
const UIDevice = plus.ios.import("UIDevice");
const dev = UIDevice.currentDevice(); 
if (!dev.isBatteryMonitoringEnabled()) { 
    dev.setBatteryMonitoringEnabled(true); 
} 
let level = dev.batteryLevel();
this.value = level * max
}
}
})
// #endif
}
}
}
</script>
<style scoped>
.yingbing-battery {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
opacity: 0.5;
}
.yingbing-battery-wrapper {
width: 20px;
height: 9px;
border-width: 1px;
border-style: solid;
padding: 1px;
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
/* #endif */
}
.yingbing-battery-content {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
/* #endif */
}
.yingbing-battery-content-value {
height: 5px;
}
.yingbing-battery-content-value-text {
font-size: 8px;
}
.yingbing-battery-top {
width: 2px;
height: 5px;
}
</style>
\ No newline at end of file
<template>
<view class="yingbing-reader-footer">
<view class="yingbing-reader-footer-left">
<battery ref="battery"></battery>
<text class="yingbing-reader-footer-text" :style="{color: color}">{{time}}</text>
</view>
<text class="yingbing-reader-footer-text" :style="{color: color}"> {{progress ? progress + '%' : current + ' / ' + total }}</text>
</view>
</template>
<script>
import Battery from '../battery/battery.vue'
export default {
inject: ['getColor'],
components: {
Battery
},
props: {
total: {
type: [Number, String],
default: 0
},
current: {
type: [Number, String],
default: 0
},
progress: {
type: [Number, String],
default: 0
}
},
computed: {
color () {
return this.getColor()
}
},
data () {
return {
time: ''
}
},
created() {
this.getTime()
},
methods: {
zeroize (value) {
return value >= 10 ? value : '0' + value
},
getTime () {
const d = new Date()
this.time = this.zeroize(d.getHours()) + ':' + this.zeroize(d.getMinutes())
},
refresh () {
this.$refs.battery.getBattery()
this.getTime()
}
}
}
</script>
<style scoped>
.yingbing-reader-footer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 20px;
margin-top: 10px;
}
.yingbing-reader-footer-left {
display: flex;
flex-direction: row;
align-items: center;
}
.yingbing-reader-footer-text {
font-size: 10px;
opacity: 0.5;
margin-left: 10px;
}
</style>
\ No newline at end of file
<template>
<view class="yingbing-reader-header">
<view class="yingbing-reader-header-left">
<view class="yingbing-reader-header-back" :style="{'border-top-color': color, 'border-left-color': color}" v-if="backShow"
@touchstart.stop.prevent @touchmove.stop.prevent @touchend.stop.prevent="handleBack"
@mousedown.stop.prevent @mousemove.stop.prevent @mouseup.stop.prevent="handleBack"></view>
<text class="yingbing-reader-header-text" :style="{color: color}">{{title}}</text>
</view>
<text class="yingbing-reader-header-text" :style="{color: color}" v-if="progress">{{progress.toFixed(2)}}%</text>
</view>
</template>
<script>
import Battery from '../battery/battery.vue'
export default {
inject: ['getColor', 'getBackShow'],
props: {
title: {
type: String,
default: ''
},
progress: {
type: [Number, String],
default: 0
}
},
computed: {
color () {
return this.getColor()
},
backShow () {
return this.getBackShow()
}
},
methods: {
handleBack (e) {
e.preventDefault && e.preventDefault()
e.stopPropagation && e.stopPropagation()
uni.$emit('yingbing-reader-back')
}
}
}
</script>
<style>
.yingbing-reader-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
height: 20px;
}
.yingbing-reader-header-left {
display: flex;
flex-direction: row;
align-items: center;
}
.yingbing-reader-header-text {
font-size: 10px;
opacity: 0.5;
}
.yingbing-reader-header-back {
width: 6px;
height: 6px;
border-top-width: 1px;
border-left-width: 1px;
border-top-style: solid;
border-left-style: solid;
transform: rotateZ(-45deg);
margin-right: 5px;
opacity: 0.5;
}
</style>
\ No newline at end of file
<template>
<view class="yb-flex">
<view class="yb-loading yb-flex yb-align-center">
<view v-if="visible" class="circle yb-flex" ref="loading" :style="{
width: pixelSize + 'px',
height: pixelSize + 'px',
'border-radius': pixelSize + 'px'
}">
<view
class="line yb-flex"
:style="{
'border-top-width': (pixelSize / 4) + 'px',
'border-bottom-width': (pixelSize / 4) + 'px',
'border-top-color': item.top,
'border-bottom-color': item.bottom,
width: (pixelSize / 12) + 'px',
left: ((pixelSize / 2) - (pixelSize / 24)) + 'px',
}"
:class="'line_' + index"
v-for="(item, index) in rgbs" :key="index"></view>
</view>
<text class="loading-text" :style="{color: color}" v-if="text && visible">{{text}}</text>
</view>
</view>
</template>
<script>
import Util from '@/uni_modules/yingbing-ReadPage/js_sdk/util.js'
// #ifdef APP-NVUE
const Binding = uni.requireNativePlugin('bindingx')
// #endif
export default {
props: {
visible: {
type: Boolean,
default: false
},
size: {
type: [Number, String],
default: 40
},
color: {
type: String,
default: '#333333'
},
text: {
type: String,
default: ''
}
},
computed: {
rgbs () {
let rgb = Util.hex2rgb(this.color).replace('rgb(', '').replace(')', '')
return [{
top: `rgba(${rgb}, 1)`,
bottom: `rgba(${rgb}, .4)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .5)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .6)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .7)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .8)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .9)`
}]
},
pixelSize () {
return Util.unitpixel(this.size)
}
},
data () {
return {
loading_binding: null
}
},
mounted() {
// #ifdef APP-NVUE
this.$nextTick(() => {
if ( this.visible ) {
this.start()
}
})
// #endif
},
beforeDestroy() {
// #ifdef APP-NVUE
if ( this.loading_binding ) {
Binding.unbind({
token: this.loading_binding.token,
eventType: 'timing'
})
this.loading_binding = null
}
// #endif
},
methods: {
start () {
let loading = Util.getEl(this.$refs.loading);
this.loading_binding = Binding.bind({
eventType: 'timing',
props: [{
element: loading,
property: 'transform.rotateZ',
expression: 'floor(t/100)*30'
}]
});
}
},
watch: {
visible (newVal) {
// #ifdef APP-NVUE
this.$nextTick(() => {
if ( newVal ) {
this.start()
} else {
if ( this.loading_binding ) {
Binding.unbind({
token: this.loading_binding.token,
eventType: 'timing'
})
this.loading_binding = null
}
}
})
// #endif
}
}
}
</script>
<style scoped>
@import url(@/uni_modules/yingbing-ReadPage/css/common.css);
/* #ifndef APP-NVUE */
@keyframes loading{
0% {
transform: rotateZ(30deg);
}
9.33333%{
transform: rotateZ(60deg);
}
18.66666%{
transform: rotateZ(90deg);
}
27.99999%{
transform: rotateZ(120deg);
}
37.33332%{
transform: rotateZ(150deg);
}
46.66665%{
transform: rotateZ(180deg);
}
55.99998%{
transform: rotateZ(210deg);
}
65.33331%{
transform: rotateZ(240deg);
}
74.66664%{
transform: rotateZ(270deg);
}
83.99997%{
transform: rotateZ(300deg);
}
93.33333%{
transform: rotateZ(330deg);
}
100%{
transform: rotateZ(360deg);
}
}
/* #endif */
.yb-loading .loading-text {
margin-top: 15rpx;
font-size: 28rpx;
}
.circle {
position: relative;
/* #ifndef APP-NVUE */
animation: loading 1200ms step-start infinite;
/* #endif */
}
.circle .line {
position: absolute;
border-top-style: solid;
border-bottom-style: solid;
top: 0;
bottom: 0;
}
.circle .line_0 {
}
.circle .line_1 {
transform: rotateZ(30deg);
}
.circle .line_2 {
transform: rotateZ(60deg);
}
.circle .line_3 {
transform: rotateZ(90deg);
}
.circle .line_4 {
transform: rotateZ(120deg);
}
.circle .line_5 {
transform: rotateZ(150deg);
}
</style>
<template>
<view v-if="visible" class="circle y-flex" ref="loading" :style="{
width: pixelSize + 'px',
height: pixelSize + 'px',
'border-radius': pixelSize + 'px'
}">
<view
class="line y-flex"
:style="{
'border-top-width': (pixelSize / 4) + 'px',
'border-bottom-width': (pixelSize / 4) + 'px',
'border-top-color': item.top,
'border-bottom-color': item.bottom,
width: (pixelSize / 12) + 'px',
left: ((pixelSize / 2) - (pixelSize / 24)) + 'px',
}"
:class="'line_' + index"
v-for="(item, index) in rgbs" :key="index"></view>
</view>
</template>
<script>
// #ifdef APP-NVUE
const Binding = uni.requireNativePlugin('bindingx');
// #endif
export default {
props: {
visible: {
type: Boolean,
default: true
},
size: {
type: [Number, String],
default: 40
},
color: {
type: String,
default: '#333333'
}
},
computed: {
rgbs () {
const rgb = this.hexToRgb(this.color).replace('rgb(', '').replace(')', '')
return [{
top: `rgba(${rgb}, 1)`,
bottom: `rgba(${rgb}, .4)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .5)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .6)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .7)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .8)`
},{
top: `rgba(${rgb}, .4)`,
bottom: `rgba(${rgb}, .9)`
}]
},
pixelSize () {
return this.unitpixel(this.size)
}
},
data () {
return {
loading_binding: null
}
},
mounted() {
// #ifdef APP-NVUE
this.$nextTick( function () {
if ( this.visible ) this.start()
})
// #endif
},
beforeDestroy() {
// #ifdef APP-NVUE
if ( this.loading_binding ) {
Binding.unbind({
token: this.loading_binding.token,
eventType: 'timing'
})
this.loading_binding = null
}
// #endif
},
methods: {
start () {
const loading = this.getEl(this.$refs.loading);
this.loading_binding = Binding.bind({
eventType: 'timing',
props: [{
element: loading,
property: 'transform.rotateZ',
expression: 'floor(t/100)*30'
}]
});
},
unitpixel (size) {
const text = size.toString()
return text.indexOf('rpx') > -1 ? uni.upx2px(text.replace('rpx', '')) : text.indexOf('px') > -1 ? parseFloat(text.replace('px', '')) : uni.upx2px(text)
},
hexToRgb (hex) {
hex = hex.length == 7 ? hex : '#' + hex.slice(1, 4) + hex.slice(1, 4)
let str="rgb("
const r = parseInt(hex.slice(1,3),16).toString(); //ff slice不包括end
const g = parseInt(hex.slice(3,5),16).toString(); //00
const b = parseInt(hex.slice(5,7),16).toString(); //ff
str += r+","+g+","+b+")";
return str
},
getEl (el) {
if (typeof el === 'string' || typeof el === 'number') return el;
if (WXEnvironment) {
return el.ref;
} else {
return el instanceof HTMLElement ? el : el.$el;
}
}
},
watch: {
visible (newVal) {
// #ifdef APP-NVUE
this.$nextTick(() => {
if ( newVal ) {
this.start()
} else {
if ( this.loading_binding ) {
Binding.unbind({
token: this.loading_binding.token,
eventType: 'timing'
})
this.loading_binding = null
}
}
})
// #endif
}
}
}
</script>
<style scoped>
/* #ifndef APP-NVUE */
@keyframes loading{
0% {
transform: rotateZ(30deg);
}
9.33333%{
transform: rotateZ(60deg);
}
18.66666%{
transform: rotateZ(90deg);
}
27.99999%{
transform: rotateZ(120deg);
}
37.33332%{
transform: rotateZ(150deg);
}
46.66665%{
transform: rotateZ(180deg);
}
55.99998%{
transform: rotateZ(210deg);
}
65.33331%{
transform: rotateZ(240deg);
}
74.66664%{
transform: rotateZ(270deg);
}
83.99997%{
transform: rotateZ(300deg);
}
93.33333%{
transform: rotateZ(330deg);
}
100%{
transform: rotateZ(360deg);
}
}
/* #endif */
.circle {
position: relative;
/* #ifndef APP-NVUE */
animation: loading 1200ms step-start infinite;
/* #endif */
}
.circle .line {
position: absolute;
border-top-style: solid;
border-bottom-style: solid;
top: 0;
bottom: 0;
}
.circle .line_0 {
}
.circle .line_1 {
transform: rotateZ(30deg);
}
.circle .line_2 {
transform: rotateZ(60deg);
}
.circle .line_3 {
transform: rotateZ(90deg);
}
.circle .line_4 {
transform: rotateZ(120deg);
}
.circle .line_5 {
transform: rotateZ(150deg);
}
</style>
\ No newline at end of file
export default {
provide () {
return {
getPrevChapterDefaultText: () => this.prevChapterDefaultText,
getNextChapterDefaultText: () => this.nextChapterDefaultText,
getChapterReadyText: () => this.chapterReadyText,
getChapterLoadingText: () => this.chapterLoadingText,
getChapterSuccessText: () => this.chapterSuccessText,
getChapterFailText: () => this.chapterFailText,
getPrevChapterEndText: () => this.prevChapterEndText,
getNextChapterEndText: () => this.nextChapterEndText
}
},
props: {
loadingText: {
type: String,
default: '内容排版中'
},
errorText: {
type: String,
default: '渲染失败\n\n点击重试'
},
chapterReadyText: {
type: String,
default: '松开加载'
},
chapterLoadingText: {
type: String,
default: '正在加载'
},
chapterSuccessText: {
type: String,
default: '加载成功'
},
chapterFailText: {
type: String,
default: '加载失败'
},
prevChapterDefaultText: {
type: String,
default: '加载上一章'
},
prevChapterEndText: {
type: String,
default: '前面没有了'
},
nextChapterDefaultText: {
type: String,
default: '加载下一章'
},
nextChapterEndText: {
type: String,
default: '后面没有了'
}
}
}
\ No newline at end of file
const threshold = 50
export default {
props: {
//点击事件位置设置
clickOption: {
type: Object,
default () {
return {
width: uni.upx2px(200),
height: uni.upx2px(200),
left: 'auto',
top: 'auto'
}
}
}
},
computed: {
//点击区域左边位置
clickLeft () {
return typeof this.clickOption.left == 'number' ? this.clickOption.left : (this.windowWidth / 2) - (this.clickOption.width / 2)
},
//点击区域右边位置
clickRight () {
return this.clickLeft + this.clickOption.width
},
//点击区域顶部位置
clickTop () {
return typeof this.clickOption.top == 'number' ? this.clickOption.top : (this.windowHeight / 2) - (this.clickOption.height / 2)
},
//点击区域底部位置
clickBottom () {
return this.clickTop + this.clickOption.height
}
},
data () {
return {
autoplaySync: false,
windowHeight: 0,//组件高度
windowWidth: 0,//组件宽度
clickTime: 0,//点击次数
touchstartX: 0,//触摸开始x轴位置
touchstartY: 0,//触摸开始y轴位置
touchmoveX: 0,//触摸滑动x轴距离
touchmoveY: 0,//触摸滑动y轴距离
touchTime: 0//触摸时间
}
},
methods: {
touchstart (e) {
if ( this.pageType != 'scroll' ) this.autoplaySync = false//如果是翻页阅读关闭自动播放
if ( this.touchTimer ) {//清楚定时任务
clearTimeout(this.touchTimer)
this.touchTimer = null
}
if ( this.longTimer ) {//清楚定时任务
clearTimeout(this.longTimer)
this.longTimer = null
}
const touch = e.touches[0]
this.touchstartX = touch.pageX
this.touchstartY = touch.pageY
//点击在规定区域中
if ( this.touchstartX >= this.clickLeft && this.touchstartX <= this.clickRight && this.touchstartY >= this.clickTop && this.touchstartY <= this.clickBottom) {
this.clickTime++
if ( this.clickTime == 1 ) {//第一次点击
this.touchTimer = setTimeout(() => {
this.touchTime = 300
}, 300)
this.longTimer = setTimeout(() => {
this.touchTime = 500
if ( this.touchmoveX <= threshold && this.touchmoveY <= threshold ) this.$emit('long-click')//抛出长按事件
this.resetTouch()
}, 500)
}
} else {//没有点击在规定区域中直接重置触摸事件
this.resetTouch()
}
},
touchmove (e) {
if ( this.clickTime > 0 ) {
const touch = e.touches[0]
this.touchmoveX = Math.abs(touch.pageX - this.touchstartX)
this.touchmoveY = Math.abs(touch.pageY - this.touchstartY)
}
},
touchend (e) {
if ( this.pageType != 'scroll' ) this.autoplaySync = this.autoplay//如果是横向翻页开启自动播放
if ( this.touchTimer ) {//清楚按下定时任务
clearTimeout(this.touchTimer);
this.touchTimer = null
}
if ( this.longTimer ) {//清楚长按定时任务
clearTimeout(this.longTimer)
this.longTimer = null
}
//点击次数为零或者触摸时间超过300,或者滑动距离超过阙值
if ( this.clickTime == 0 || this.touchTime > 300 || this.touchmoveX > threshold || this.touchmoveY > threshold ) {
this.resetTouch()
return
}
if ( this.clickTime == 1 ) {//第一次点击
this.touchTimer = setTimeout(() => {
this.$emit('single-click')//抛出单击事件
this.resetTouch()
}, 300)
}
if ( this.clickTime == 2 ) {//第二次点击
this.$emit('double-click')//抛出双击事件
this.resetTouch()
}
},
resetTouch () {
this.touchstartX = 0
this.touchstartY = 0
this.touchmoveX = 0
this.touchmoveY = 0
this.touchTime = 0
this.clickTime = 0
}
}
}
\ No newline at end of file
.computed {
position: fixed;
top: -1000rpx;
left: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
}
.computed-text {
font-size: 20px;
flex-shrink: 0;
}
\ No newline at end of file
.yingbing-read-page-flip {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.flip-item-wrapper {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
/* #endif */
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.flip-item-header {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
/* #endif */
justify-content: center;
height: 50rpx;
overflow: hidden;
}
.flip-item-header-text {
font-size: 24rpx;
opacity: .4;
font-weight: bold;
/* #ifdef APP-NVUE */
lines: 1;
text-overflow: ellipsis;
/* #endif */
/* #ifndef APP-NVUE */
display: -webkit-box !important;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
-webkit-box-orient:vertical;
-webkit-line-clamp: 1;
/* #endif */
}
.flip-item-footer {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
align-items: center;
flex-direction: row;
justify-content: space-between;
height: 50rpx;
overflow: hidden;
}
.flip-item-footer-text {
font-size: 24rpx;
opacity: .4;
font-weight: bold;
}
.flip-item-content {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
flex: 1;
}
.flip-item-text {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
/* #endif */
},
.flip-text {
/* #ifndef APP-NVUE */
box-sizing: border-box;
white-space: pre-wrap;
/* #endif */
}
.flip-loading {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.flip-loading-text {
font-size: 20px;
}
/* #ifndef APP-NVUE */
view, image, input, scroll-view, swiper, swiper-item, text, textarea, video {
position: static;
}
/* #endif */
\ No newline at end of file
import Util from '../../../js_sdk/util.js'
export default {
computed: {
current () {
return this.pages.findIndex(item => item.dataId == this.currentDataId)
},
prevDataId () {
return this.pages[this.current - 1] && this.pages[this.current - 1].dataId
},
nextDataId () {
return this.pages[this.current + 1] && this.pages[this.current + 1].dataId
}
},
data() {
return {
currentDataId: -1,
isShow: false,
viewWidth: 0,
moreLoading: false
}
},
methods: {
//翻往上一页
pagePrevFlip () {
this.$refs.flip.flipToPrev()
},
//翻往下一页
pageNextFlip () {
this.$refs.flip.flipToNext()
},
reloadLoadmoreFlip (p) {
let loadIndex = this.pages.findIndex(page => p.dataId == page.dataId)
this.$set(this.pages[loadIndex], 'type', 'loading')
let nextChapter = p.direction == 'next' ? p.chapter + 1 : p.chapter - 1
this.loadmoreFlip(nextChapter, p.direction == 'next' ? 1 : -1);
},
loadmoreFlip (chapter, value) {
this.$emit('loadmore', chapter, (status, content) => {
this.moreLoading = false;
if (status == 'success') {
const index = this.contents.findIndex(item => item.chapter == content.chapter)
if (index > -1) {
this.contents[index] = content;
} else {
this.contents.push(content);
}
this.computedPage({
content: content,
type: value > 0 ? 'next' : 'prev'
});
this.preload(chapter)
} else {
let loadIndex = this.pages.findIndex(page => page.type == 'loading' && page.direction == (value > 0 ? 'next' : 'prev'))
this.$set(this.pages[loadIndex], 'type', status)
}
})
},
handleFlipChangeRender (e) {
this.handleFlipChange(e.detail.dataId)
},
handleFlipChange (dataId) {
const value = dataId < this.currentDataId ? -1 : 1
this.currentDataId = dataId
const index = this.pages.findIndex(page => page.dataId == dataId);
let pageInfo = this.pages[index]
const nowChapters = this.pages.filter(item => item.chapter == pageInfo.chapter && (item.type == 'text' || item.type == 'custom' || item.type == 'slot'))
let contentIndex = this.contents.findIndex(content => content.chapter == pageInfo.chapter)
pageInfo.totalPage = nowChapters.length
pageInfo.currentPage = nowChapters.findIndex(item => item.dataId == pageInfo.dataId) + 1
if ( this.contents[contentIndex].title ) pageInfo.title = this.contents[contentIndex].title
this.pageInfo = pageInfo
this._emitPageInfo(pageInfo, this.pages)
const nextType = this.pages[index + value] && this.pages[index + value].type
if ( this.pages[index].type == 'loading' || nextType == 'loading') {
if (this.moreLoading) return
this.moreLoading = true;
const loadChapter = this.pages[index].chapter + value;
contentIndex = this.contents.findIndex(content => content.chapter == loadChapter)
if (contentIndex > -1) {
this.computedPage({
content: this.contents[contentIndex],
type: value > 0 ? 'next' : 'prev'
});
this.preload(loadChapter)
this.moreLoading = false;
} else {
this.loadmoreFlip(loadChapter, value)
}
} else {
this.startAutoplay()
}
}
}
}
\ No newline at end of file
<template>
<view class="yb-list yb-flex">
<!-- #ifdef APP-VUE || H5 || MP-WEIXIN || MP-QQ -->
<view class="yb-refresh yb-flex yb-flex-1"
:prop="pulldownProp" :change:prop="pulldownwxs.propWatcher"
@touchstart="pulldownwxs.touchstart"
@touchmove="pulldownwxs.touchmove"
@touchend="pulldownwxs.touchend">
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view class="yb-refresh yb-flex yb-flex-1" ref="ybRefresh">
<!-- #endif -->
<!-- #ifdef APP-VUE || H5 || MP-WEIXIN || MP-QQ -->
<view ref="ybListPulldown" class="yb-list-pulldown yb-flex">
<list-pulldown :status="pulldownStatus" :options="pulldownOptionsSync">
<template v-slot:pulldown-symbol>
<slot name="pulldown-symbol"></slot>
</template>
<template v-slot:pulldown-loading>
<slot name="pulldown-loading"></slot>
</template>
<template v-slot:pulldown-success>
<slot name="pulldown-success"></slot>
</template>
<template v-slot:pulldown-fail>
<slot name="pulldown-fail"></slot>
</template>
<template v-slot:pulldown-end>
<slot name="pulldown-end"></slot>
</template>
</list-pulldown>
</view>
<!-- #endif -->
<scroll-view
scroll-y
:render-whole="true"
@scroll="onScroll"
:scroll-top="scrollTop"
:scroll-with-animation="scrollWithAnimation"
ref="ybPulldownScroller"
class="yb-pulldown-scroller yb-pulldown-scroll-view"
@scrolltoupper="onScrolltoupper"
@scrolltolower="onScrolltolower">
<!-- #ifdef APP-NVUE -->
<template v-if="pulldownOptionsSync.show">
<refresh :display="display" @pullingdown="pullingdown($event.pullingDistance)" @refresh="refresh">
<view class="yb-flex" style="height: 30rpx;"></view>
<list-pulldown :status="pulldownStatus" :options="pulldownOptionsSync">
<template v-slot:pulldown-symbol>
<slot name="pulldown-symbol"></slot>
</template>
<template v-slot:pulldown-loading>
<slot name="pulldown-loading"></slot>
</template>
<template v-slot:pulldown-success>
<slot name="pulldown-success"></slot>
</template>
<template v-slot:pulldown-fail>
<slot name="pulldown-fail"></slot>
</template>
<template v-slot:pulldown-end>
<slot name="pulldown-end"></slot>
</template>
</list-pulldown>
<view class="yb-flex" style="height: 20rpx;"></view>
</refresh>
</template>
<!-- #endif -->
<slot></slot>
<template v-if="loadmoreOptionsSync.show">
<list-loadmore :status="loadmoreStatus" :options="loadmoreOptionsSync" @reload="reload">
<template v-slot:loadmore-symbol>
<slot name="loadmore-symbol"></slot>
</template>
<template v-slot:loadmore-loading>
<slot name="loadmore-loading"></slot>
</template>
<template v-slot:loadmore-fail>
<slot name="loadmore-fail"></slot>
</template>
<template v-slot:loadmore-end>
<slot name="loadmore-end"></slot>
</template>
</list-loadmore>
</template>
</scroll-view>
</view>
</view>
</template>
<script>
import Util from '@/uni_modules/yingbing-ReadPage/js_sdk/util.js'
import ListPulldown from './modules/pulldown/pulldown.vue'
import ListLoadmore from './modules/loadmore/loadmore.vue'
import PulldownMixin from './modules/pulldown/pulldown.js'
import LoadmoreMixin from './modules/loadmore/loadmore.js'
export default {
mixins: [PulldownMixin, LoadmoreMixin],
options: {
addGlobalClass: true,
virtualHost: true, // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
},
components: {
ListPulldown,
ListLoadmore
},
props: {
//下拉加载配置
pulldown: {
type: [Object,Boolean],
default () {
return new Object
}
},
//触底加载更多配置
loadmore: {
type: [Object,Boolean],
default () {
return new Object
}
}
},
data () {
return {
scrollTop: 0,
scrollWithAnimation: false
}
},
computed: {
Util () {
return Util
}
},
methods: {
onScrolltoupper () {
this.$emit('scrolltoupper')
},
scrollTo (offset, animated = false) {
this.scrollWithAnimation = animated
this.$nextTick(function() {
this.scrollTop = offset - 1
this.$nextTick(function () {
this.scrollTop = offset
})
})
},
onScroll (e) {
this.$emit('scroll', {
scrollTop: e.detail.scrollTop,
scrollHeight: e.detail.scrollHeight,
scrollWidth: e.detail.scrollWidth
})
}
}
}
</script>
<!-- #ifdef APP-VUE || H5 || MP-WEIXIN || MP-QQ -->
<script module="pulldownwxs" lang="wxs" src="./modules/pulldown/pulldown.wxs"></script>
<!-- #endif -->
<style scoped>
@import url(@/uni_modules/yingbing-ReadPage/css/common.css);
.yb-list {
position: relative;
flex: 1;
}
.yb-list .yb-refresh {
position: relative;
/* #ifndef APP-NVUE */
overflow: visible;
/* #endif */
}
.yb-list .yb-list-pulldown {
height: 400rpx;
margin-top: -400rpx;
justify-content: flex-end;
padding: 40rpx 0;
}
.yb-pulldown-scroller {
/* #ifndef APP-NVUE */
overflow: visible;
/* #endif */
}
.yb-list .yb-pulldown-scroll-view {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
import Util from '@/uni_modules/yingbing-ReadPage/js_sdk/util.js'
export default {
data () {
return {
isLoadmore: false,//是否触底
loadmoreStatus: '',//触底状态
isPageFirst: false//判断页面滚动时是否首次加载
}
},
computed: {
loadmoreOptionsSync () {
return Object.assign({}, {
//是否展示加载更多
show: false,
//是否采用row布局
row: false,
//默认文本
defaultText: '上拉或点击加载',
//刷新中的提示文本
refreshingText: '正在加载',
//刷新成功的文本
successText: '加载成功,点击继续',
//刷新失败的提示文本
failText: '加载失败,点击重试',
//数据全部加载完毕的提示文本
endText: '数据加载完毕',
//文本颜色
color: '#333333',
//距底部的距离
bottom: 0
}, Util.typeof(this.loadmore) == 'Object' ? this.loadmore : Util.typeof(this.loadmore) == 'Boolean' ? { show: this.loadmore } : {})
}
},
mounted () {
//页面滚动通过判断上拉加载组件是否出现在屏幕内来触发触发触底事件,组件初始化时不触发触底事件,延迟一秒后才能触发
this.$nextTick(function () {
setTimeout(() => {
this.isPageFirst = true
}, 1000)
})
},
methods: {
onScrolltolower () {
if ( !this.isPageFirst && this.typeSync == 'page' ) {
return
}
if ( this.isLoadmore ) {
return
}
this.isLoadmore = true
this.loadmoreStatus = 'loading'
this.$emit('loadmore', (state) => {
this.loadmoreStatus = state
if ( state != 'fail' && state != 'end') {
this.isLoadmore = false
// #ifdef APP-NVUE
this.$refs.ybPulldownScroller.resetLoadmore()
// #endif
}
})
},
//重置加载更多状态
resetLoadmore () {
this.loadmoreStatus = ''
this.isLoadmore = false
// #ifdef APP-NVUE
this.$refs.ybPulldownScroller.resetLoadmore()
// #endif
},
//设置加载更多为完毕状态
setLoadmoreEnd () {
this.loadmoreStatus = 'end'
this.isLoadmore = true
},
//设置加载更多为成功状态
setLoadmoreSuccess () {
this.loadmoreStatus = 'success'
this.isLoadmore = false
},
//设置加载更多为失败状态
setLoadmoreFail () {
this.loadmoreStatus = 'fail'
this.isLoadmore = false
},
//重加载更多
reload () {
this.isLoadmore = false
this.onScrolltolower()
}
}
}
\ No newline at end of file
<template>
<view class="yb-loadmore yb-flex yb-align-center yb-justify-center"
:class="{
'yb-row': options.row
}"
@tap="onTap" v-if="options.show">
<view class="yb-flex" style="height: 20rpx;"></view>
<view class="yb-flex yb-align-center yb-justify-center" :style="{'margin-left': options.row ? '-70rpx' : 0}">
<view class="yb-flex indicator-icon yb-align-center yb-justify-center">
<template v-if="status == ''">
<slot name="loadmore-symbol">
<slot name="loadmore-symbol">
<list-icon
name="arrow-up"
:size="50"
:color="options.color"></list-icon>
</slot>
</slot>
</template>
<template v-if="status == 'loading'">
<slot name="loadmore-loading">
<list-loading :visible="status == 'loading'" :color="options.color"></list-loading>
</slot>
</template>
<template v-if="status == 'fail'">
<slot name="loadmore-fail">
<list-icon
name="fork-circle"
:size="50"
:color="options.color"></list-icon>
</slot>
</template>
<template v-if="status == 'success'">
<slot name="loadmore-success">
<list-icon
name="hook-circle"
:size="50"
:color="options.color"></list-icon>
</slot>
</template>
<template v-if="status == 'end'">
<slot name="loadmore-end">
<list-icon
name="hook-circle"
:size="50"
:color="options.color"></list-icon>
</slot>
</template>
</view>
</view>
<view class="yb-flex indicator-text yb-align-center">
<text class="refresh-text" :style="{color: options.color}">{{refreshText}}</text>
</view>
<view class="yb-flex" style="height: 20rpx;"></view>
<view class="yb-flex" :style="{
height: Util.pixelunit(options.bottom)
}"></view>
</view>
</template>
<script>
import Util from '@/uni_modules/yingbing-ReadPage/js_sdk/util.js'
import ListIcon from '../common/icon.vue'
import ListLoading from '../common/loading.vue'
export default {
components: {
ListIcon,
ListLoading
},
props: {
options: {
type: Object,
default () {
return new Object
}
},
status: {
type: String,
default: ''
}
},
computed: {
refreshText () {
return this.status == 'loading' ? this.options.refreshingText : this.status == 'success' ? this.options.successText : this.status == 'fail' ? this.options.failText : this.status == 'end' ? this.options.endText : this.options.defaultText
},
Util () {
return Util
}
},
methods: {
onTap () {
if ( this.status != 'end' && this.status != 'loading' ) {
this.$emit('reload')
}
}
}
}
</script>
<style scoped>
@import url(@/uni_modules/yingbing-ReadPage/css/common.css);
.yb-loadmore .indicator-icon {
width: 70rpx;
height: 70rpx;
}
.yb-loadmore .refresh-text {
text-align: center;
font-size: 24rpx;
}
.yb-loadmore .indicator-symbol {
transition: transform .1s;
}
.yb-loadmore .refresh-time {
font-size: 23rpx;
}
</style>
\ No newline at end of file
import Util from '@/uni_modules/yingbing-ReadPage/js_sdk/util.js'
export default {
data () {
return {
pulldownStatus: '',//下拉状态
pulldownRestore: false,//下拉复位
display: 'show'
}
},
computed: {
pulldownOptionsSync () {
return Object.assign({}, {
show: false,
//是否采用row布局
row: false,
//是否显示刷新时间
enableRefreshTime: true,
//默认文本
defaultText: '下拉刷新',
//准备刷新
readyText: '释放刷新',
//刷新中的提示文本
refreshingText: '正在刷新',
//刷新成功的提示文本
successText: '刷新成功',
//刷新失败的提示文本
failText: '刷新失败',
//刷新结束的提示文本
endText: '刷新完毕',
//文本颜色
color: '#333333',
//刷新完成后的隐藏周期
duration: 300
}, Util.typeof(this.pulldown) == 'Object' ? this.pulldown : Util.typeof(this.pulldown) == 'Boolean' ? { show: this.pulldown } : {})
},
pulldownProp () {
return {
pulldownRestore: this.pulldownRestore,
enablePulldown: this.pulldownOptionsSync.show
}
}
},
methods: {
refresh () {
if ( !this.pulldownOptionsSync.show ) {
return
}
if ( this.pulldownStatus != 'end' ) {
this.pulldownStatus = 'loading'
this.$emit('pulldown', (state) => {
this.pulldownStatus = state
this.pulldownTimer = setTimeout(() => {
this.pulldownRestore = true
clearTimeout(this.pulldownTimer)
this.pulldownTimer = null
// #ifdef APP-NVUE
this.display = 'hide';
this.pulldownTimer = setTimeout(() => {
this.display = 'show'
clearTimeout(this.pulldownTimer)
this.pulldownTimer = null
}, this.pulldownOptionsSync.duration)
// #endif
}, this.pulldownOptionsSync.duration)
})
} else {
this.pulldownRestore = true
// #ifdef APP-NVUE
this.display = 'hide';
this.pulldownTimer = setTimeout(() => {
this.display = 'show'
clearTimeout(this.pulldownTimer)
this.pulldownTimer = null
}, this.pulldownOptionsSync.duration)
// #endif
}
},
pullingdown (threshold) {
if ( !this.pulldownOptionsSync.show ) {
return
}
if ( this.pulldownStatus != 'end' ) {
// #ifndef APP-NVUE
if ( threshold >= 120 ) {
this.pulldownStatus = 'ready'
} else {
this.pulldownStatus = ''
}
// #endif
// #ifdef APP-NVUE
if ( threshold >= 195 ) {
this.pulldownStatus = 'ready'
} else {
this.pulldownStatus = ''
}
// #endif
}
this.$emit('pullingdown', threshold)
},
pullingup (threshold) {
this.$emit('pullingup', threshold)
},
resetPulldownIns () {
this.pulldownTimer = setTimeout(() => {
if ( this.pulldownStatus != 'end' ) {
this.pulldownStatus = ''
}
this.pulldownRestore = false
clearTimeout(this.pulldownTimer)
this.pulldownTimer = null
}, this.pulldownOptionsSync.duration)
},
resetPulldown () {
this.pulldownStatus = ''
}
}
}
\ No newline at end of file
<template>
<view class="yb-pulldown yb-flex yb-align-center yb-justify-center"
:class="{
'yb-row': options.row
}" v-if="options.show">
<view class="yb-flex yb-align-center yb-justify-center" :style="{'margin-left': options.row ? '-70rpx' : 0}">
<view class="yb-flex indicator-icon yb-align-center yb-justify-center">
<template v-if="status == 'ready' || status == ''">
<view class="yb-flex indicator-symbol"
:style="{
'transform': 'rotateZ(' + (status == 'ready' ? '180deg' : 0) + ')'
}">
<slot name="pulldown-symbol">
<list-icon
name="arrow-down"
:size="50"
:color="options.color"></list-icon>
</slot>
</view>
</template>
<template v-if="status == 'loading'">
<slot name="pulldown-loading">
<list-loading :visible="status == 'loading'" :color="options.color"></list-loading>
</slot>
</template>
<template v-if="status == 'success'">
<slot name="pulldown-success">
<list-icon
name="hook-circle"
:size="50"
:color="options.color"></list-icon>
</slot>
</template>
<template v-if="status == 'fail'">
<slot name="pulldown-fail">
<list-icon
name="fork-circle"
:size="50"
:color="options.color"></list-icon>
</slot>
</template>
<template v-if="status == 'end'">
<slot name="pulldown-end">
<list-icon
name="hook-circle"
:size="50"
:color="options.color"></list-icon>
</slot>
</template>
</view>
</view>
<view class="yb-flex indicator-text yb-align-center">
<text class="refresh-text" :style="{color: options.color}">{{pulldownText}}</text>
<text class="refresh-time" :style="{color: options.color}" v-if="options.enableRefreshTime">上次更新 {{lastTime}}</text>
</view>
</view>
</template>
<script>
import Util from '@/uni_modules/yingbing-ReadPage/js_sdk/util.js'
import ListIcon from '../common/icon.vue'
import ListLoading from '../common/loading.vue'
export default {
components: {
ListIcon,
ListLoading
},
props: {
options: {
type: Object,
default () {
return new Object
}
},
status: {
type: String,
default: ''
}
},
data () {
return {
lastTime: '刷新时间'
}
},
computed: {
pulldownText () {
return this.status == 'ready' ? this.options.readyText : this.status == 'loading' ? this.options.refreshingText : this.status == 'success' ? this.options.successText : this.status == 'fail' ? this.options.failText : this.status == 'end' ? this.options.endText : this.options.defaultText
}
},
mounted() {
this.lastTime = this.getTime()
},
methods: {
getTime () {
let d = new Date()
return (d.getMonth() + 1) + '-' + d.getDate() + ' ' + Util.zeroize(d.getHours()) + ':' + Util.zeroize(d.getMinutes())
}
},
watch: {
status (newVal) {
if ( newVal == 'success' || newVal == 'fail' ) {
this.lastTime = this.getTime()
}
}
}
}
</script>
<style scoped>
@import url(@/uni_modules/yingbing-ReadPage/css/common.css);
.yb-pulldown .indicator-icon {
width: 70rpx;
height: 70rpx;
}
.yb-pulldown .refresh-text {
text-align: center;
font-size: 24rpx;
}
.yb-pulldown .indicator-symbol {
transition: transform .1s;
}
.yb-pulldown .refresh-time {
font-size: 23rpx;
}
</style>
\ No newline at end of file
export default {
methods: {
computedNochapter (data) {
const reg = new RegExp(/(第+[一二两三四五六七八九十○零百千万亿0-91234567890※✩★☆]{1,6}[章回卷节折篇幕集部]?[、.-\s][^\n]*)[_,-]?/g)
let match = ''
let catalog = []
let chapter = 0
let content = data.content
while ((match = reg.exec(content)) != null) {
chapter++
if ( chapter == 1 && match.index > 0 ) {
catalog.push({
title: content.slice(0, 10).replace(/[\r\n\t]/g, ''),
start: 0,
end: match.index,
content: content.slice(0, match.index),
isStart: true,
isEnd: false,
chapter: chapter
})
chapter++
}
catalog.push({
title: match[0].replace(/[\r\n\t]/g, '').slice(0, 10),
start: match.index,
isStart: false,
isEnd: false,
chapter: chapter
})
if ( chapter > 1 && !catalog[chapter-2].content ) {
catalog[chapter-2].content = content.slice(catalog[chapter-2].start, match.index)
catalog[chapter-2].end = match.index
}
}
if ( catalog.length == 1 ) {
catalog[0].content = content
catalog[0].end = content.length
}
if ( catalog.length > 0 ) {
catalog[0].isStart = true
catalog[catalog.length-1].isEnd = true
catalog[catalog.length-1].content = content.slice(catalog[catalog.length-1].start)
catalog[catalog.length-1].end = content.length
}
if ( data.content.length / catalog.length <= 10000 ) {
this.contents = catalog
if (catalog[catalog.length-1].content.length > 50000) {
let lastContent = catalog[catalog.length-1].content
this.contents.pop()
this.cutChapter({
content: lastContent,
currentChapter: data.currentChapter,
start: data.start
}, 0, 3000)
} else {
this.initLoading = true;
this.resetPage({
start: parseInt(data.start || 0),
currentChapter: parseInt(data.currentChapter),
title: data.title || null
});
this.$emit('setCatalog', this.contents)
}
} else {
this.contents = []
this.cutChapter(data, 0, 3000)
}
},
//分割章节
cutChapter (data, start, length) {
let end = start + length
let str = data.content.slice(start, end)
let index1 = str.lastIndexOf('\r')
let index2 = str.lastIndexOf('\n')
let index = Math.max(index1, index2)
index > -1 ? str = str.slice(0, index+1) : null
end = start + str.length
let chapter = this.contents.length + 1
this.contents.push({
title: '第' + chapter + '节 ' + str.replace(/[\r\n\t\s]/g, '').slice(0, 10),
chapter: chapter,
isStart: false,
isEnd: false,
start: start,
end: end,
content: str
})
if ( end < data.content.length ) {
this.cutChapter(data, end, length)
} else {
this.contents[0].isStart = true
this.contents[this.contents.length-1].isEnd = true
this.initLoading = true;
this.resetPage({
start: parseInt(data.start || 0),
currentChapter: parseInt(data.currentChapter || 1),
title: data.title || null
});
this.$emit('setCatalog', this.contents)
}
}
}
}
\ No newline at end of file
<template>
<!-- #ifndef APP-NVUE -->
<view class="read-rich-text" :style="richTextStyle" v-html="richtext">
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view class="read-rich-text" :style="richTextStyle">
<web-view
ref="webview"
@onPostMessage="onPostMessage"
:style="webviewStyle"
:src="'/uni_modules/yingbing-ReadPage/hybrid/html/richtext.html?rich=' + encodeURIComponent(JSON.stringify(richtext)) + '&pageType=' + pageType"></web-view>
</view>
<!-- #endif -->
</template>
<script>
export default {
props: {
richtext: {
type: String,
default: ''
},
pageType: {
type: String,
default: ''
},
fontFace: {
type: Array,
default () {
return new Array
}
}
},
computed: {
webviewStyle () {
return this.pageType == 'scroll' ? {
height: this.customWebviewHeight + 'px'
} : {
flex: 1
}
},
richTextStyle () {
return this.pageType == 'scroll' ? {
'padding-bottom': '20rpx'
} : {
flex: 1
}
}
},
data () {
return {
customWebviewHeight: 0
}
},
mounted() {
// #ifdef APP-NVUE
if ( this.fontFace.length > 0 ) {
this.$nextTick(function () {
setTimeout(() => {
this.setFontFace()
}, 100)
})
}
// #endif
},
methods: {
onPostMessage (e) {
e.detail.data.forEach(item => {
if ( item.customClick ) {
this.$emit('customClick', item.customClick)
}
if ( item.height ) {
this.customWebviewHeight = item.height
}
})
},
setFontFace () {
this.$refs.webview && this.$refs.webview.evalJS("setFontFace(" + encodeURIComponent(JSON.stringify(this.fontFace)) + ")")
}
},
watch: {
FontFace (newVal) {
if ( newVal.length > 0 ) {
this.$nextTick(function () {
setTimeout(() => {
this.setFontFace()
}, 100)
})
}
}
}
}
</script>
<style scoped>
.read-rich-text {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
</style>
\ No newline at end of file
.yingbing-scroll {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.scroll-wrapper {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
flex: 1;
}
.scroll-item {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.scroll-item-content {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
padding-bottom: 20rpx;
}
.scroll-text {
/* #ifndef APP-NVUE */
box-sizing: border-box;
white-space: pre-wrap;
/* #endif */
}
.scroll-item-header {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
overflow: hidden;
/* #endif */
padding: 0 40rpx;
position: fixed;
left: 20rpx;
align-items: center;
flex-direction: row;
justify-content: space-between;
height: 50rpx;
background-color: rgba(0,0,0,.4);
border-radius: 50rpx;
}
.scroll-item-header-text {
font-size: 24rpx;
color: #fff;
/* #ifdef APP-NVUE */
lines: 1;
text-overflow: ellipsis;
/* #endif */
/* #ifndef APP-NVUE */
display: -webkit-box !important;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
-webkit-box-orient:vertical;
-webkit-line-clamp: 1;
/* #endif */
}
.scroll-item-footer {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
overflow: hidden;
/* #endif */
padding: 0 40rpx;
position: fixed;
left: 20rpx;
right: 20rpx;
align-items: center;
flex-direction: row;
justify-content: space-between;
height: 50rpx;
background-color: rgba(0,0,0,.4);
border-radius: 50rpx;
}
.scroll-item-footer-text {
font-size: 24rpx;
color: #fff;
}
\ No newline at end of file
import Util from '../../../js_sdk/util.js'
export default {
data () {
return {
pageTo: 0,
scrollTop: 0,
scrolling: false,
scrollDate: ''
}
},
beforeDestroy() {
if ( this.scrollTimer ) {
clearTimeout(this.scrollTimer)
this.scrollTimer = null
}
},
mounted () {
this.scrollDate = this.filterDate()
},
methods: {
scrollNext () {
if ( this.scrolling ) {
return
}
this.scrolling = true
this.$refs.list.scrollTo(this.scrollTop + (this.windowHeight / 2), true)
this.scrollTimer = setTimeout(() => {
this.scrolling = false
clearTimeout(this.scrollTimer)
this.scrollTimer = null
}, 300)
},
scrollPrev () {
if ( this.scrolling ) {
return
}
this.scrolling = true
this.$refs.list.scrollTo(this.scrollTop - (this.windowHeight / 2), true)
this.scrollTimer = setTimeout(() => {
this.scrolling = false
clearTimeout(this.scrollTimer)
this.scrollTimer = null
}, 300)
},
pulldownScroll (callback) {
let contentsIndex = this.contents.findIndex(content => content.chapter == this.pages[0].chapter)
if ( this.contents[contentsIndex].isStart ) {
callback('end')
} else {
this.scroll_loadmore({
chapter: this.pages[0].chapter - 1,
type: 'prev'
}, callback)
this.$refs.list.resetLoadmore()
}
},
scrolltoupper () {
let contentsIndex = this.contents.findIndex(content => content.chapter == (this.pages[0].chapter - 1))
if ( contentsIndex > -1 ) {
this.scroll_loadmore({
chapter: this.pages[0].chapter - 1,
type: 'prev'
})
this.$refs.list.resetLoadmore()
}
},
loadmoreScroll (callback) {
this.stopAutoplay()
let contentsIndex = this.contents.findIndex(content => content.chapter == this.pages[this.pages.length - 1].chapter)
if ( this.contents[contentsIndex].isEnd ) {
callback('end')
} else {
this.scroll_loadmore({
chapter: this.pages[this.pages.length - 1].chapter + 1,
type: 'next'
}, callback)
this.$refs.list.resetPulldown()
}
},
//加载更多章节
scroll_loadmore (load, callback) {
const chapter = load.chapter;
const type = load.type;
const contentIndex = this.contents.findIndex(item => item.chapter == chapter);
if ( contentIndex > -1 ) {
this.computedPage({
content: this.contents[contentIndex],
type: type
});
this.preload(chapter)
callback && callback('success')
} else {
this.$emit('loadmore', chapter, (status, content) => {
if (status == 'success') {
const index = this.contents.findIndex(item => item.chapter == content.chapter)
if (index > -1) {
this.contents[index] = content;
} else {
this.contents.push(content);
}
this.computedPage({
content: content,
type: type
});
this.preload(chapter)
}
callback && callback(status)
})
}
},
async scrollEnd(e) {
const size = await this.getRect()
let rate = Math.floor(e.scrollTop / size.height)
let maybe = this.pages[rate] ? rate : this.pages.length-1
let top = -1
let pageInfo = null
while ( top < 0 ) {
let rect = await this.getScrollItemRect(this.pages[maybe].dataId)
top = rect.top
pageInfo = this.pages[maybe]
maybe++
}
if ( top >= 0 ) {
const nowChapters = this.pages.filter(item => item.chapter == pageInfo.chapter && (item.type == 'text' || item.type == 'custom' || item.type == 'slot'))
let contentIndex = this.contents.findIndex(content => content.chapter == pageInfo.chapter)
pageInfo.totalPage = nowChapters.length
pageInfo.currentPage = nowChapters.findIndex(item => item.dataId == pageInfo.dataId) + 1
this.pageInfo = pageInfo
//刷新当前时间和设备电量
this.scrollDate = this.filterDate()
this.$refs.scrollBattery.getBattery()
this._emitPageInfo(pageInfo, this.pages)
this.startAutoplay()
}
},
getScrollItemRect (dataId) {
return new Promise(resolve => {
Util.getRect('#scroll-item_' + dataId, Util.getRefs(this, 'scrollItem_' + dataId, 0), this).then(res => {
resolve(res)
})
})
},
onScroll (e) {
this.stopAutoplay()
if ( this.options.pageType == 'scroll' ) {
this.scrollTop = e.scrollTop
if ( this.scrollTimer ) {
clearTimeout(this.scrollTimer)
this.scrollTimer = null
}
this.scrollTimer = setTimeout(() => {
this.scrolling = false
this.scrollEnd(e)
}, 300)
}
}
}
}
\ No newline at end of file
var max = 200
function touchstart(event, ins) {
var state = ins.getState()
if ( !state.enablePulldown ) {
return
}
var touch = event.touches[0] || event.changedTouches[0]
state.startX = touch.pageX
state.startY = touch.pageY
}
function touchmove(event, ins) {
var state = ins.getState()
if ( state.startY > 0) {
var touch = event.touches[0] || event.changedTouches[0]
if ((Math.abs(touch.pageY - state.startY) > Math.abs(touch.pageX - state.startX)) && Math.abs(touch.pageY -
state.startY) > 20) {
var pageY = touch.pageY
var rate = max / (max + Math.abs(pageY - state.startY))
state.threshold = rate * (pageY - state.startY)
if ( state.threshold > max ) {
state.threshold = max
}
if ( state.threshold < -max ) {
state.threshold = -max
}
ins.selectComponent('.yb-pulldown-scroller').setStyle({
transform: 'translateY(' + state.threshold + 'px)',
transition: ''
})
ins.selectComponent('.yb-list-pulldown').setStyle({
transform: 'translateY(' + state.threshold + 'px)',
transition: ''
})
if ( state.threshold > 0 ) {
ins.callMethod('pullingdown', state.threshold)
} else {
ins.callMethod('pullingup', Math.abs(state.threshold))
}
}
}
}
function touchend(event, ins) {
var state = ins.getState()
if ( state.threshold > 120 && state.enablePulldown ) {
ins.selectComponent('.yb-pulldown-scroller').setStyle({
transform: 'translateY(120px)',
transition: 'transform .1s'
})
ins.selectComponent('.yb-list-pulldown').setStyle({
transform: 'translateY(120px)',
transition: 'transform .1s'
})
ins.callMethod('refresh')
} else {
ins.selectComponent('.yb-pulldown-scroller').setStyle({
transform: 'translateY(0)',
transition: 'transform .1s'
})
ins.selectComponent('.yb-list-pulldown').setStyle({
transform: 'translateY(0)',
transition: 'transform .1s'
})
}
}
function stop (ins) {
var state = ins.getState()
ins.selectComponent('.yb-pulldown-scroller').setStyle({
transform: 'translateY(0)',
transition: 'transform .1s'
})
ins.selectComponent('.yb-list-pulldown').setStyle({
transform: 'translateY(0)',
transition: 'transform .1s'
})
state.threshold = 0
state.startY = 0
ins.callMethod('resetPulldownIns')
}
function propWatcher (newVal, oldVal, ins) {
ins.setTimeout(function () {
var state = ins.getState()
state.enablePulldown = (newVal && newVal.enablePulldown)
if ( (newVal && newVal.pulldownRestore) != (oldVal && oldVal.pulldownRestore) ) {
if ( newVal.pulldownRestore ) {
stop(ins)
}
}
}, 100)
}
module.exports = {
propWatcher: propWatcher,
touchstart: touchstart,
touchmove: touchmove,
touchend: touchend
var max = 200
var refreshHeight = 80
function touchstart(event, ins) {
console.log('touchstart');
var state = ins.getState()
if ( state.refresh ) return
var touch = event.touches[0] || event.changedTouches[0]
state.startX = touch.pageX
state.startY = touch.pageY
}
function touchmove(event, ins) {
console.log('touchmove');
var state = ins.getState()
if ( state.refresh ) return
if ( state.startY > 0) {
var touch = event.touches[0] || event.changedTouches[0]
if ((Math.abs(touch.pageY - state.startY) > Math.abs(touch.pageX - state.startX)) && Math.abs(touch.pageY - state.startY) > 20) {
var pageY = touch.pageY
var rate = max / (max + Math.abs(pageY - state.startY))
state.threshold = rate * (pageY - state.startY)
if ( state.threshold > 0 && !state.pulldownable ) return
if ( state.threshold < 0 && !state.pullupable ) return
if ( state.threshold > max ) {
state.threshold = max
}
if ( state.threshold < -max ) {
state.threshold = -max
}
ins.selectComponent('.yingbing-scroller-wrapper').setStyle({
transform: 'translateY(' + state.threshold + 'px)',
transition: ''
})
if ( state.threshold > 0 ) {
ins.callMethod('pullingdown', state.threshold)
} else {
ins.callMethod('pullingup', Math.abs(state.threshold))
}
}
}
}
function touchend(event, ins) {
touchaction(event, ins)
}
function touchcancel(event, ins) {
touchaction(event, ins)
}
function touchaction (event, ins) {
var state = ins.getState()
if ( state.refresh ) return
if ( state.threshold > 0 && state.pulldownable ) {//下拉
if ( state.threshold > refreshHeight ) {//满足下拉条件
ins.selectComponent('.yingbing-scroller-wrapper').setStyle({
transform: 'translateY(' + refreshHeight + 'px)',
transition: 'transform .1s'
})
ins.callMethod('pulldown')
state.refresh = true
} else {//不满足下拉条件恢复样式
stop(ins)
}
}
if ( state.threshold < 0 && state.pullupable ) {//上拉
if ( Math.abs(state.threshold) > refreshHeight ) {//满足上拉条件
ins.selectComponent('.yingbing-scroller-wrapper').setStyle({
transform: 'translateY(-' + refreshHeight + 'px)',
transition: 'transform .1s'
})
ins.callMethod('pullup')
state.refresh = true
} else {//不满足上拉条件恢复样式
stop(ins)
}
}
}
function stop (ins) {
var state = ins.getState()
ins.selectComponent('.yingbing-scroller-wrapper').setStyle({
transform: 'translateY(0)',
transition: 'transform .1s'
})
state.threshold = 0
state.startY = 0
state.refresh = false
}
function refreshStateWatcher (newVal, oldVal, ins) {
if ( newVal == 'pulldown' ) {
var state = ins.getState()
state.threshold = refreshHeight + 1
state.refresh = false
touchaction(null, ins)
}
if ( newVal == 'pullup' ) {
var state = ins.getState()
state.threshold = -(refreshHeight + 1)
state.refresh = false
touchaction(null, ins)
}
if ( newVal == 'stop' ) stop(ins)
}
function pulldownableWatcher (newVal, oldVal, ins) {
var state = ins.getState()
if ( state ) state.pulldownable = newVal
}
function pullupableWatcher (newVal, oldVal, ins) {
var state = ins.getState()
if ( state ) state.pullupable = newVal
}
module.exports = {
refreshStateWatcher: refreshStateWatcher,
pulldownableWatcher: pulldownableWatcher,
pullupableWatcher: pullupableWatcher,
touchstart: touchstart,
touchmove: touchmove,
touchend: touchend,
touchcancel: touchcancel
}
\ No newline at end of file
<template>
<view class="yingbing-computed" :style="[computedStyle]">
<reader-header :text="chapter.title || ''" v-if="headerShowSync"></reader-header>
<web-view ref="webview" style="flex:1" src="/uni_modules/yingbing-ReadPage/hybrid/html/computed.html" @onPostMessage="onPostMessage"></web-view>
<reader-footer v-if="footerShowSync"></reader-footer>
</view>
</template>
<script>
import ReaderHeader from '../../header/header.vue'
import ReaderFooter from '../../footer/footer.vue'
export default {
inject: ['fontSize', 'fontFamily', 'lineHeight', 'topGap', 'bottomGap', 'slide', 'headerShow', 'footerShow', 'totalChapter'],
components: {
ReaderHeader,
ReaderFooter
},
computed: {
computedStyle () {
return {
'padding-top': this.topGap + 'px',
'padding-bottom': this.bottomGap + 'px',
'padding-left': this.slide + 'px',
'padding-right': this.slide + 'px'
}
}
},
data () {
return {
chapter: {},//章节内容临时存储
success: null,//成功回调
fail: null,//失败回调,
headerShowSync: false,//展示头部
footerShowSync: false//展示底部
}
},
beforeDestroy() {
this.$refs.webview && this.$refs.webview.evalJS("destroyHandleTimer()")
},
methods: {
start ({chapter, success, fail}) {
this.chapter = chapter
this.success = success
this.fail = fail
this.headerShowSync = typeof chapter.headerShow == 'boolean' ? chapter.headerShow : this.headerShow//判断是否显示头部
this.footerShowSync = typeof chapter.footerShow == 'boolean' ? chapter.footerShow : this.footerShow//判断是否显示底部
const style = {fontSize: this.fontSize, fontFamily: this.fontFamily, lineHeight: this.lineHeight, totalChapter: this.totalChapter}
this.$nextTick(function () {
setTimeout(() => {
this.$refs.webview && this.$refs.webview.evalJS("start(" + encodeURIComponent(JSON.stringify(chapter)) + ', ' + encodeURIComponent(JSON.stringify(style)) + ")")
}, 100)
})
},
onPostMessage (e) {
e.detail.data.forEach(item => {
if ( item.handleSuccess ) {
this.success && this.success(item.handleSuccess)
this.success = null
this.fail = null
this.chapter = {}
}
})
}
}
}
</script>
<style scoped>
.yingbing-computed {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transform: translateX(-1000px);
}
</style>
\ No newline at end of file
<template>
<web-view :src="'/uni_modules/yingbing-ReadPage/hybrid/html/content.html?content=' + encodeURIComponent(contentSync)"></web-view>
</template>
<script>
export default {
inject: ['color', 'fontSize', 'fontFamily', 'lineHeight', 'selectable'],
props: {
item: {
type: Object,
default () {
return new Object
}
}
},
computed: {
contentSync () {
let content = this.item.content || ''
content = content.replace('<whole-render', '<div').replace('<\/whole-render>', '<\/div>')
return `<div class="yingbing-reader-content-html ${this.selectable ? 'user-selectable' : 'user-selectclose'}" style="box-sizing: border-box;color:${this.color};font-family:${this.fontFamily};font-size:${this.fontSize}px;line-height:${this.lineHeight}px;">` + content + '</div>'
}
}
}
</script>
<style>
</style>
\ No newline at end of file
export default {
methods: {
//判断是否显示头部
getFlipHeaderShow (item) {
return ['text', 'slot'].includes(item.type) ? typeof item.headerShow == 'boolean' ? item.headerShow : this.headerShow : false
},
//判断是否显示底部
getFlipFooterShow (item) {
return ['text', 'slot'].includes(item.type) ? typeof item.footerShow == 'boolean' ? item.footerShow : this.footerShow : false
},
//渲染页面
flipRender ({chapter, current, start = 0}) {
if ( chapter ) {//如果传入章节内容
const index = this.chapters.findIndex(c => c.index == chapter.index)//是否已经包含相同章节
if (index > -1) this.chapters[index] = chapter//如果包含则更新
else this.chapters.push(chapter)//否则添加新章节
}
current = parseInt(current || 0)//强制转换类型为int
start = parseInt(start)//强制转换类型为int
this.pages = [{index: current, type: 'loading'}]//显示loading
this.current = 0//重置current
this.$refs.flip.refresh()
const cgs = this.chapters.filter(c => c.index == current || c.index == current - 1 || c.index == current + 1)//筛选出符合条件的三个章节内容
let index = 0, arr = []
const computedId = this.getComputedId()
this.computeds.push(computedId)
const computedIndex = this.computeds.indexOf(computedId)
this.$nextTick(function () {
setTimeout(() => {
const handle = () => {
this.$refs.computed[computedIndex] && this.$refs.computed[computedIndex].start({
chapter: cgs[index],
success: pages => {
arr = arr.concat(pages)
if ( index < cgs.length - 1 ) {
index++
this.$nextTick(function () { handle() })
} else {
this.computeds.splice(computedIndex, 1)
this.pages = this.handlePages(arr)
const pageIndex = this.pages.findIndex(p => start >= p.start && start < p.end && p.index == current )//定位章节
if ( pageIndex == -1 ) this.current = this.pages.findIndex(p => start >= p.end && p.index == current )//定位章节
else this.current = pageIndex
this.$nextTick(function () {
this.$refs.flip.refresh()
this.handleChange({current: this.current, detail: this.pages[this.current] })
this.autoplaySync = this.autoplay
})
}
}
})
}
handle()
}, 100)
})
},
//渲染下一章节
handleFlipLoadRender (status, chapter, callback, isPrev) {
if (status == 'success') {//获取内容成功
const nowIndex = isPrev ? this.pages[0].index : this.pages[this.pages.length-1].index
const chapterIndex = this.chapters.findIndex(c => c.index == chapter.index)//是否已经包含相同章节
if (chapterIndex > -1) this.chapters[chapterIndex] = chapter//如果包含则更新
else this.chapters.push(chapter)//否则添加新章节
const computedId = this.getComputedId()
this.computeds.push(computedId)
const computedIndex = this.computeds.indexOf(computedId)
this.$nextTick(function () {
setTimeout(() => {
this.$refs.computed[computedIndex] && this.$refs.computed[computedIndex].start({
chapter: chapter,
success: p => {
this.computeds.splice(computedIndex, 1)
const pages = isPrev ? p.concat(this.pages) : this.pages.concat(p)//添加新计算的章节内容
this.pages = pages//渲染章节内容
if ( isPrev ) {//如果是加载上一章节需要重新定位
this.current = pages.findIndex(page => page.index == nowIndex)//定位页面
this.$refs.flip.refresh()//刷新翻页组件
}
this.chapterLoading = false//关闭章节加载等待
callback && callback(status)//关闭加载动画
this.handleChange({current: this.current, detail: this.pages[this.current]})//触发change事件
}
})
}, 100)
})
} else {
this.chapterLoading = false//关闭章节加载等待
callback && callback(status)//关闭加载动画
}
}
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
## 1.0.8(2024-03-29)
* 修复app-vue初始化报错,并且无法翻页的问题
## 1.0.7(2024-03-29)
* 恢复微信小程序使用插槽的方式
* 保留微信小程序引入子组件的方式
## 1.0.6(2024-03-06)
* 添加vue3兼容
* 修改微信小程序使用方式(为了提高性能以及兼容vue3)如果使用过该组件编写微信小程序的开发者,没有什么问题可以不用更新,避免出错
## 1.0.5(2023-11-13)
* 增加pc端兼容
## 1.0.4(2023-09-02)
* 解决插槽中有scroll-view等滚动组件时滑动报错的问题
## 1.0.3(2023-08-23)
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment