This commit is contained in:
lipnggao 2023-09-20 16:58:34 +08:00
parent b0b8965b28
commit 2c18c9ec0d
115 changed files with 59434 additions and 179 deletions

16
.hbuilderx/launch.json Normal file
View File

@ -0,0 +1,16 @@
{ // launch.json configurations app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"app-plus" :
{
"launchtype" : "local"
},
"default" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

View File

@ -7,6 +7,14 @@
"transformPx" : false, "transformPx" : false,
/* 5+App */ /* 5+App */
"app-plus" : { "app-plus" : {
"safearea" : {
//ioS
"background" : "#F5F6F9", //"#FFFFFF"
"bottom" : {
//
"offset" : "none|auto" // "none""auto""none"
}
},
"usingComponents" : true, "usingComponents" : true,
"nvueStyleCompiler" : "uni-app", "nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3, "compilerVersion" : 3,

View File

@ -0,0 +1,980 @@
<template>
<view class="novelReading_content">
<u-navbar :title="navbarTitle" @rightClick="rightClick" :autoBack="true" :fixed="true" :bgColor="mainBodyBg"
:titleStyle="{ color: navigationBarTitleTextColor }" :leftIconColor="navigationBarTitleTextBackColor"
:shadow="true" :safeAreaInsetTop="true" :placeholder="true" />
<view class="novelReading_body" :style="`background:${bodyReadingBg}`" @tap="handelShowStepUp">
<view class="novelReading_main_con">
<!-- :style="`height:${bodyReadingHeight}px;`" -->
<!-- :refresher-enabled="true" upper-threshold="200" @refresherrefresh="refresherrefresh" :scroll-top="isScrollTop" -->
<view class="novelReading_main">
<!-- @refresherpulling="refresherpulling" -->
<!-- :scroll-top="isScrollTop" :scroll-into-view="`richText_${readDirectoryActive}`" @scrolltoupper="charactersToupper"-->
<scroll-view scroll-y="true" :scroll-top="isScrollTop" @scrolltolower="charactersLower"
:refresher-enabled="true" @scroll="charactersScroll" @refresherrefresh="refresherrefresh"
class="novelReading_characters_scroll" lower-threshold="10"
:refresher-triggered="refresherTriggered">
<view class="novelReading_characters_main"
:style="`color:${novelContentColor};font-size:${newCharactersSize}rpx;line-height:${defaultCharactersLineHeight};`"
v-for="m in novelReadingContentText" :key="m.id" :data-chapterorder="m.chapterorder"
:data-lastid="m.lastid" :data-nextid="m.nextid">
<!-- <rich-text :nodes="m.content" :ref="`richText_${m.id}`"></rich-text> -->
<!-- <rich-text :nodes="`<h3>${m.chaptername}</h3></br>${m.novel_content}`"
:ref="`richText_${m.id}`"></rich-text> -->
<rich-text :nodes="`${m.richTextNodes}`" :ref="`richText_${m.id}`"></rich-text>
<!-- recharge_empty -->
</view>
<!-- <view v-if="readChapterInfoObj.chackpay == 2" class="read_chapter">
<view class=""
:style="`color:${novelContentColor};font-size:${newCharactersSize}rpx;line-height:${defaultCharactersLineHeight};`">
<h3>{{readChapterInfoObj.chaptername}}</h3>
</view>
<u-empty icon="/static/images/recharge_empty.png" text="余额不足,请充值..." />
</view> -->
</scroll-view>
</view>
<!-- <view class="reading_schedule_box" :style="`background:${bodyReadingBg}`">
<view class="reading_schedule_body">
<view class="_previous_chapter"
:style="`color:${previousChapterBbuttonTextColor};background:${previousChapterBbuttonBg}`"
@tap="previousChapter">
上一章
</view>
<view class="_next_chapter"
:style="`color:${nextChapterBbuttonTextColor};background:${nextChapterBbuttonBg}`"
@tap="nextChapter">下一章
</view>
</view>
</view> -->
</view>
<view id="compute_rich_text"
:style="`width:${computeRichTextWidth}px;color:${novelContentColor};font-size:${newCharactersSize}rpx;line-height:${defaultCharactersLineHeight}`">
<rich-text :nodes="computeRichText"></rich-text>
</view>
</view>
<view class="u_popup_all">
<u-popup :show="tabBarPopupShow" mode="bottom" :overlay="false" zIndex="6" :bgColor="bodyReadingBg">
<view class="my_tabBar_Reading" :style="`background:${bodyReadingBg}`">
<view class="tabBar_Reading_item">
<view class="reading_item_icon" @tap="handelDirectoryPopup">
<image class="is_images" :src="barPopupIcon[novelMainTypeColor].directory_icon"></image>
</view>
<view class="reading_item_name" :style="`color:${tabBarTextColor}`">目录</view>
</view>
<view class="tabBar_Reading_item">
<view class="reading_item_icon" @tap="toBookshelf">
<image class="is_images" :src="barPopupIcon[novelMainTypeColor].bookshelf_icon"></image>
</view>
<view class="reading_item_name" :style="`color:${tabBarTextColor}`">书城</view>
</view>
<view class="tabBar_Reading_item" @tap="handelSteUpPopup">
<view class="reading_item_icon">
<image class="is_images" :src="barPopupIcon[novelMainTypeColor].step_up_icon"></image>
</view>
<view class="reading_item_name" :style="`color:${tabBarTextColor}`">设置</view>
</view>
</view>
</u-popup>
<u-popup :show="readingPopupshow" mode="bottom" :overlay="false" zIndex="4" bgColor="transparent">
<view class="reading_schedule_box" :style="`background:${bodyReadingBg}`">
<view class="reading_schedule_body">
<view class="_previous_chapter"
:style="`color:${previousChapterBbuttonTextColor};background:${previousChapterBbuttonBg}`"
@tap="previousChapter">
上一章
</view>
<view class="_next_chapter"
:style="`color:${nextChapterBbuttonTextColor};background:${nextChapterBbuttonBg}`"
@tap="nextChapter">下一章
</view>
</view>
<view style="height: 140rpx" />
</view>
</u-popup>
<u-popup :show="stepUpPopupShow" mode="bottom" :overlay="false" bgColor="transparent" zIndex="5">
<view class="step_up_box" :style="`background:${bodyReadingBg}`">
<view class="step_up_box_body">
<view class="step_up_item">
<view class="step_up_item_name" :style="`color:${dialogTextColor}`">背景</view>
<view :class="[`step_up_item_bg`, novelMainTypeColor == 'F3EFE9' ? 'activeBlack' : '']"
style="background: #f3efe9" @tap="changNovelMainType('F3EFE9')"></view>
<view :class="[`step_up_item_bg`, novelMainTypeColor == 'CCD9E2' ? 'activeBlack' : '']"
style="background: #ccd9e2" @tap="changNovelMainType('CCD9E2')"></view>
<view :class="[`step_up_item_bg`, novelMainTypeColor == '333333' ? 'activeWhite' : '']"
@tap="changNovelMainType('333333')">
<image class="is_images" :src="setUpModeBlack" />
</view>
</view>
<view class="step_up_item mt40rpx">
<view class="step_up_item_name" :style="`color:${dialogTextColor}`">字号</view>
<view class="step_up_item_text_bg"
:style="`color:${dialogTextColor};background:${dialogATextBg}`"
@tap="reduceCharactersSize">
A-</view>
<view class="step_up_item_text_bg"
:style="`color:${dialogTextColor};background:${dialogATextBg}`"
@tap="addCharactersSize">A+
</view>
<view class="step_up_item_text_bg"
:style="`color:${dialogTextColor};background:${dialogATextBg}`"
@tap="handelCharactersSize">
默认</view>
</view>
</view>
<view style="height: 140rpx" />
</view>
</u-popup>
<u-popup :show="directoryPopupShow" @close="directoryPopupClose" mode="right" :safeAreaInsetTop="true">
<view class="directory_popup_box">
<scroll-view scroll-y="true" @scrolltoupper="directoryPopupUpper"
@scrolltolower="directoryPopupLower" :show-scrollbar="false" class="directory_scroll_y">
<view class="_popup_box_item" v-for="m in directoryList" :key="m.id" @tap="handelDirectoryItem">
<view :class="['_item_name', readDirectoryActive == m.chapterorder ? 'active' : '']">
{{ m.chaptername }}
</view>
<view v-if="m.isvip" class="_item_chapter_lock">
<image class="is_images" src="/static/images/chapter_lock.png"></image>
</view>
</view>
</scroll-view>
</view>
</u-popup>
<u-popup :show="purchaseFullShow" @close="purchaseFullClose" mode="bottom" overlayStyle="top:44px">
<view class="purchaseFull_popup_box" :style="`background:${bodyReadingBg}`">
<view>
<CommBookLeftRigth :bookTips="bookInfo.category_name" :bookName="bookInfo.title"
:bookImage="bookInfo.cover" :bookIntroduction="`${readChapterInfoObj.allprice}书币`" />
</view>
<view class="purchaseFull_popup_btn_box">
<view class="purchaseFull_popup_btn" @tap="handelPurchaseFull">需要全本购买</view>
</view>
</view>
</u-popup>
<u-modal :show="balanceShow" :title="balanceTitle" content='余额不足,请充值...'
:showCancelButton="!readChapterFlag" @confirm="balanceConfirm" @cancel="balanceCancel" />
</view>
</view>
</template>
<script>
import setUpReadingColorAll from './setUpReadingColorAll.js';
import bookshelf_333 from '@/static/images/novelReading/bookshelf_333.png';
import directory_333 from '@/static/images/novelReading/directory_333.png';
import step_up_333 from '@/static/images/novelReading/step_up_333.png';
import bookshelf_ccd9e2 from '@/static/images/novelReading/bookshelf_ccd9e2.png';
import step_up_ccd9e2 from '@/static/images/novelReading/step_up_ccd9e2.png';
import directory_ccd9e2 from '@/static/images/novelReading/directory_ccd9e2.png';
import bookshelf_f3efe from '@/static/images/novelReading/bookshelf_f3efe.png';
import directory_f3efe9 from '@/static/images/novelReading/directory_f3efe9.png';
import step_up_f3efe from '@/static/images/novelReading/step_up_f3efe.png';
import set_up_mode_black from '@/static/images/novelReading/set_up_mode_black.png';
import recharge_empty from '@/static/images/recharge_empty.png'
import config from '@/config/index';
import {
isGetSystemInfo
} from '@/utils/systemInfo.js';
import {
myGetStorage,
mySetStorage
} from '@/utils/storage/index.js';
import myData from './data.js';
import CommBookLeftRigth from '@/components/commBookLeftRigth/index.vue'
export default {
components: {
CommBookLeftRigth
},
data() {
return {
navbarTitle: '小说阅读页面',
novelMainTypeColor: '',
setUpColorAll: {},
navigationBarTitleTextColor: '',
mainBodyBg: '',
navigationBarTitleTextBackColor: '',
previousChapterBbuttonBg: '',
previousChapterBbuttonTextColor: '',
nextChapterBbuttonBg: '',
nextChapterBbuttonTextColor: '',
bodyReadingHeight: 0,
scrollReadingHeight: 0,
//
bodyReadingBg: '',
//
// bottomSecureHeight: 0,
tabBarPopupShow: false,
readingPopupshow: false,
stepUpPopupShow: false,
purchaseFullShow: false,
balanceShow: false,
balanceTitle: '下一章,付费章节',
// bar icon
barPopupIcon: {},
dialogTextColor: '',
progressBg: '',
progressActiveBg: '',
setUpModeBlack: '',
dialogATextBg: '',
novelContentColor: '',
//
booksDirectorySid: '2387',
bookInfo: {},
booksDirectoryPage: 1,
newBooksDirectoryPage: [],
directoryList: [],
// directoryPopupHeight: 0,
directoryPopupShow: false,
//
novelReadingContentText: [],
defaultCharactersSize: 46,
newCharactersSize: 46,
defaultCharactersLineHeight: 2,
readDirectoryActive: '',
myData: [],
computeRichText: '',
computeRichTextWidth: 0,
isScrollTop: 0,
refresherTriggered: false,
readChapterid: '',
readChapterFlag: false,
readChapterInfoObj: {},
readChapterLastid: '',
readChapterNextid: '',
refresherEnabled: true,
};
},
onLoad(options) {
this.booksDirectorySid = options.sid;
this.navbarTitle = options.n;
this.readChapterid = options.id;
},
onShow() {
// const booksDirectoryPage = this.booksDirectoryPage;
// const newBooksDirectoryPage = [...this.newBooksDirectoryPage, booksDirectoryPage];
// this.newBooksDirectoryPage = newBooksDirectoryPage;
const bookSid = this.booksDirectorySid;
const data = {
sid: bookSid,
}
uni.$u.http.post('/bookdetails', data).then((res) => {
uni.hideLoading();
if (res.status == 1) {
const info = res.data.info;
const cover = info.cover.includes('http') ? info.cover : `${config.baseUrl}${info.cover}`;
this.bookInfo = {
...info,
cover
};
}
}).catch((err) => {
uni.hideLoading();
})
this.isGetUserRead();
},
methods: {
async isGetUserRead() {
const booksDirectorySid = this.booksDirectorySid;
const booksDirectoryPage = this.booksDirectoryPage;
const readChapterid = this.readChapterid ? this.readChapterid : '';
// this.readDirectoryActive = '3';
await this.isGetDirectory(booksDirectorySid, booksDirectoryPage);
await this.isGetBookInfo(readChapterid, '', true);
},
isGetBookInfo(chapterId, statusType, isShowLoading = false) {
const booksDirectorySid = this.booksDirectorySid;
const novelReadingContentText = this.novelReadingContentText;
if (isShowLoading) {
uni.showLoading({
title: '加载中...'
});
}
const data = {
sid: booksDirectorySid,
};
if (chapterId) {
data.id = chapterId;
}
uni.$u.http.post('/read', data).then((res) => {
uni.hideLoading();
if (res.status == 1) {
const readChapterInfoObj = {
...res.data,
richTextNodes: `<h3>${res.data.chaptername}</h3></br>${res.data.novel_content}`
};
this.readChapterInfoObj = readChapterInfoObj;
if (readChapterInfoObj.chackpay == 1) {
this.isScrollTop = 0.01;
if (statusType == 'refresherrefresh') {
this.novelReadingContentText = [readChapterInfoObj, ...novelReadingContentText];
this.computeRichText = readChapterInfoObj.richTextNodes;
const query = uni.createSelectorQuery().in(this);
this.$nextTick(() => {
query.select(`#compute_rich_text`).boundingClientRect((data) => {
this.isScrollTop = parseInt(data.height) - 20;
}).exec();
});
this.refresherTriggered = false;
} else if (statusType == 'charactersLower') {
this.novelReadingContentText = [...novelReadingContentText, readChapterInfoObj];
} else if (statusType == 'nextChapter') {
this.novelReadingContentText = [readChapterInfoObj];
this.readDirectoryActive = readChapterInfoObj.chapterorder;
this.readChapterLastid = readChapterInfoObj.lastid;
this.readChapterNextid = readChapterInfoObj.nextid;
this.$nextTick(() => {
this.isScrollTop = 0;
})
} else if (statusType == 'previousChapter') {
this.novelReadingContentText = [readChapterInfoObj];
this.readDirectoryActive = readChapterInfoObj.chapterorder;
this.readChapterLastid = readChapterInfoObj.lastid;
this.readChapterNextid = readChapterInfoObj.nextid;
this.$nextTick(() => {
this.isScrollTop = 0;
})
} else {
this.novelReadingContentText = [readChapterInfoObj];
this.readDirectoryActive = readChapterInfoObj.chapterorder;
this.readChapterLastid = readChapterInfoObj.lastid;
this.readChapterNextid = readChapterInfoObj.nextid;
}
} else if (readChapterInfoObj.chackpay == 2) {
this.balanceShow = true;
this.balanceTitle = '下一章,付费章节';
if (isShowLoading) {
this.readChapterFlag = true;
}
} else if (readChapterInfoObj.chackpay == 3) {
this.purchaseFullShow = true;
if (isShowLoading) {
this.readChapterFlag = true;
}
}
}
}).catch((err) => {
uni.hideLoading();
console.log(err, '========');
});
},
refresherrefresh() {
const readChapterInfoObj = this.readChapterInfoObj;
const readChapterLastid = this.readChapterLastid;
this.refresherTriggered = true;
if (!readChapterLastid) {
setTimeout(() => {
uni.showToast({
icon: 'none',
title: "已经是第一章了"
})
this.refresherTriggered = false;
}, 1000)
return
}
this.isGetBookInfo(readChapterLastid, 'refresherrefresh');
},
charactersLower() {
const readChapterNextid = this.readChapterNextid;
// if (readChapterInfoObj.chackpay != 1) {
// return
// }
if (!readChapterNextid) {
uni.showToast({
icon: 'none',
title: "已经是最后一章了~"
})
return
}
this.isGetBookInfo(readChapterNextid, 'charactersLower');
},
charactersScroll(e) {
const bodyReadingHeight = this.bodyReadingHeight;
const scrollTop = e.detail.scrollTop;
// this.isScrollTop = scrollTop;
uni.createSelectorQuery().selectAll('.novelReading_characters_main').boundingClientRect((data) => {
data.forEach((m) => {
if (m.top < bodyReadingHeight) {
this.readDirectoryActive = m.dataset.chapterorder;
this.readChapterLastid = m.dataset.lastid;
this.readChapterNextid = m.dataset.nextid;
}
});
}).exec();
},
previousChapter() {
const readChapterLastid = this.readChapterLastid
if (!readChapterLastid) {
uni.showToast({
icon: 'none',
title: "已经是第一章了"
})
return
}
this.isGetBookInfo(readChapterLastid, 'previousChapter');
},
nextChapter() {
const readChapterNextid = this.readChapterNextid;
if (!readChapterNextid) {
uni.showToast({
icon: 'none',
title: "已经是最后一章了~"
})
return
}
this.isGetBookInfo(readChapterNextid, 'nextChapter');
},
handelDirectoryItem(row) {},
handelShowStepUp() {
this.tabBarPopupShow = !this.tabBarPopupShow;
this.readingPopupshow = !this.readingPopupshow;
this.stepUpPopupShow = false;
},
handelDirectoryPopup() {
// this.directoryPopupShow = true;
const readChapterInfoObj = this.readChapterInfoObj;
const readDirectoryActive = this.readDirectoryActive;
uni.navigateTo({
url: `/pages/bookRecommendList/bookRecommendList?sid=${readChapterInfoObj.sid}&t=${readChapterInfoObj.title}&c=${readDirectoryActive}`
})
},
directoryPopupClose() {
// this.directoryPopupShow = false;
// const readChapterInfoObj = this.readChapterInfoObj;
// uni.navigateTo({
// url: `/pages/bookRecommendList/bookRecommendList?sid=${readChapterInfoObj.sid}&t=${readChapterInfoObj.title}&c=${readChapterInfoObj.chapterorder}`
// })
},
purchaseFullClose() {
const readChapterFlag = this.readChapterFlag;
if (readChapterFlag) {
return;
}
this.purchaseFullShow = false;
},
handelSteUpPopup() {
this.stepUpPopupShow = true;
},
balanceConfirm() {
uni.navigateTo({
url: `/pages/voucherCenter/index`
})
},
balanceCancel() {
this.balanceShow = false;
},
handelPurchaseFull() {
const readChapterInfoObj = this.readChapterInfoObj;
uni.showLoading({
title: '加载中...'
});
const data = {
sid: readChapterInfoObj.sid,
id: readChapterInfoObj.id,
allprice: readChapterInfoObj.allprice,
};
uni.$u.http.post('/buyall', data).then((res) => {
uni.hideLoading();
if (res.status == 1) {
if (res.data.status == 2) {
this.balanceShow = true;
this.balanceTitle = '';
}
}
}).catch((err) => {
uni.hideLoading();
});
},
//
changNovelMainType(type) {
this.novelMainTypeColor = type;
this.initPage(type);
const novelMainObj = myGetStorage('novelMainObj') || '{}';
const obj = {
...JSON.parse(novelMainObj),
novelMainTypeColor: type,
}
mySetStorage('novelMainObj', JSON.stringify(obj));
},
//
reduceCharactersSize() {
const newCharactersSize = this.newCharactersSize;
this.newCharactersSize = newCharactersSize - 2;
const novelMainObj = myGetStorage('novelMainObj') || '{}';
const obj = {
...JSON.parse(novelMainObj),
charactersSize: newCharactersSize - 2,
}
mySetStorage('novelMainObj', JSON.stringify(obj));
},
//
addCharactersSize() {
const newCharactersSize = this.newCharactersSize;
this.newCharactersSize = newCharactersSize + 2;
const novelMainObj = myGetStorage('novelMainObj') || '{}';
const obj = {
...JSON.parse(novelMainObj),
charactersSize: newCharactersSize + 2,
}
mySetStorage('novelMainObj', JSON.stringify(obj));
},
//
handelCharactersSize() {
const defaultCharactersSize = this.defaultCharactersSize;
this.newCharactersSize = defaultCharactersSize;
const novelMainObj = myGetStorage('novelMainObj') || '{}';
const obj = {
...JSON.parse(novelMainObj),
charactersSize: defaultCharactersSize,
}
mySetStorage('novelMainObj', JSON.stringify(obj));
},
//
directoryPopupUpper() {
const isBooksDirectoryPage = this.booksDirectoryPage;
const newBooksDirectoryPage = this.newBooksDirectoryPage;
if (isBooksDirectoryPage > 1 && newBooksDirectoryPage.indexOf(isBooksDirectoryPage) == -1) {
const booksDirectorySid = this.booksDirectorySid;
const booksDirectoryPage = isBooksDirectoryPage - 1;
this.booksDirectoryPage = booksDirectoryPage;
this.isGetDirectory(booksDirectorySid, booksDirectoryPage, 'upper');
}
},
//
directoryPopupLower() {
const isBooksDirectoryPage = this.booksDirectoryPage;
const booksDirectorySid = this.booksDirectorySid;
const booksDirectoryPage = isBooksDirectoryPage + 1;
const newBooksDirectoryPage = [...this.newBooksDirectoryPage, booksDirectoryPage];
this.newBooksDirectoryPage = newBooksDirectoryPage;
this.booksDirectoryPage = booksDirectoryPage;
this.isGetDirectory(booksDirectorySid, booksDirectoryPage, 'lower');
},
isGetDirectory(sid, page, type) {
uni.showLoading({
title: '加载中...'
});
const data = {
sid,
page
};
uni.$u.http.post('/getDirectory', data).then((res) => {
uni.hideLoading();
if (res.status == 1) {
const directory = res.data.directory;
let columnsLabel = '';
if (type == 'upper') {
this.directoryList = [...directory, ...this.directoryList];
} else if (type == 'lower') {
this.directoryList = [...this.directoryList, ...directory];
} else {
this.directoryList = directory;
}
}
}).catch((err) => {
uni.hideLoading();
console.log(err, '========');
});
},
initPage(novelMainTypeColor) {
const mainBodyBg = setUpReadingColorAll[novelMainTypeColor].mainBodyBg;
this.navigationBarTitleTextColor = setUpReadingColorAll[novelMainTypeColor].navigationBarTitleTextColor;
this.mainBodyBg = mainBodyBg;
this.bodyReadingBg = mainBodyBg;
this.dialogTextColor = setUpReadingColorAll[novelMainTypeColor].dialogTextColor;
this.progressBg = setUpReadingColorAll[novelMainTypeColor].progressBg;
this.progressActiveBg = setUpReadingColorAll[novelMainTypeColor].progressActiveBg;
this.setUpModeBlack = set_up_mode_black;
this.dialogATextBg = setUpReadingColorAll[novelMainTypeColor].dialogATextBg;
this.tabBarTextColor = setUpReadingColorAll[novelMainTypeColor].tabBarTextColor;
this.novelContentColor = setUpReadingColorAll[novelMainTypeColor].novelContentColor;
this.navigationBarTitleTextBackColor = setUpReadingColorAll[novelMainTypeColor]
.navigationBarTitleTextBackColor;
this.previousChapterBbuttonBg = setUpReadingColorAll[novelMainTypeColor].previousChapterBbuttonBg;
this.previousChapterBbuttonTextColor = setUpReadingColorAll[novelMainTypeColor]
.previousChapterBbuttonTextColor;
this.nextChapterBbuttonBg = setUpReadingColorAll[novelMainTypeColor].nextChapterBbuttonBg;
this.nextChapterBbuttonTextColor = setUpReadingColorAll[novelMainTypeColor].nextChapterBbuttonTextColor;
},
rightClick() {
uni.navigateBack();
},
toBookshelf() {
uni.reLaunch({
url: `/pages/bookCity/bookCity/index`
})
}
},
onPullDownRefresh() {
// const isReadDirectoryActive = this.readDirectoryActive;
// if(isReadDirectoryActive == 0) {
// uni.showToast({
// icon:'none',
// title:""
// })
// uni.stopPullDownRefresh()
// return
// }
// const readDirectoryActive = isReadDirectoryActive- 1;
// const novelReadingContentText = this.myData[readDirectoryActive];
// this.novelReadingContentText = [novelReadingContentText, ...this.novelReadingContentText];
// // setTimeout(() => {
// this.computeRichText = this.myData[readDirectoryActive].content;
// // this.readDirectoryActive = readDirectoryActive;
// const query = uni.createSelectorQuery().in(this);
// this.$nextTick(() => {
// query.select(`#compute_rich_text`).boundingClientRect((data) => {
// this.isScrollTop = parseInt(data.height) - 30;
// }).exec();
// });
// uni.stopPullDownRefresh()
},
created() {
const novelMainObj = myGetStorage('novelMainObj') || '{}';
const novelMainTypeColor = JSON.parse(novelMainObj).novelMainTypeColor || 'F3EFE9';
const {
screenHeight = 0, statusBarHeight = 0, windowHeight = 0, devicePixelRatio, windowBottom, windowWidth,
screenWidth
} = isGetSystemInfo();
// this.myData = myData;
this.computeRichTextWidth = windowWidth;
this.novelMainTypeColor = novelMainTypeColor;
this.newCharactersSize = JSON.parse(novelMainObj).charactersSize || 46;
this.bodyReadingHeight = screenHeight - statusBarHeight - devicePixelRatio * 22;
//
// this.scrollReadingHeight = screenHeight - statusBarHeight - devicePixelRatio * 38;
// this.scrollReadingHeight = windowHeight - (screenWidth / 375) * 54;
// // #ifdef APP-PLUS
// this.directoryPopupHeight = screenHeight - statusBarHeight;
// // #endif
// // #ifdef H5 || MP-WEIXIN
// this.directoryPopupHeight = screenHeight - statusBarHeight - devicePixelRatio * 22;
// // #endif
// this.bottomSecureHeight = screenHeight - windowHeight;
this.setUpColorAll = setUpReadingColorAll;
this.barPopupIcon = {
'F3EFE9': {
bookshelf_icon: bookshelf_f3efe,
directory_icon: directory_f3efe9,
step_up_icon: step_up_f3efe
},
'CCD9E2': {
bookshelf_icon: bookshelf_ccd9e2,
directory_icon: directory_ccd9e2,
step_up_icon: step_up_ccd9e2
},
'333333': {
bookshelf_icon: bookshelf_333,
directory_icon: directory_333,
step_up_icon: step_up_333
}
};
this.initPage(novelMainTypeColor);
},
};
</script>
<style lang="scss" scoped>
page {
width: 100%;
height: 100%;
}
.is_images {
display: block;
width: 100%;
height: 100%;
}
.mt40rpx {
margin-top: 40rpx;
}
.novelReading_content::v-deep.u-navbar__content__title {
font-size: 30rpx;
}
.novelReading_content::v-deep.u-navbar--fixed {
box-shadow: 0 0 8rpx rgba(0, 0, 0, 0.2);
}
.novelReading_content::v-deep.z-paging-content {
position: relative;
}
#compute_rich_text {
position: fixed;
top: 1500px;
padding: 32rpx;
box-sizing: border-box;
}
.novelReading_content {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
.novelReading_body {
flex: 1;
width: 100%;
.novelReading_main_con {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 32rpx 32rpx;
padding-bottom: constant(safe-area-inset-bottom);
/* 兼容 iOS 设备 */
padding-bottom: env(safe-area-inset-bottom);
/* 兼容 iPhone X 及以上设备 */
box-sizing: border-box;
.novelReading_main {
flex: 1;
width: 100%;
height: 100%;
box-sizing: border-box;
position: relative;
.novelReading_characters_scroll {
width: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
.read_chapter {
padding: 40rpx 0;
}
.novelReading_characters_main {
width: 100%;
}
}
}
.reading_schedule_box {
width: 100%;
height: 82rpx;
// padding: 40rpx;
// box-shadow: 0 0 8rpx rgba(0, 0, 0, 0.2);
border-radius: 40rpx;
box-sizing: border-box;
}
}
}
.u_popup_all {
flex: 0;
}
}
.my_tabBar_Reading {
width: 100%;
height: 140rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 40rpx;
box-sizing: border-box;
border-top: 1rpx solid #d6d2ce;
// padding-bottom: constant(safe-area-inset-bottom); /* iOS */
// padding-bottom: env(safe-area-inset-bottom); /* iPhone X */
.tabBar_Reading_item {
display: flex;
flex-direction: column;
align-items: center;
.reading_item_icon {
width: 34rpx;
height: 34rpx;
}
.reading_item_name {
font-size: 30rpx;
line-height: 1;
margin-top: 10rpx;
}
}
}
.reading_schedule_box {
width: 100%;
box-shadow: 0 0 8rpx rgba(0, 0, 0, 0.2);
border-radius: 40rpx;
.reading_schedule_body {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 40rpx;
box-sizing: border-box;
background: transparent;
._previous_chapter {
display: flex;
justify-content: center;
align-items: center;
width: 192rpx;
height: 82rpx;
font-size: 34rpx;
line-height: 1;
border-radius: 12rpx;
}
._next_chapter {
display: flex;
justify-content: center;
align-items: center;
width: 442rpx;
height: 82rpx;
font-size: 34rpx;
line-height: 1;
border-radius: 12rpx;
}
}
}
.step_up_box {
width: 100%;
box-shadow: 0 0 8rpx rgba(0, 0, 0, 0.2);
border-radius: 40rpx;
.step_up_box_body {
width: 100%;
padding: 40rpx 24rpx;
box-sizing: border-box;
background: transparent;
.step_up_item {
display: flex;
justify-content: space-between;
align-items: center;
.step_up_item_name {
line-height: 1;
font-size: 30rpx;
}
.step_up_item_bg {
display: flex;
justify-content: center;
align-items: center;
width: 188rpx;
height: 74rpx;
border-radius: 37rpx;
box-sizing: border-box;
border: 2rpx solid transparent;
}
.step_up_item_text_bg {
display: flex;
justify-content: center;
align-items: center;
width: 188rpx;
height: 74rpx;
border-radius: 37rpx;
box-sizing: border-box;
}
.step_up_item_bg.activeBlack {
border-color: #1a1a1a;
}
.step_up_item_bg.activeWhite {
border-color: #fff;
}
}
}
}
.reading_schedule_slider::v-deep.uni-slider-handle-wrapper {
height: 60rpx;
border-radius: 30rpx;
.uni-slider-track {
border-radius: 30rpx;
}
}
.directory_popup_box {
position: relative;
width: 560rpx;
height: 100%;
box-sizing: border-box;
.directory_scroll_y {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding-left: 32rpx;
padding-right: 32rpx;
box-sizing: border-box;
padding-bottom: constant(safe-area-inset-bottom);
/* 兼容 iOS 设备 */
padding-bottom: env(safe-area-inset-bottom);
/* 兼容 iPhone X 及以上设备 */
// padding-top: constant(safe-area-inset-top);
// /* iOS */
// padding-top: env(safe-area-inset-top);
// /* iPhone X */
._popup_box_item {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 120rpx;
border-bottom: 1rpx solid #f2f2f2;
._item_name {
font-size: 32rpx;
color: #333333;
line-height: 1;
}
._item_name.active {
color: #ff728f;
}
._item_chapter_lock {
width: 32rpx;
height: 32rpx;
}
}
}
}
.purchaseFull_popup_box {
width: 100%;
box-sizing: border-box;
padding: 50rpx 32rpx;
.purchaseFull_popup_btn_box {
display: flex;
justify-content: center;
margin-top: 50rpx;
.purchaseFull_popup_btn {
display: flex;
justify-content: center;
align-items: center;
width: 640rpx;
height: 84rpx;
font-size: 30rpx;
color: #fff;
background: linear-gradient(to top, #FBA676, #E95E32);
border-radius: 24rpx;
}
}
}
</style>

View File

@ -1,55 +1,19 @@
<template> <template>
<view class="novelReading_content"> <view class="novelReading_content">
<u-navbar :title="navbarTitle" @rightClick="rightClick" :autoBack="true" :fixed="true" :bgColor="mainBodyBg" <!-- <u-navbar :title="navbarTitle" @rightClick="rightClick" :autoBack="true" :fixed="true" :bgColor="mainBodyBg"
:titleStyle="{ color: navigationBarTitleTextColor }" :leftIconColor="navigationBarTitleTextBackColor" :titleStyle="{ color: navigationBarTitleTextColor }" :leftIconColor="navigationBarTitleTextBackColor"
:shadow="true" :safeAreaInsetTop="true" :placeholder="true" /> :shadow="true" :safeAreaInsetTop="true" :placeholder="true" /> -->
<view class="novelReading_body" :style="`background:${bodyReadingBg}`" @tap="handelShowStepUp"> <!-- @tap="handelShowStepUp" -->
<view class="novelReading_body" :style="`background:${bodyReadingBg}`">
<!-- -->
<view class="novelReading_main_con"> <view class="novelReading_main_con">
<!-- :style="`height:${bodyReadingHeight}px;`" --> <yingbing-ReadPage style="height: 100%;" ref="yingbingReadPage" :footerShow="true"
<!-- :refresher-enabled="true" upper-threshold="200" @refresherrefresh="refresherrefresh" :scroll-top="isScrollTop" --> :page-type="charactersPageType" :font-size="newCharactersSize" :enableClick="true"
<view class="novelReading_main"> :line-height="defaultCharactersLineHeight" :color="novelContentColor" :bg-color="bodyReadingBg"
<!-- @refresherpulling="refresherpulling" --> :slide="20" :enablePreload="false" @loadmore="loadmoreContent" @preload="preloadContent"
<!-- :scroll-top="isScrollTop" :scroll-into-view="`richText_${readDirectoryActive}`" @scrolltoupper="charactersToupper"--> @change="currentChange" @setCatalog="setCatalog" @clickme="clickme" @clickher="clickher"
<scroll-view scroll-y="true" :scroll-top="isScrollTop" @scrolltolower="charactersLower" @clickTo="handelShowStepUp" :clickOption="{width:200,height: 400,left:'auto',top:'auto'}">
:refresher-enabled="true" @scroll="charactersScroll" @refresherrefresh="refresherrefresh" </yingbing-ReadPage>
class="novelReading_characters_scroll" lower-threshold="10"
:refresher-triggered="refresherTriggered">
<view class="novelReading_characters_main"
:style="`color:${novelContentColor};font-size:${newCharactersSize}rpx;line-height:${defaultCharactersLineHeight};`"
v-for="m in novelReadingContentText" :key="m.id" :data-chapterorder="m.chapterorder"
:data-lastid="m.lastid" :data-nextid="m.nextid">
<!-- <rich-text :nodes="m.content" :ref="`richText_${m.id}`"></rich-text> -->
<!-- <rich-text :nodes="`<h3>${m.chaptername}</h3></br>${m.novel_content}`"
:ref="`richText_${m.id}`"></rich-text> -->
<rich-text :nodes="`${m.richTextNodes}`" :ref="`richText_${m.id}`"></rich-text>
<!-- recharge_empty -->
</view>
<!-- <view v-if="readChapterInfoObj.chackpay == 2" class="read_chapter">
<view class=""
:style="`color:${novelContentColor};font-size:${newCharactersSize}rpx;line-height:${defaultCharactersLineHeight};`">
<h3>{{readChapterInfoObj.chaptername}}</h3>
</view>
<u-empty icon="/static/images/recharge_empty.png" text="余额不足,请充值..." />
</view> -->
</scroll-view>
</view>
<!-- <view class="reading_schedule_box" :style="`background:${bodyReadingBg}`">
<view class="reading_schedule_body">
<view class="_previous_chapter"
:style="`color:${previousChapterBbuttonTextColor};background:${previousChapterBbuttonBg}`"
@tap="previousChapter">
上一章
</view>
<view class="_next_chapter"
:style="`color:${nextChapterBbuttonTextColor};background:${nextChapterBbuttonBg}`"
@tap="nextChapter">下一章
</view>
</view>
</view> -->
</view>
<view id="compute_rich_text"
:style="`width:${computeRichTextWidth}px;color:${novelContentColor};font-size:${newCharactersSize}rpx;line-height:${defaultCharactersLineHeight}`">
<rich-text :nodes="computeRichText"></rich-text>
</view> </view>
</view> </view>
<view class="u_popup_all"> <view class="u_popup_all">
@ -225,14 +189,13 @@
directoryPopupShow: false, directoryPopupShow: false,
// //
novelReadingContentText: [], novelReadingContentText: [],
defaultCharactersSize: 46, charactersPageType: 'real',
newCharactersSize: 46, defaultCharactersSize: 28,
defaultCharactersLineHeight: 2, newCharactersSize: 28,
defaultCharactersLineHeight: 20,
readDirectoryActive: '', readDirectoryActive: '',
myData: [],
computeRichText: '', computeRichText: '',
computeRichTextWidth: 0, computeRichTextWidth: 0,
isScrollTop: 0,
refresherTriggered: false, refresherTriggered: false,
readChapterid: '', readChapterid: '',
readChapterFlag: false, readChapterFlag: false,
@ -248,9 +211,6 @@
this.readChapterid = options.id; this.readChapterid = options.id;
}, },
onShow() { onShow() {
// const booksDirectoryPage = this.booksDirectoryPage;
// const newBooksDirectoryPage = [...this.newBooksDirectoryPage, booksDirectoryPage];
// this.newBooksDirectoryPage = newBooksDirectoryPage;
const bookSid = this.booksDirectorySid; const bookSid = this.booksDirectorySid;
const data = { const data = {
sid: bookSid, sid: bookSid,
@ -276,128 +236,110 @@
const booksDirectoryPage = this.booksDirectoryPage; const booksDirectoryPage = this.booksDirectoryPage;
const readChapterid = this.readChapterid ? this.readChapterid : ''; const readChapterid = this.readChapterid ? this.readChapterid : '';
// this.readDirectoryActive = '3'; // this.readDirectoryActive = '3';
await this.isGetDirectory(booksDirectorySid, booksDirectoryPage); // await this.isGetDirectory(booksDirectorySid, booksDirectoryPage);
await this.isGetBookInfo(readChapterid, '', true); const readChapterInfoObj = await this.isGetBookInfo(readChapterid, '', true);
this.novelReadingContentText = [readChapterInfoObj];
this.readDirectoryActive = readChapterInfoObj.chapterorder;
this.readChapterLastid = readChapterInfoObj.lastid;
this.readChapterNextid = readChapterInfoObj.nextid;
this.$refs.yingbingReadPage.init({
contents: [readChapterInfoObj],
start: 0,
currentChapter: readChapterInfoObj.chapterorder
})
}, },
isGetBookInfo(chapterId, statusType, isShowLoading = false) { isGetBookInfo(chapterId, statusType, isShowLoading = false) {
const booksDirectorySid = this.booksDirectorySid; return new Promise((resolve) => {
const novelReadingContentText = this.novelReadingContentText; const booksDirectorySid = this.booksDirectorySid;
if (isShowLoading) { const novelReadingContentText = this.novelReadingContentText;
uni.showLoading({ if (isShowLoading) {
title: '加载中...' uni.showLoading({
}); title: '加载中...'
} });
const data = {
sid: booksDirectorySid,
};
if (chapterId) {
data.id = chapterId;
}
uni.$u.http.post('/read', data).then((res) => {
uni.hideLoading();
if (res.status == 1) {
const readChapterInfoObj = {
...res.data,
richTextNodes: `<h3>${res.data.chaptername}</h3></br>${res.data.novel_content}`
};
this.readChapterInfoObj = readChapterInfoObj;
if (readChapterInfoObj.chackpay == 1) {
this.isScrollTop = 0.01;
if (statusType == 'refresherrefresh') {
this.novelReadingContentText = [readChapterInfoObj, ...novelReadingContentText];
this.computeRichText = readChapterInfoObj.richTextNodes;
const query = uni.createSelectorQuery().in(this);
this.$nextTick(() => {
query.select(`#compute_rich_text`).boundingClientRect((data) => {
this.isScrollTop = parseInt(data.height) - 20;
}).exec();
});
this.refresherTriggered = false;
} else if (statusType == 'charactersLower') {
this.novelReadingContentText = [...novelReadingContentText, readChapterInfoObj];
} else if (statusType == 'nextChapter') {
this.novelReadingContentText = [readChapterInfoObj];
this.readDirectoryActive = readChapterInfoObj.chapterorder;
this.readChapterLastid = readChapterInfoObj.lastid;
this.readChapterNextid = readChapterInfoObj.nextid;
this.$nextTick(() => {
this.isScrollTop = 0;
})
} else if (statusType == 'previousChapter') {
this.novelReadingContentText = [readChapterInfoObj];
this.readDirectoryActive = readChapterInfoObj.chapterorder;
this.readChapterLastid = readChapterInfoObj.lastid;
this.readChapterNextid = readChapterInfoObj.nextid;
this.$nextTick(() => {
this.isScrollTop = 0;
})
} else {
this.novelReadingContentText = [readChapterInfoObj];
this.readDirectoryActive = readChapterInfoObj.chapterorder;
this.readChapterLastid = readChapterInfoObj.lastid;
this.readChapterNextid = readChapterInfoObj.nextid;
}
} else if (readChapterInfoObj.chackpay == 2) {
this.balanceShow = true;
this.balanceTitle = '下一章,付费章节';
if (isShowLoading) {
this.readChapterFlag = true;
}
} else if (readChapterInfoObj.chackpay == 3) {
this.purchaseFullShow = true;
if (isShowLoading) {
this.readChapterFlag = true;
}
}
} }
}).catch((err) => { const data = {
uni.hideLoading(); sid: booksDirectorySid,
console.log(err, '========'); };
}); if (chapterId) {
}, data.id = chapterId;
refresherrefresh() { }
const readChapterInfoObj = this.readChapterInfoObj; uni.$u.http.post('/read', data).then((res) => {
const readChapterLastid = this.readChapterLastid; uni.hideLoading();
this.refresherTriggered = true; if (res.status == 1) {
if (!readChapterLastid) { const resData = res.data;
setTimeout(() => { const is_novel_content = resData.novel_content.replace(/<\/p>/g, '\n')
uni.showToast({ const readChapterInfoObj = {
icon: 'none', ...resData,
title: "已经是第一章了" content: is_novel_content.replace(/<p>/g, ''),
}) isStart: resData.lastid == '' ? true : false,
this.refresherTriggered = false; isEnd: resData.nextid == '' ? true : false,
}, 1000) chapter: resData.chapterorder,
return title: resData.chaptername,
} isTtitle: resData.title
this.isGetBookInfo(readChapterLastid, 'refresherrefresh'); // richTextNodes: `<h3>${res.data.chaptername}</h3></br>${res.data.novel_content}`
}, // richTextNodes: `<h3>${res.data.chaptername}</h3></br>${res.data.novel_content}`
charactersLower() { };
const readChapterNextid = this.readChapterNextid; this.readChapterInfoObj = readChapterInfoObj;
// if (readChapterInfoObj.chackpay != 1) { if (readChapterInfoObj.chackpay == 1) {
// return resolve(readChapterInfoObj);
// } } else if (readChapterInfoObj.chackpay == 2) {
if (!readChapterNextid) { this.balanceShow = true;
uni.showToast({ this.balanceTitle = '下一章,付费章节';
icon: 'none', if (isShowLoading) {
title: "已经是最后一章了~" this.readChapterFlag = true;
}) }
return } else if (readChapterInfoObj.chackpay == 3) {
} this.purchaseFullShow = true;
this.isGetBookInfo(readChapterNextid, 'charactersLower'); if (isShowLoading) {
}, this.readChapterFlag = true;
charactersScroll(e) { }
const bodyReadingHeight = this.bodyReadingHeight; }
const scrollTop = e.detail.scrollTop;
// this.isScrollTop = scrollTop;
uni.createSelectorQuery().selectAll('.novelReading_characters_main').boundingClientRect((data) => {
data.forEach((m) => {
if (m.top < bodyReadingHeight) {
this.readDirectoryActive = m.dataset.chapterorder;
this.readChapterLastid = m.dataset.lastid;
this.readChapterNextid = m.dataset.nextid;
} }
}).catch((err) => {
uni.hideLoading();
console.log(err, '========');
}); });
}).exec(); })
},
async loadmoreContent(chapter, callback) {
console.log(chapter, "*****************")
const newReadDirectoryActive = this.readDirectoryActive;
const newReadChapterLastid = this.readChapterLastid;
const newNovelReadingContentText = this.novelReadingContentText;
const newReadChapterNextid = this.readChapterNextid;
console.log(newNovelReadingContentText,"newNovelReadingContentText")
// const itemTemp = newNovelReadingContentText.filter((m) => m.chapter == parseInt(chapter + 1));
// console.log(itemTemp,itemTemp[0].lastid, "itemTempitemTempitemTemp")
if (newReadDirectoryActive != 1) {
if (chapter < newReadDirectoryActive) {
// const readChapterInfoObj = await this.isGetBookInfo(itemTemp[0].lastid);
const readChapterInfoObj = await this.isGetBookInfo(newReadChapterLastid);
console.log(readChapterInfoObj, "上一章")
this.novelReadingContentText = [...newNovelReadingContentText, readChapterInfoObj];
this.readDirectoryActive = chapter;
// this.readChapterLastid = readChapterInfoObj.lastid;
// this.readChapterNextid = readChapterInfoObj.nextid;
callback('success', readChapterInfoObj)
}
if (chapter > newReadDirectoryActive) {
// const readChapterInfoObj = await this.isGetBookInfo(itemTemp[0].nextid);
const readChapterInfoObj = await this.isGetBookInfo(newReadChapterNextid);
console.log(readChapterInfoObj, "下一章")
this.novelReadingContentText = [readChapterInfoObj, ...newNovelReadingContentText];
this.readDirectoryActive = chapter;
// this.readChapterLastid = readChapterInfoObj.lastid;
// this.readChapterNextid = readChapterInfoObj.nextid;
callback('success', readChapterInfoObj)
}
}
},
currentChange(pageInfo) {
const novelReadingContentText = this.novelReadingContentText;
const chapter = pageInfo.chapter;
const itemTemp = novelReadingContentText.filter((m) => m.chapter == chapter);
this.readChapterLastid = itemTemp[0].lastid;
this.readChapterNextid = itemTemp[0].nextid;
}, },
previousChapter() { previousChapter() {
const readChapterLastid = this.readChapterLastid const readChapterLastid = this.readChapterLastid
@ -637,7 +579,7 @@
// this.myData = myData; // this.myData = myData;
this.computeRichTextWidth = windowWidth; this.computeRichTextWidth = windowWidth;
this.novelMainTypeColor = novelMainTypeColor; this.novelMainTypeColor = novelMainTypeColor;
this.newCharactersSize = JSON.parse(novelMainObj).charactersSize || 46; this.newCharactersSize = JSON.parse(novelMainObj).charactersSize || 28;
this.bodyReadingHeight = screenHeight - statusBarHeight - devicePixelRatio * 22; this.bodyReadingHeight = screenHeight - statusBarHeight - devicePixelRatio * 22;
// //
// this.scrollReadingHeight = screenHeight - statusBarHeight - devicePixelRatio * 38; // this.scrollReadingHeight = screenHeight - statusBarHeight - devicePixelRatio * 38;
@ -724,11 +666,14 @@
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 32rpx 32rpx; // // padding: 32rpx 32rpx;
padding-bottom: constant(safe-area-inset-bottom); padding-bottom: constant(safe-area-inset-bottom);
/* 兼容 iOS 设备 */ /* 兼容 iOS 设备 */
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
/* 兼容 iPhone X 及以上设备 */ /* 兼容 iPhone X 及以上设备 */
padding-top: var(--status-bar-height);
/* 兼容 iOS 设备 */
// padding-top:var(status-bar-height);
box-sizing: border-box; box-sizing: border-box;
.novelReading_main { .novelReading_main {
@ -922,10 +867,6 @@
/* 兼容 iOS 设备 */ /* 兼容 iOS 设备 */
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
/* 兼容 iPhone X 及以上设备 */ /* 兼容 iPhone X 及以上设备 */
// padding-top: constant(safe-area-inset-top);
// /* iOS */
// padding-top: env(safe-area-inset-top);
// /* iPhone X */
._popup_box_item { ._popup_box_item {
display: flex; display: flex;

View File

@ -0,0 +1,104 @@
## 1.4.32023-05-25
* 新增fontFamily属性设置字体名称
* 新增fontFace属性添加自定义字体
## 1.4.22023-05-25
* 修复IOS电量显示
## 1.4.12023-05-24
* 修复单章节内容太短可能会引起得卡死和报错
## 1.4.02023-04-04
* 优化翻页模式,减少闪烁问题
## 1.3.92023-04-03
* 优化APP端电量获取修复获取电量可能会造成闪退的问题
* 修复APP-NVUE翻页模式加载下一章节会闪烁的问题
## 1.3.82023-04-01
* 修复APP-NVUE 向前翻页会闪烁的问题
* 删除一些不必要的代码文件
## 1.3.72023-04-01
* APP-NVUE使用webview替代rich-text实现自定义页面
* APP-NVUE自定义页面支持添加自定义点击事件
* 稍微优化了一下翻页
## 1.3.62023-03-15
* 修复在低端机上,分页会卡住的问题
## 1.3.52023-03-14
* 优化整书模式的分页
## 1.3.42023-03-13
* 修复APP-NVUE一些问题
## 1.3.32023-03-13
* 优化滚动模式定位问题
## 1.3.22023-03-01
* 修复整书模式 init初始化 无效果的 bug
## 1.3.12023-01-04
* 修复直接点击上一页文字不显示的bug
## 1.3.02022-12-20
* 新增APP-NVUE和微信小程序支持
* 新增自定义插槽功能
* 整书模式已整合进入章节模式,表现效果和章节模式相同
* 新增页面顶部和底部的电池电量、章节、事件,页数显示
## 1.2.92022-09-06
* 修复滑动过快造成翻页动画异常的bug
* 修复调用refresh方法报错的bug
* 修复翻页动画有时候方向判断错误的bug
* 滚动模式不再使用better-scroll插件改用自己写的滚动组件
* 添加pageNext, pagePrev翻页方法
## 1.2.82022-06-08
* 更改整书模式的翻页方法(上次更新忘记改了)
## 1.2.72022-06-04
* 添加自定义页面功能(可用于付费章节、广告展示等)
* 更改触摸翻页方式,现在页面上任何地方都能触摸翻页
* 减小了翻页需要的距离
* 减少了翻页节流时间
## 1.2.62022-02-14
* 修复向前点击翻页出现2种动画的bug
## 1.2.52021-10-19
* 优化整书模式一些问题
## 1.2.42021-10-18
* 修复滚动模式定位问题
## 1.2.32021-10-17
* 添加点击区域配置,具体见下面得介绍
## 1.2.22021-10-17
* 优化滚动模式
## 1.2.12021-10-15
* 优化滚动模式逻辑
## 1.2.02021-10-15
* 修复滚动模式下得问题
## 1.1.92021-10-15
* 修复一些显示问题
* 优化了一下滚动模式滚动效果
## 1.1.82021-10-14
* content对象中可以带上章节名称方便取值
* 增加初始化loading效果
## 1.1.72021-10-14
* 增加整书模式返回得页面信息
## 1.1.62021-10-14
* 更改整书模式返回章节集合得参数
## 1.1.52021-10-14
* 优化翻页得性能同时最多显示3张页面
## 1.1.42021-10-13
修复一些问题
## 1.1.32021-10-13
* 修复一些问题
## 1.1.22021-10-13
* 修复一些问题
## 1.1.12021-10-13
* 修复了一些问题
## 1.1.02021-10-12
* 此次更新为重构插件
* 首先感谢一下2位插件使用者提供的反馈
* 主要更新如下:
- 1、为请求事件增加了过渡效果滚动模式下增加上拉和下拉操作
- 2、解决了一些逻辑问题现在preload和loadmore事件触发更合理
- 3、其它都是些小更新比如章节模式下currentChange事件增加了返回的页面信息
- 4、更改了参数结构请注意阅读使用须知
## 1.0.52021-09-22
* 修复上次更新引起无法翻到上一页的问题
## 1.0.42021-09-22
* 修复横向翻页部分页面不绘制的bug
## 1.0.32021-09-05
* 修复整书模式下阅读小说到最后面下一页会从小说最前面再次排版的bug
## 1.0.22021-09-04
* 修复预加载章节内容无效的问题
* 添加初始化触发currentChange事件
## 1.0.12021-08-29
* 减少滚动模式下触底加载更多时的滚动距离
## 1.0.02021-08-28
* 发布1.0.0版本

View File

@ -0,0 +1,116 @@
<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
mounted () {
this.getBattery()
},
methods: {
getBattery () {
// #ifdef H5
window.navigator.getBattery().then((res) => {
// 01100
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 */
}
</style>

View File

@ -0,0 +1,366 @@
import Util from '../../../js_sdk/util.js'
export default {
data () {
return {
computedResolve: null
}
},
methods: {
reset (data, pages = []) {
if ( data.custom && data.custom.length > 0 ) {
pages.length > 0 ? pages[pages.length - 1].isLastPage = false : null
data.custom.forEach(custom => {
let type = ''
if ( custom.indexOf('slot:') > -1 ) {
type = 'slot'
custom = custom.split(':')[1]
} else {
type = 'custom'
let clicks = custom.match(/onclick=\"*([\s\S]*?)\"/ig);
if ( clicks ) {
clicks.forEach(click => {
let name = click.match(/onclick=\"*([\s\S]*?)(\(|\")/)[1]
let func = click.match(/onclick=\"*([\s\S]*?)\"/)
let args = func[1].replace(name, '')
args = args ? args.slice(1, args.length - 1).replace(/\s/g, '') : ''
custom = custom.replace(func[0], `onclick="triggerCustomClick('${name}', [${args}])"`)
})
}
}
let end = pages.length > 0 ? pages[pages.length - 1].end : 0
pages.push({
chapter: data.chapter,
title: data.title || '',
type: type,
dataId: data.chapter * 100000 + end,
start: end,
end: end + 10,
isLastPage: false,
text: custom
})
})
pages[pages.length - 1].isLastPage = true
}
this.computedResolve(pages)
this.computedResolve = null
},
measureText (text, fontSize=10) {
text = new String(text);
text = text.split('');
let width = 0;
text.forEach(function(item) {
if (/[a-zA-Z]/.test(item)) {
width += 7;
} else if (/[0-9]/.test(item)) {
width += 5.5;
} else if (/\./.test(item)) {
width += 2.7;
} else if (/-/.test(item)) {
width += 3.25;
} else if (/[\u4e00-\u9fa5]/.test(item)) { //中文匹配
width += 10;
} else if (/\(|\)/.test(item)) {
width += 3.73;
} else if (/\s/.test(item)) {
width += 2.5;
} else if (/[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/.test(item)) {
width += 8;
} else {
width += 10;
}
});
return width * fontSize / 10;
},
async computedText (data, start) {
let rect = await this.getRect()
let viewWidth = rect.width - (this.options.slide * 2)
let viewHeight = rect.height - this.options.topGap - this.options.bottomGap
if ( this.options.headerShow ) viewHeight = viewHeight - uni.upx2px(50)
if ( this.options.footerShow ) viewHeight = viewHeight - uni.upx2px(50)
let pageHeight = this.options.fontSize + this.options.lineHeight;
let strs = [];
let page = {
title: data.title || '',
chapter: data.chapter,
type: 'text',
dataId: data.chapter * 100000 + start,
start: start,
end: 0,
isLastPage: false,
text: []
}
let length = 0;
let contentSync = data.content.substr(start);
let lastIndex = 0;
while ( (pageHeight + this.options.fontSize + this.options.lineHeight) <= viewHeight ) {
strs.push('');
let lineWidth = 0;
for ( let i = lastIndex; i < contentSync.length; i++ ) {
if ( JSON.stringify(contentSync[i]) == JSON.stringify('\r') || JSON.stringify(contentSync[i]) == JSON.stringify('\n') ) {
length += 1
page.end = page.start + length;
lastIndex = i + 1;
break;
}
lineWidth += JSON.stringify(contentSync[i]) == JSON.stringify('\t') ? 0 : this.measureText(contentSync[i], this.options.fontSize);
if ( lineWidth >= viewWidth ) {
lastIndex = i;
break;
} else {
if ( JSON.stringify(contentSync[i]) != JSON.stringify('\t') ) {
strs[strs.length - 1] += contentSync[i].replace(' ', ' ')
length += 1
page.end = page.start + length
}
}
}
pageHeight += this.options.fontSize + this.options.lineHeight;
if ( page.end >= data.content.replace(/\t/g, '').length - 1 ) {
page.isLastPage = true;
break;
}
}
page.text = strs;
return page;
},
getRect () {
return new Promise (resolve => {
// #ifndef APP-NVUE
const query = uni.createSelectorQuery().in(this);
query.select('.yingbing-read-page').boundingClientRect(data => {
resolve(data)
}).exec();
// #endif
// #ifdef APP-NVUE
uni.requireNativePlugin('dom').getComponentRect(this.$refs.yingbingReadPage, res => {
resolve(res.size)
})
// #endif
})
},
getPages (data) {
let pages = [];
const doWhile = (start = 0) => {
this.computedText(data, start).then(page => {
pages.push(page);
if ( page.isLastPage ) {
this.reset(data, pages)
} else {
doWhile(page.end);
}
});
}
doWhile();
},
computedChapter(data) {
return new Promise(resolve => {
this.computedResolve = resolve
data.content ? this.getPages(data) : this.reset(data)
})
},
//绘制页面
resetPage(data) {
setTimeout(() => {
//一次最多渲染3章的内容根据定位的章节剪切出3章内容渲染
let currentChapter = data.currentChapter || this.contents[0].chapter
let nowIndex = this.contents.findIndex(item => item.chapter == currentChapter);
let prevIndex = -1;
let nextIndex = -1;
let contents = [];
if (!this.contents[nowIndex].isStart) prevIndex = this.contents.findIndex(item => item.chapter == currentChapter - 1);
if (!this.contents[nowIndex].isEnd) nextIndex = this.contents.findIndex(item => item.chapter == currentChapter + 1);
if (prevIndex > -1) {
contents.push(this.contents[prevIndex])
}
contents.push(this.contents[nowIndex])
if (nextIndex > -1) {
contents.push(this.contents[nextIndex])
}
let arr = [];
const dowhile = (i) => {
let item = contents[i];
this.computedChapter(item).then(pages => {
if (currentChapter == item.chapter) {
let index = Object.keys(pages).findIndex(key => data.start >= pages[key].start && data.start < pages[key].end)
this.currentDataId = pages[index > -1 ? index : 0].dataId;
}
arr = arr.concat(pages)
if (i == contents.length - 1) {
if ( this.options.pageType != 'scroll' ) {
arr.unshift({
title: contents[0].title || '',
chapter: contents[0].chapter,
type: contents[0].isStart ? 'top' : 'prevLoading',
dataId: arr[0].dataId - 1,
start: 0,
end: 0
})
arr.push({
title: item.title || '',
chapter: item.chapter,
type: item.isEnd ? 'bottom' : 'nextLoading',
dataId: arr[arr.length - 1].dataId + 1,
start: 0,
end: 0
})
}
this.pages = arr
if ( this.options.pageType == 'scroll' ) {
this.$refs.list.scrollTo(0)
}
this.$nextTick(() => {
if ( this.options.pageType != 'scroll' ) {
this.onChange(this.currentDataId);
} else {
setTimeout(() => {
Util.getRect('#scroll-item_' + this.currentDataId, Util.getRefs(this, 'scrollItem_' + this.currentDataId, 0), this).then(rect => {
this.$refs.list.scrollTo(rect.top)
})
}, 50)
}
this.initLoading = false;
this.preload(currentChapter);
})
} else {
setTimeout(() => {
dowhile(i + 1)
}, 100)
}
})
}
dowhile(0)
}, 50)
},
computedPage(e) {
this.computedChapter(e.content).then((pages) => {
let arr = [];
let newPages = [];
const pagesSync = e.type == 'prev' ? pages.concat(this.pages) : this.pages.concat(pages);
pagesSync.forEach(item => {
if (arr.indexOf(item.chapter) == -1) arr.push(item.chapter)
})
if (arr.length > 3) {
let reChapter = e.type == 'prev' ? pagesSync[pagesSync.length - 1].chapter : pagesSync[0].chapter;
newPages = pagesSync.filter(item => item.chapter != reChapter && (item.type == 'text' || item.type == 'custom' || item.type == 'slot'));
} else {
newPages = pagesSync.filter(item => (item.type == 'text' || item.type == 'custom' || item.type == 'slot'));
}
if ( this.options.pageType != 'scroll' ) {
const prevIndex = this.contents.findIndex(content => content.chapter == newPages[0].chapter);
const nextIndex = this.contents.findIndex(content => content.chapter == newPages[newPages.length - 1].chapter);
newPages.unshift({
title: this.contents[prevIndex].title || '',
chapter: this.contents[prevIndex].chapter,
type: this.contents[prevIndex].isStart ? 'top' : 'prevLoading',
dataId: newPages[0].dataId - 1,
start: 0,
end: 0
})
newPages.push({
title: this.contents[nextIndex].title || '',
chapter: this.contents[nextIndex].chapter,
type: this.contents[nextIndex].isEnd ? 'bottom' : 'nextLoading',
dataId: newPages[newPages.length - 1].dataId + 1,
start: 0,
end: 0
})
this.pages = newPages
const nowIndex = newPages.findIndex(page => page.dataId == this.currentDataId);
if ( nowIndex == -1 ) {
this.currentDataId = e.type == 'next' ? pages[0].dataId : pages[pages.length - 1].dataId;
this.onChange(this.currentDataId)
}
} else {
let dataId = e.type == 'prev' ? this.pages[0].dataId : this.pages[this.pages.length-1].dataId
Util.getRect('.scroll-item-wrapper', this.$refs.scrollItemWrapper, this).then(rect => {
let lastHeight = rect.height
this.pages = e.type == 'prev' ? pages.concat(this.pages) : this.pages.concat(pages)
if ( e.type == 'prev' ) {
this.$nextTick(function () {
setTimeout(() => {
Util.getRect('.scroll-item-wrapper', this.$refs.scrollItemWrapper, this).then(rect => {
this.$refs.list.scrollTo(rect.height - lastHeight)
})
}, 50)
})
}
})
}
})
},
//预加载章节
preload (chapter) {
if ( !this.enablePreload ) return false
const nowIndex = this.contents.findIndex(item => item.chapter == chapter);
let prevIndex = -2;
let nextIndex = -2;
let chapters = [];
if ( !this.contents[nowIndex].isStart ) prevIndex = this.contents.findIndex(item => item.chapter == chapter - 1);
if ( !this.contents[nowIndex].isEnd ) nextIndex = this.contents.findIndex(item => item.chapter == chapter + 1);
if ( prevIndex == -1 ) {
chapters.push(chapter - 1);
}
if ( nextIndex == -1 ) {
chapters.push(chapter + 1);
}
if ( chapters.length > 0 ) {
this.$emit('preload', chapters, (status, contents) => {
if (status == 'success') {
contents.forEach(item => {
const index = this.contents.findIndex(content => content.chapter == item.chapter)
if (index > -1) {
this.contents[index] = item;
} else {
this.contents.push(item);
}
})
}
})
}
},
filterPage (pageInfo) {
if ( pageInfo && pageInfo.dataId > -1 ) {
const nowChapters = this.pages.filter(item => item.chapter == pageInfo.chapter && (item.type == 'text' || item.type == 'custom' || item.type == 'slot'))
let currentPage = nowChapters.findIndex(item => item.dataId == pageInfo.dataId)
if ( currentPage > -1 ) {
return (currentPage + 1) + ' / ' + nowChapters.length
} else {
return pageInfo.type == 'top' ? '最前面' : pageInfo.type == 'bottom' ? '最后面' : pageInfo.type.indexOf('Loading') > -1 ? '请等待' : ''
}
} else {
return '加载中'
}
},
filterDate () {
let date = new Date()
return Util.zeroize(date.getHours()) + ':' + Util.zeroize(date.getMinutes())
},
//翻往上一页
pagePrev () {
if ( this.options.pageType != 'scroll' ) {
// #ifndef APP-NVUE
this.pagePrevWxs()
// #endif
// #ifdef APP-NVUE
this.pagePrevBinding()
// #endif
} else {
this.scrollPrev()
}
},
//翻往下一页
pageNext () {
if ( this.options.pageType != 'scroll' ) {
// #ifndef APP-NVUE
this.pageNextWxs()
// #endif
// #ifdef APP-NVUE
this.pageNextBinding()
// #endif
} else {
this.scrollNext()
}
},
}
}

View File

@ -0,0 +1,379 @@
<template>
<!-- #ifdef APP-VUE || H5 -->
<div class="yingbing-computed-page" :prop="computedPageProp" :change:prop="computedPage.propWatcher" :style="{
'padding-left': options.slide + 'px',
'padding-right': options.slide + 'px',
'padding-top': options.topGap + 'px',
'padding-bottom': options.bottomGap + 'px'
}">
<view class="yingbing-computed-page-header" v-if="options.headerShow"></view>
<view class="yingbing-computed-page-content"></view>
<view class="yingbing-computed-page-footer" v-if="options.footerShow"></view>
</div>
<!-- #endif -->
<!-- #ifndef APP-VUE || H5 -->
<div class="yingbing-computed-page" :style="{
'padding-left': options.slide + 'px',
'padding-right': options.slide + 'px',
'padding-top': options.topGap + 'px',
'padding-bottom': options.bottomGap + 'px'
}">
<view class="yingbing-computed-page-header" v-if="options.headerShow"></view>
<view class="yingbing-computed-page-content" ref="yingbingComputedPageContent"></view>
<view class="yingbing-computed-page-footer" v-if="options.footerShow"></view>
</div>
<!-- #endif -->
</template>
<script>
export default {
props: {
options: {
type: Object,
default () {
return new Object
}
}
},
data () {
return {
content: '',
custom: [],
isStart: false,
chapter: null,
title: '',
resolve: null
}
},
computed: {
computedPageProp () {
return {
isStart: this.isStart,
fontSize: this.options.fontSize,
lineHeight: this.options.lineHeight,
pageType: this.options.pageType,
content: this.content,
chapter: this.chapter,
title: this.title
}
}
},
methods: {
computed ({content, chapter, custom, title}) {
return new Promise((resolve) => {
this.content = content || '';
this.custom = custom || [];
this.chapter = chapter || null;
this.title = title || '';
this.resolve = resolve;
// #ifdef APP-VUE || H5
content ? this.isStart = true : this.reset();
// #endif
// #ifndef APP-VUE || H5
content ? this.getPages() : this.reset();
// #endif
})
},
reset (pages = []) {
if ( this.custom.length > 0 ) {
pages.length > 0 ? pages[pages.length - 1].isLastPage = false : null
this.custom.forEach(custom => {
let type = ''
if ( custom.indexOf('slot:') > -1 ) {
type = 'slot'
custom = custom.split(':')[1]
} else {
type = 'custom'
let clicks = custom.match(/onclick=\"*([\s\S]*?)\"/ig);
if ( clicks ) {
clicks.forEach(click => {
let name = click.match(/onclick=\"*([\s\S]*?)(\(|\")/)[1]
let func = click.match(/onclick=\"*([\s\S]*?)\"/)
let args = func[1].replace(name, '')
args = args ? args.slice(1, args.length - 1).replace(/\s/g, '') : ''
custom = custom.replace(func[0], `onclick="triggerCustomClick('${name}', [${args}])"`)
})
}
}
let end = pages.length > 0 ? pages[pages.length - 1].end : 0
pages.push({
chapter: this.chapter,
title: this.title,
type: type,
dataId: this.chapter * 100000 + end,
start: end,
end: end + 10,
isLastPage: false,
text: custom
})
})
pages[pages.length - 1].isLastPage = true
}
this.resolve(pages);
this.resolve = null
this.isStart = false
this.content = ''
this.title = ''
this.chapter = null
this.start = null
this.end = null
},
// #ifndef APP-VUE || H5
measureText (text, fontSize=10) {
text = new String(text);
text = text.split('');
let width = 0;
text.forEach(function(item) {
if (/[a-zA-Z]/.test(item)) {
width += 7;
} else if (/[0-9]/.test(item)) {
width += 5.5;
} else if (/\./.test(item)) {
width += 2.7;
} else if (/-/.test(item)) {
width += 3.25;
} else if (/[\u4e00-\u9fa5]/.test(item)) { //
width += 10;
} else if (/\(|\)/.test(item)) {
width += 3.73;
} else if (/\s/.test(item)) {
width += 2.5;
} else if (/[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/.test(item)) {
width += 8;
} else {
width += 10;
}
});
return width * fontSize / 10;
},
async computedText (start) {
let rect = await this.getRect()
let viewWidth = rect.width;
let viewHeight = rect.height;
let pageHeight = this.options.fontSize + this.options.lineHeight;
let strs = [];
let page = {
title: this.title,
chapter: this.chapter,
type: 'text',
dataId: this.chapter * 100000 + start,
start: start,
end: 0,
isLastPage: false,
text: []
}
let length = 0;
let contentSync = this.content.substr(start);
let lastIndex = 0;
while ( (pageHeight + this.options.fontSize + this.options.lineHeight) <= viewHeight ) {
strs.push('');
let lineWidth = 0;
for ( let i = lastIndex; i < contentSync.length; i++ ) {
if ( JSON.stringify(contentSync[i]) == JSON.stringify('\r') || JSON.stringify(contentSync[i]) == JSON.stringify('\n') ) {
length += 1
page.end = page.start + length;
lastIndex = i + 1;
break;
}
lineWidth += JSON.stringify(contentSync[i]) == JSON.stringify('\t') ? 0 : this.measureText(contentSync[i], this.options.fontSize);
if ( lineWidth >= viewWidth ) {
lastIndex = i;
break;
} else {
if ( JSON.stringify(contentSync[i]) != JSON.stringify('\t') ) {
strs[strs.length - 1] += contentSync[i].replace(' ', ' ')
length += 1
page.end = page.start + length
}
}
}
pageHeight += this.options.fontSize + this.options.lineHeight;
if ( page.end >= this.content.length - 1 ) {
page.isLastPage = true;
break;
}
}
page.text = strs;
return page;
},
getRect () {
return new Promise (resolve => {
// #ifndef APP-NVUE
const query = uni.createSelectorQuery().in(this);
query.select('.yingbing-computed-page-content').boundingClientRect(data => {
resolve(data)
}).exec();
// #endif
// #ifdef APP-NVUE
uni.requireNativePlugin('dom').getComponentRect(this.$refs.yingbingComputedPageContent, res => {
resolve(res.size)
})
// #endif
})
},
getPages () {
let pages = [];
const doWhile = (start = 0) => {
this.computedText(start).then(page => {
pages.push(page);
if ( page.isLastPage ) {
this.reset(pages)
} else {
doWhile(page.end);
}
});
}
doWhile();
}
// #endif
}
}
</script>
<!-- #ifdef H5 || APP-VUE -->
<script lang="renderjs" type="module" module="computedPage">
let myComputedPageDom;
export default {
data () {
return {
viewWidth: 0,
viewHeight: 0
}
},
mounted () {
this.initDom.bind(this);
},
methods: {
initDom () {
myComputedPageDom = computedPage.init(document.getElementsByClassName('yingbing-computed-page')[0]);
// view 访
myComputedPageDom.setOption(this.computedPageProp);
},
//
computedText (start) {
let parent = document.getElementsByClassName('yingbing-computed-page-content')[0];
this.viewWidth = parent.offsetWidth;
this.viewHeight = parent.offsetHeight;
let computedCanvas = this.createComputedCanvas(parent);
let context = computedCanvas.getContext('2d');
context.font = this.computedPageProp.fontSize + 'px 微软雅黑';
context.lineWidth = 1;
let pageHeight = this.computedPageProp.fontSize + this.computedPageProp.lineHeight;
let strs = [];
let page = {
title: this.computedPageProp.title,
chapter: this.computedPageProp.chapter,
type: 'text',
dataId: this.computedPageProp.chapter * 100000 + start,
start: start,
end: 0,
isLastPage: false,
text: []
}
let length = 0;
let contentSync = this.computedPageProp.content.substr(start);
let lastIndex = 0;
while ( (pageHeight + this.computedPageProp.fontSize + this.computedPageProp.lineHeight) <= this.viewHeight ) {
strs.push('');
let lineWidth = 0;
for ( let i = lastIndex; i < contentSync.length; i++ ) {
if ( JSON.stringify(contentSync[i]) == JSON.stringify('\r') || JSON.stringify(contentSync[i]) == JSON.stringify('\n') ) {
length += 1
page.end = page.start + length;
lastIndex = i + 1;
break;
}
lineWidth += (JSON.stringify(contentSync[i]) == JSON.stringify('\t') ? 0 : context.measureText(contentSync[i]).width);
if ( lineWidth >= this.viewWidth ) {
lastIndex = i;
break;
} else {
if ( JSON.stringify(contentSync[i]) != JSON.stringify('\t') ) {
strs[strs.length - 1] += contentSync[i];
length += 1;
page.end = page.start + length;
}
}
}
pageHeight += (this.computedPageProp.fontSize + this.computedPageProp.lineHeight);
if ( page.end >= this.computedPageProp.content.length - 1 ) {
page.isLastPage = true;
break;
}
}
page.text = strs;
return page;
},
//canvas
createComputedCanvas (el) {
if ( el.getElementsByClassName('computedCanvas')[0] ) return el.getElementsByClassName('computedCanvas')[0];
let canvasDom = document.createElement('canvas');
canvasDom.width = this.viewWidth;
canvasDom.height = this.viewHeight;
canvasDom.style.position = 'absolute';
canvasDom.style.top = 0;
canvasDom.style.left = 0;
canvasDom.setAttribute('class', 'computedCanvas');
el.appendChild(canvasDom);
return el.getElementsByClassName('computedCanvas')[0];
},
//
propWatcher (newValue, oldValue) {
if ( newValue.isStart != (oldValue ? oldValue.isStart : false) ) {
if ( newValue.isStart ) {
this.getPages();
}
}
},
getPages () {
let pages = [];
const doWhile = (start = 0) => {
let page = this.computedText(start);
pages.push(page);
if ( page.isLastPage ) {
this.triggerReset(pages)
} else {
doWhile(page.end);
}
}
doWhile();
},
triggerReset (pages) {
// #ifndef H5
this.$ownerInstance.callMethod('reset', pages);
// #endif
// #ifdef H5
this.reset(pages);
// #endif
}
}
}
</script>
<!-- #endif -->
<style scoped>
.yingbing-computed-page {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
/* #endif */
}
.yingbing-computed-page-header {
height: 50rpx;
}
.yingbing-computed-page-footer {
height: 50rpx;
}
.yingbing-computed-page-content {
flex: 1;
}
</style>

View File

@ -0,0 +1,250 @@
const Binding = uni.requireNativePlugin('bindingx')
const animation = uni.requireNativePlugin('animation')
import Util from '../../../js_sdk/util.js'
export default {
data () {
return {
disableTouch: false,
isTouch: false,
flipTouchTime: 0,
interval: false,
direction: ''
}
},
beforeDestroy () {
if ( this.flip_binding ) {
Binding.unbind({
token: this.flip_binding.token,
eventType: 'pan'
})
this.flip_binding = null
}
if ( this.flip_animation_binding ) {
Binding.unbind({
token: this.flip_animation_binding.token,
eventType: 'timing'
})
this.flip_animation_binding = null
}
},
methods: {
//翻往上一页
pagePrevBinding () {
if ( !this.isTouch && !this.disableTouch ) {
this.isTouch = true
this.startX = 0
this.onFilpTouchend()
}
},
//翻往下一页
pageNextBinding () {
if ( !this.isTouch && !this.disableTouch ) {
this.isTouch = true
this.startX = this.viewWidth
this.onFilpTouchend()
}
},
onFilpTouchstart (event) {
if ( this.isTouch || this.disableTouch ) {
return
}
this.isTouch = true
this.flipTouchTime = 0
this.interval = true
this.setInterval()
let touch = event.touches[0]
this.startX = touch.pageX
this.startY = touch.pageY
},
async onFilpTouchmove (event) {
if ( this.isTouch && (this.pageType == 'real' || this.pageType == 'cover') && !this.disableTouch && this.flipTouchTime > 200 ) {
if ( !this.direction ) {
let touch = event.touches[0]
if ( touch.pageX < this.startX ) {
if ( this.nextDataId ) {
this.direction = 'next'
}
} else {
if ( this.prevDataId ) {
this.direction = 'prev'
}
}
}
if ( this.direction ) {
this.disableTouch = true
let currentDataId = this.direction == 'next' ? this.currentDataId : this.prevDataId
let props = [{
element: Util.getEl(this.$refs['flipItem_' + currentDataId][0]),
property: 'transform.translateX',
expression: `${this.direction == 'next' ? ('x > 0 ? 0 : (x < -' + this.viewWidth + ' ? -' + this.viewWidth + ' : x + 0)') : ('x < 0 ? -' + this.viewWidth + ' : (x > ' + this.viewWidth + ' ? 0 : x-' + this.viewWidth + ')')}`
}]
if ( this.pageType == 'real' ) {
props.push({
element: Util.getEl(this.$refs['flipItemWrapper_' + currentDataId][0]),
property: 'transform.translateX',
expression: `${this.direction == 'next' ? ('x > 0 ? 0 : (x < -' + this.viewWidth + ' ? ' + this.viewWidth + ' : 0 - x)') : ('x < 0 ? ' + this.viewWidth + ' : (x > ' + this.viewWidth + ' ? 0 : ' + this.viewWidth + '-x)')}`
})
props.push({
element: Util.getEl(this.$refs['flipItemBg_' + currentDataId][0]),
property: 'transform.translateX',
expression: `${this.direction == 'next' ? ('x > 0 ? 0 : (x < -' + this.viewWidth + ' ? -' + this.viewWidth + ' : x + 0)') : ('x < 0 ? -' + this.viewWidth + ' : (x > ' + this.viewWidth + ' ? 0 : x-' + this.viewWidth + ')')}`
})
let rect = await this.getRect(this.$refs['flipItemBg_' + currentDataId][0])
let height = rect.height / 2;
let maxDeg = height / 5;
props.push({
element: Util.getEl(this.$refs['flipItemBg_' + currentDataId][0]),
property: 'transform.rotateZ',
expression: `${this.direction == 'next' ? 'y/' + maxDeg : '-(y/' + maxDeg + ')'}`
})
props.push({
element: Util.getEl(this.$refs['flipItemShadow_' + currentDataId][0]),
property: 'width',
expression: `${this.direction == 'next' ? 'abs(x) / 2 + 0' : this.viewWidth / 2 + '-abs(x) / 2'}`
})
}
this.flip_binding = Binding.bind({
anchor: Util.getEl(this.$refs.yingbingFlip),
eventType: 'pan',
props: props
}, (e) => {
if ((e.state == 'end' || e.state == 'cancel') && this.flip_binding) {
this.clearInterval()
Binding.unbind({
token: this.flip_binding.token,
eventType: 'pan'
})
this.flip_binding = null
let value = this.direction == 'next' ? 1 : -1;
if (this.flipTouchTime <= 200) {
let duration = (this.pageType == 'real' || this.pageType == 'cover') ? 200 : 1
this.pageAnimation(value, -value * this.viewWidth, duration);
} else {
let duration = (this.pageType == 'real' || this.pageType == 'cover') ? 200 : 1
let deltaX = Binding.getComputedStyle(Util.getEl(this.$refs['flipItem_' + currentDataId][0])).translateX
let late = this.direction == 'next' ? deltaX : this.viewWidth + deltaX
if (Math.abs(late) >= this.viewWidth / 4) {
this.pageAnimation(value, -value * this.viewWidth, duration)
} else {
let value = this.direction == 'next' ? 1 : -1;
this.pageAnimation(value, 0, duration);
}
}
}
})
} else {
this.resetPageBinding()
}
}
},
onFilpTouchend () {
if ( this.isTouch && !this.disableTouch ) {
this.disableTouch = true
this.clearInterval()
if ( this.flipTouchTime <= 200 ) {
if ( !this.direction ) {
if (this.startX > (this.viewWidth / 4) * 3) {
if ( this.nextDataId ) {
this.direction = 'next'
}
}
if (this.startX < (this.viewWidth / 4)) {
if ( this.prevDataId ) {
this.direction = 'prev'
}
}
}
if ( this.direction ) {
let duration = (this.pageType == 'real' || this.pageType == 'cover') ? 200 : 1
let value = this.direction == 'next' ? 1 : -1;
this.pageAnimation(value, -value * this.viewWidth, duration);
} else {
this.resetPageBinding()
}
} else {
this.resetPageBinding()
}
}
},
getRect (el) {
return new Promise(resolve => {
uni.requireNativePlugin('dom').getComponentRect(el, res => {
resolve(res.size)
})
})
},
setInterval () {
this.flipTouchTimer = setTimeout(() => {
this.flipTouchTime += 10
if ( this.interval ) {
this.setInterval()
}
}, 10)
},
clearInterval () {
this.interval = false
if ( this.flipTouchTimer ) {
clearTimeout(this.flipTouchTimer)
this.flipTouchTimer = null
}
},
pageAnimation (value, offset, duration) {
let currentDataId = this.direction == 'next' ? this.currentDataId : this.prevDataId
let late = this.direction == 'next' ? offset : offset - this.viewWidth;
let flipItemTrans = Binding.getComputedStyle(Util.getEl(this.$refs['flipItem_' + currentDataId][0])).translateX
let props = [{
element: Util.getEl(this.$refs['flipItem_' + currentDataId][0]),
property: 'transform.translateX',
expression: `linear(t, ${flipItemTrans}, ${late - flipItemTrans}, ${duration})`
}]
if ( this.pageType == 'real' ) {
let flipItemWrapperTrans = Binding.getComputedStyle(Util.getEl(this.$refs['flipItemWrapper_' + currentDataId][0])).translateX
props.push({
element: Util.getEl(this.$refs['flipItemWrapper_' + currentDataId][0]),
property: 'transform.translateX',
expression: `linear(t, ${flipItemWrapperTrans}, ${-late - flipItemWrapperTrans}, ${duration})`
})
let flipItemBgTrans = Binding.getComputedStyle(Util.getEl(this.$refs['flipItemBg_' + currentDataId][0])).translateX
props.push({
element: Util.getEl(this.$refs['flipItemBg_' + currentDataId][0]),
property: 'transform.translateX',
expression: `linear(t, ${flipItemBgTrans}, ${late - flipItemBgTrans}, ${duration})`
})
let flipItemShadowWidth = this.flipTouchTime <= 200 && this.direction == 'prev' ? this.viewWidth : Binding.getComputedStyle(Util.getEl(this.$refs['flipItemShadow_' + currentDataId][0])).width
props.push({
element: Util.getEl(this.$refs['flipItemShadow_' + currentDataId][0]),
property: 'width',
expression: `linear(t, ${flipItemShadowWidth}, ${-late - flipItemShadowWidth}, ${duration})`
})
}
this.flip_animation_binding = Binding.bind({
eventType: 'timing',
exitExpression: 't>' + duration,
props: props
}, (e) => {
if (e.state == 'exit' && this.flip_animation_binding && e.t > duration) {
Binding.unbind({
token: this.flip_animation_binding.token,
eventType: 'timing'
})
this.flip_animation_binding = null
if ( Math.abs(offset) > 0 ) {
this.onChange(value > 0 ? this.nextDataId : this.prevDataId)
}
this.resetPageBinding();
}
})
},
resetPageBinding () {
this.direction = ''
this.flipTouchTime = 0
this.startX = 0
this.startY = 0
this.$nextTick(function () {
this.isTouch = false
this.disableTouch = false
})
}
}
}

View File

@ -0,0 +1,134 @@
.yingbing-read-page-flip {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.yingbing-flip {
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 */
}
.flip-item {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.flip-item-wrapper {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.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: 30rpx;
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 {
flex: 1;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.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-item-bg {
position: absolute;
/* #ifdef APP-NVUE */
box-shadow: 0 20rpx 20rpx rgba(0,0,0,0.2);
/* #endif */
}
.flip-item-shadow {
position: absolute;
width: 0;
top: 0;
bottom: 0;
right: 0;
/* #ifdef APP-NVUE */
background-image: linear-gradient(to right, rgba(255,255,255, 0), rgba(0,0,0,.5));
opacity: 0.5;
/* #endif */
}

View File

@ -0,0 +1,154 @@
import Util from '../../../js_sdk/util.js'
// #ifdef APP-NVUE
import FlipBindingx from './bindingx.js'
// #endif
export default {
// #ifdef APP-NVUE
mixins: [FlipBindingx],
// #endif
computed: {
dataReverse () {
let data = JSON.parse(JSON.stringify(this.pages))
return data.reverse()
},
current () {
return this.dataReverse.findIndex(item => item.dataId == this.currentDataId)
},
prevDataId () {
return this.dataReverse[this.current + 1] && this.dataReverse[this.current + 1].dataId
},
nextDataId () {
return this.dataReverse[this.current - 1] && this.dataReverse[this.current - 1].dataId
},
flipProp () {
return {
prevDataId: this.prevDataId,
nextDataId: this.nextDataId,
currentDataId: this.currentDataId,
pageType: this.options.pageType,
pageTo: this.pageTo
}
}
},
data() {
return {
currentDataId: -1,
isShow: false,
viewWidth: 0,
viewHeight: 0,
pageTo: 0,
moreLoading: false,
initLoading: true,
loadingText: '正在加载内容',
loadStatus: 'none',
loadChapter: -1,
loadValue: 0
}
},
mounted () {
if ( this.pageType != 'scroll' ) {
this.$nextTick(function () {
setTimeout(() => {
this.getViewRect()
}, 50)
})
}
},
methods: {
//翻往上一页
pagePrevWxs () {
this.pageTo = 0
this.$nextTick(function(){
this.pageTo = -1
})
},
//翻往下一页
pageNextWxs () {
this.pageTo = 0
this.$nextTick(function(){
this.pageTo = 1
})
},
reload () {
if ( this.loadStatus == 'fail' || this.loadStatus == 'timeout' ) {
this.initLoading = false
this.loadingText = '正在加载内容'
this.loadStatus = 'none';
this.loadmore(this.loadChapter, this.loadValue);
this.loadChapter = -1;
this.loadValue = 0;
}
},
loadmore (chapter, value) {
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: value > 0 ? 'next' : 'prev'
});
this.preload(chapter)
this.moreLoading = false;
} else if ( status == 'fail' ) {
this.loadStatus = status;
this.loadingText = '请求失败,点击重试'
this.initLoading = true
this.loadChapter = chapter;
this.loadValue = value;
} else {
this.loadStatus = status;
this.loadingText = '请求超时,点击重试'
this.initLoading = true
this.loadChapter = chapter;
this.loadValue = value;
}
})
},
getViewRect () {
return new Promise(resolve => {
Util.getRect('.yingbing-flip', this.$refs.yingbingFlip, this).then(res => {
this.viewWidth = res.width
this.viewHeight = res.height
this.isShow = true
resolve(true)
})
})
},
onChange(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.$emit('change', pageInfo, this.pages)
const nextType = this.pages[index + value] && this.pages[index + value].type
const loadings = ['nextLoading', 'prevLoading']
if ( loadings.indexOf(this.pages[index].type) >-1 || loadings.indexOf(nextType) >-1) {
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.loadmore(loadChapter, value)
}
}
}
}
}

View File

@ -0,0 +1,569 @@
<template>
<view
class="yingbing-flip"
ref="yingbingFlip"
:style="{
'background-color': options.bgColor
}"
:prop="flipProp"
:change:prop="flip.propWatcher"
@touchstart="flip.touchstart"
@touchmove="flip.touchmove"
@touchend="flip.touchend">
<template v-if="isShow">
<view v-if="(item.dataId == currentDataIdSync || item.dataId == prevDataId || item.dataId == nextDataId)"
class="flip-item"
:class="'flip-item_' + item.dataId"
v-for="(item, index) in dataReverse"
:key="item.dataId"
:style="{
'transform': item.dataId < currentDataIdSync ? `translateX(${-viewWidth}px)` : ''
}">
<view
class="flip-item-wrapper"
:class="'flip-item-wrapper_' + item.dataId"
:style="{
'padding-left': options.slide + 'px',
'padding-right': options.slide + 'px',
'padding-top': options.topGap + 'px',
'padding-bottom': options.bottomGap + 'px',
'background': options.bgColor,
'transform': item.dataId < currentDataIdSync ? options.pageType == 'real' ? `translateX(${viewWidth}px)` : 'translateX(0)' : ''
}">
<view class="flip-item-header" v-if="options.headerShow">
<text class="flip-item-header-text" :style="{
color: options.color
}">{{item.title}}</text>
</view>
<template v-if="item.type == 'text'">
<view class="flip-item-text flip-item-content"
>
<text class="flip-text"
v-for="(text, i) in item.text" :key="i"
:style="{
'margin-top': options.lineHeight + 'px',
'height': options.fontSize + 'px',
'font-size': options.fontSize + 'px',
'color': options.color
}">{{text}}</text>
</view>
</template>
<template v-else-if="item.type == 'custom'">
<view class="flip-custom flip-item-content" v-html="item.text">
</view>
</template>
<template v-else-if="item.type == 'slot'">
<view class="flip-slot flip-item-content">
<slot :name="item.text"></slot>
</view>
</template>
<template v-else-if="item.type == 'nextLoading' || item.type == 'prevLoading' ">
<view class="flip-loading flip-item-content">
<text :style="{
'color': options.color,
'font-size': options.fontSize + 'px'
}">正在加载内容</text>
</view>
</template>
<template v-else-if="item.type == 'top' || item.type == 'bottom' ">
<view class="flip-loading flip-item-content">
<text :style="{
'color': options.color,
'font-size': options.fontSize + 'px'
}">{{item.type == 'top' ? '前面已经没有了' : '后面已经没有了'}}</text>
</view>
</template>
<template v-else>
<view class="flip-loading flip-item-content">
<text :style="{
'color': options.color,
'font-size': options.fontSize + 'px'
}">未知类型页面</text>
</view>
</template>
<view class="flip-item-footer" v-if="options.footerShow">
<text class="flip-item-footer-text" :style="{
color: options.color
}">{{filterDate()}}</text>
<text class="flip-item-footer-text" :style="{
color: options.color
}">{{filterPage(item)}}</text>
<battery :color="options.color" style="opacity: 0.5"></battery>
</view>
</view>
<view
class="flip-item-bg"
:class="'flip-item-bg_' + item.dataId"
:style="{
left: viewWidth + 'px',
width: viewWidth + 'px',
height: (viewHeight * 1.5) + 'px',
top: (viewHeight / 2 - (viewHeight * 1.5) / 2) + 'px',
background: options.bgColor,
}"></view>
<view class="flip-item-shadow" :class="'flip-item-shadow_' + item.dataId"></view>
</view>
</template>
</view>
</template>
<script>
import Util from '../../../js_sdk/util.js'
import Battery from '../battery.vue'
export default {
components: {
Battery
},
props: {
currentDataId: {
type: Number,
default: -1
},
data: {
type: Array,
default () {
return new Array
}
},
options: {
type: Object,
default () {
return new Object
}
}
},
computed: {
dataReverse () {
let data = JSON.parse(JSON.stringify(this.data))
data.reverse()
return data
},
prevDataId () {
return this.dataReverse[this.current + 1] && this.dataReverse[this.current + 1].dataId
},
nextDataId () {
return this.dataReverse[this.current - 1] && this.dataReverse[this.current - 1].dataId
},
flipProp () {
return {
current: this.current,
prevDataId: this.prevDataId,
nextDataId: this.nextDataId,
currentDataId: this.currentDataIdSync,
pageType: this.options.pageType,
pageTo: this.pageTo
}
}
},
data () {
return {
isShow: false,
viewWidth: 0,
viewHeight: 0,
currentDataIdSync: -1,
current: -1,
pageTo: 0
}
},
mounted () {
this.currentDataIdSync = this.currentDataId
this.current = this.dataReverse.findIndex(item => item.dataId == this.currentDataIdSync)
this.$nextTick(function () {
setTimeout(() => {
// #ifndef APP-NVUE
const query = uni.createSelectorQuery().in(this);
query.select('.yingbing-flip').boundingClientRect(data => {
this.viewWidth = data.width
this.viewHeight = data.height
this.isShow = true
}).exec();
// #endif
// #ifdef APP-NVUE
uni.requireNativePlugin('dom').getComponentRect(this.$refs.yingbingFlip, res => {
this.viewWidth = res.size.width
this.viewHeight = res.size.height
this.isShow = true
})
// #endif
}, 50)
})
},
methods: {
onChange (e) {
this.current = e.current
this.currentDataIdSync = this.dataReverse[this.current].dataId
this.$emit('change', this.currentDataIdSync)
},
pagePrev () {
this.pageTo = 0
this.$nextTick(function(){
this.pageTo = -1
})
},
pageNext () {
this.pageTo = 0
this.$nextTick(function(){
this.pageTo = 1
})
},
filterPage (pageInfo) {
const nowChapters = this.data.filter(item => item.chapter == pageInfo.chapter && (item.type == 'text' || item.type == 'custom' || item.type == 'slot'))
let currentPage = nowChapters.findIndex(item => item.dataId == pageInfo.dataId)
if ( currentPage > -1 ) {
return (currentPage + 1) + ' / ' + nowChapters.length
} else {
return pageInfo.type == 'top' ? '最前面' : pageInfo.type == 'bottom' ? '最后面' : pageInfo.type.indexOf('Loading') > -1 ? '请等待' : ''
}
},
filterDate () {
let date = new Date()
return Util.zeroize(date.getHours()) + ':' + Util.zeroize(date.getMinutes())
}
},
watch: {
currentDataId (newVal) {
this.currentDataIdSync = newVal
this.current = this.dataReverse.findIndex(item => item.dataId == this.currentDataIdSync)
},
data (newVal) {
this.$nextTick(function () {
this.current = this.dataReverse.findIndex(item => item.dataId == this.currentDataIdSync)
})
}
}
}
</script>
<!-- #ifdef APP-VUE || H5 || MP-QQ || MP-WEIXIN -->
<script lang="wxs" module="flip">
function touchstart (event, ins) {
var state = ins.getState()
if ( state.isTouch || state.disableTouch ) {
return
}
state.isTouch = true
state.touchTime = 0
state.interval = true
setInterval(ins)
var touch = event.touches[0]
state.startX = touch.pageX
state.startY = touch.pageY
}
function touchmove (event, ins) {
event.preventDefault && event.preventDefault()
var state = ins.getState()
if ( state.isTouch && (state.pageType == 'real' || state.pageType == 'cover') && !state.disableTouch ) {
var touch = event.touches[0]
if (state.direction) {
var rect = ins.getBoundingClientRect()
var height = rect.height / 2;
var maxDeg = height / 5;
state.rotate = state.direction == 'next' ? ((touch.pageY - height) / maxDeg) : -((touch.pageY - height) / maxDeg);
state.offset = touch.pageX - state.startX;
if ( (state.offset > 0 && state.direction == 'next') || (state.offset < 0 && state.direction == 'prev') ) {
state.offset = 0
}
if ( Math.abs(state.offset) <= rect.width ) {
animation(state.offset, 0, ins);
}
} else {
if ( touch.pageX < state.startX ) {
if ( state.nextDataId ) {
state.direction = 'next'
}
} else {
if ( state.prevDataId ) {
state.direction = 'prev'
}
}
}
}
}
function touchend (event, ins) {
var state = ins.getState()
clearInterval(ins)
if ( state.isTouch && !state.disableTouch ) {
var rect = ins.getBoundingClientRect()
if ( !state.direction && state.touchTime <= 200 ) {
//
if (state.startX > (rect.width / 4) * 3) {
if ( state.nextDataId ) {
state.direction = 'next'
}
}
if (state.startX < (rect.width / 4)) {
if ( state.prevDataId ) {
state.direction = 'prev'
}
}
}
if (state.direction) {
state.disableTouch = true
if (state.touchTime <= 200) {
var duration = (state.pageType == 'real' || state.pageType == 'cover') ? 200 : 0
var value = state.direction == 'next' ? 1 : -1;
animation(-value * rect.width, duration, ins);
ins.setTimeout(function () {
reset(-value * rect.width, ins);
state.current -= value
ins.callMethod('onChange', {
current: state.current
})
}, duration)
} else {
var duration = (state.pageType == 'real' || state.pageType == 'cover') ? 100 : 0
if (Math.abs(state.offset) >= rect.width / 4) {
var value = state.direction == 'next' ? 1 : -1;
animation(-value * rect.width, duration, ins);
ins.setTimeout(function () {
reset(-value * rect.width, ins);
state.current -= value
ins.callMethod('onChange', {
current: state.current
})
}, duration)
} else {
animation(0, duration, ins);
ins.setTimeout(function () {
reset(0, ins);
}, duration)
}
}
} else {
reset(0, ins)
}
}
}
function propWatcher (newVal, oldVal, ins) {
ins.setTimeout(function () {
var state = ins.getState()
state.current = newVal.current
state.currentDataId = newVal.currentDataId
state.prevDataId = newVal.prevDataId
state.nextDataId = newVal.nextDataId
state.pageType = newVal.pageType
if (newVal.pageTo != (oldVal && oldVal.pageTo)) {
if ( !state.disableTouch ) {
if ( newVal.pageTo == -1 && state.prevDataId ) {
state.isTouch = true
state.startX = 1
state.touchTime = 0
state.direction = 'prev'
touchend(null, ins)
}
if ( newVal.pageTo == 1 && state.nextDataId ) {
state.isTouch = true
var rect = ins.getBoundingClientRect()
state.startX = rect.width
state.touchTime = 0
state.direction = 'next'
touchend(null, ins)
}
}
}
}, 50)
}
function setInterval (ins) {
var state = ins.getState()
state.touchTimer = ins.setTimeout(function () {
state.touchTime += 10
if ( state.interval ) {
setInterval(ins)
}
}, 10)
}
function clearInterval (ins) {
var state = ins.getState()
state.interval = false
if ( state.touchTimer ) {
ins.clearTimeout(state.touchTimer)
state.touchTimer = null
}
}
function reset (offset, ins) {
var state = ins.getState()
var rect = ins.getBoundingClientRect()
if ( state.direction ) {
var late = state.direction == 'next' ? offset : offset - rect.width;
var currentDataId = state.direction == 'next' ? state.currentDataId : state.prevDataId
if ( currentDataId ) {
ins.selectComponent('.flip-item_' + currentDataId).setStyle({
transform: 'translateX(' + (-late) + 'px)',
'box-shadow': '',
transition: ''
})
if ( state.pageType == 'real' ) {
ins.selectComponent('.flip-item-bg_' + currentDataId).setStyle({
transform: 'translateX(' + late + 'px) rotateZ(' + state.rotate + 'deg)',
'box-shadow': '',
transition: ''
})
}
ins.selectComponent('.flip-item-shadow_' + currentDataId).setStyle({
'box-shadow': '',
transition: ''
})
}
}
state.direction = null
state.isTouch = false
state.disableTouch = false
state.offset = 0
state.touchTime = 0
state.startX = 0
state.startY = 0
}
function animation (offset, duration, ins) {
var state = ins.getState()
var rect = ins.getBoundingClientRect()
var late = state.direction == 'next' ? offset : offset - rect.width;
var currentDataId = state.direction == 'next' ? state.currentDataId : state.prevDataId
ins.selectComponent('.flip-item_' + currentDataId).setStyle({
transform: 'translateX(' + late + 'px)',
'box-shadow': state.pageType == 'real' ? '0 0 30px 20px rgba(0,0,0,0.4)' : state.pageType == 'cover' ? '0 0 10px 5px rgba(0,0,0,0.3)' : '',
transition: duration > 0 ? 'transform ' + duration + 'ms' : ''
})
if ( state.pageType == 'real' ) {
ins.selectComponent('.flip-item-wrapper_' + currentDataId).setStyle({
transform: 'translateX(' + (-late) + 'px)',
transition: duration > 0 ? 'transform ' + duration + 'ms' : ''
})
ins.selectComponent('.flip-item-bg_' + currentDataId).setStyle({
transform: 'translateX(' + late + 'px) rotateZ(' + state.rotate + 'deg)',
'box-shadow': '-5px 0 20px rgba(0,0,0,0.1)',
transition: duration > 0 ? 'transform ' + duration + 'ms, ' + 'boxShadow ' + duration + 'ms' : ''
})
ins.selectComponent('.flip-item-shadow_' + currentDataId).setStyle({
'box-shadow': '0 0 60px ' + (Math.abs(late) > 30 ? 30 : Math.abs(late)) + 'px rgba(0,0,0,0.4)',
transition: duration > 0 ? 'boxShadow ' + duration + 'ms' : ''
})
} else {
ins.selectComponent('.flip-item-wrapper_' + currentDataId).setStyle({
transform: 'translateX(0px)',
transition: duration > 0 ? 'transform ' + duration + 'ms' : ''
})
ins.selectComponent('.flip-item-shadow_' + currentDataId).setStyle({
'box-shadow': '0 0 60px 0 rgba(0,0,0,0.5)',
transition: duration > 0 ? 'boxShadow ' + duration + 'ms' : ''
})
}
}
module.exports = {
touchstart: touchstart,
touchmove: touchmove,
touchend: touchend,
propWatcher: propWatcher
}
</script>
<!-- #endif -->
<style scoped>
.yingbing-flip {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
box-sizing: border-box;
overflow: hidden;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
position: relative;
}
.flip-item {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.flip-item-wrapper {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.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;
}
.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 {
flex: 1;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
/* #endif */
}
.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;
font-family: Microsoft YaHei, 微软雅黑;
/* #endif */
}
.flip-loading {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.flip-item-bg {
position: absolute;
}
.flip-item-shadow {
position: absolute;
width: 0;
top: 0;
bottom: 0;
right: 0;
}
</style>

View File

@ -0,0 +1,208 @@
function touchstart (event, ins) {
var state = ins.getState()
if ( state.isTouch || state.disableTouch ) {
return
}
state.isTouch = true
state.touchTime = 0
state.interval = true
setInterval(ins)
var touch = event.touches[0]
state.startX = touch.pageX
state.startY = touch.pageY
}
function touchmove (event, ins) {
event.preventDefault && event.preventDefault()
var state = ins.getState()
if ( state.isTouch && (state.pageType == 'real' || state.pageType == 'cover') && !state.disableTouch ) {
var touch = event.touches[0]
if (state.direction) {
var rect = ins.getBoundingClientRect()
var height = rect.height / 2;
var maxDeg = height / 5;
state.rotate = state.direction == 'next' ? ((touch.pageY - height) / maxDeg) : -((touch.pageY - height) / maxDeg);
state.offset = touch.pageX - state.startX;
if ( (state.offset > 0 && state.direction == 'next') || (state.offset < 0 && state.direction == 'prev') ) {
state.offset = 0
}
if ( Math.abs(state.offset) <= rect.width ) {
animation(state.offset, 0, ins)
}
} else {
if ( touch.pageX < state.startX ) {
if ( state.nextDataId ) {
state.direction = 'next'
}
} else {
if ( state.prevDataId ) {
state.direction = 'prev'
}
}
}
}
}
function touchend (event, ins) {
var state = ins.getState()
clearInterval(ins)
if ( state.isTouch && !state.disableTouch ) {
var rect = ins.getBoundingClientRect()
if ( !state.direction && state.touchTime <= 200 ) {
//获取点击位置,判断向哪里翻页
if (state.startX > (rect.width / 4) * 3) {
if ( state.nextDataId ) {
state.direction = 'next'
}
}
if (state.startX < (rect.width / 4)) {
if ( state.prevDataId ) {
state.direction = 'prev'
}
}
}
if (state.direction) {
state.disableTouch = true
if (state.touchTime <= 200) {
var duration = (state.pageType == 'real' || state.pageType == 'cover') ? 200 : 0
var value = state.direction == 'next' ? 1 : -1;
animation(-value * rect.width, duration, ins);
ins.setTimeout(function () {
reset(-value * rect.width, ins);
ins.callMethod('onChange', value > 0 ? state.nextDataId : state.prevDataId)
}, duration)
} else {
var duration = (state.pageType == 'real' || state.pageType == 'cover') ? 100 : 0
if (Math.abs(state.offset) >= rect.width / 4) {
var value = state.direction == 'next' ? 1 : -1;
animation(-value * rect.width, duration, ins);
ins.setTimeout(function () {
reset(-value * rect.width, ins);
ins.callMethod('onChange', value > 0 ? state.nextDataId : state.prevDataId)
}, duration)
} else {
animation(0, duration, ins);
ins.setTimeout(function () {
reset(0, ins);
}, duration)
}
}
} else {
reset(0, ins)
}
}
}
function propWatcher (newVal, oldVal, ins) {
if ( oldVal ) {
var state = ins.getState()
state.currentDataId = newVal.currentDataId
state.prevDataId = newVal.prevDataId
state.nextDataId = newVal.nextDataId
state.pageType = newVal.pageType
if (newVal.pageTo != oldVal.pageTo) {
if ( !state.disableTouch ) {
if ( newVal.pageTo == -1 && state.prevDataId ) {
state.isTouch = true
state.startX = 1
state.touchTime = 0
state.direction = 'prev'
touchend(null, ins)
}
if ( newVal.pageTo == 1 && state.nextDataId ) {
state.isTouch = true
var rect = ins.getBoundingClientRect()
state.startX = rect.width
state.touchTime = 0
state.direction = 'next'
touchend(null, ins)
}
}
}
}
}
function setInterval (ins) {
var state = ins.getState()
state.touchTimer = ins.setTimeout(function () {
state.touchTime += 10
if ( state.interval ) {
setInterval(ins)
}
}, 10)
}
function clearInterval (ins) {
var state = ins.getState()
state.interval = false
if ( state.touchTimer ) {
ins.clearTimeout(state.touchTimer)
state.touchTimer = null
}
}
function reset (offset, ins) {
var state = ins.getState()
var rect = ins.getBoundingClientRect()
if ( state.direction ) {
var late = state.direction == 'next' ? offset : offset - rect.width;
var currentDataId = state.direction == 'next' ? state.currentDataId : state.prevDataId
if ( currentDataId ) {
var draw = function () {
ins.selectComponent('.flip-item_' + currentDataId).setStyle({
transform: 'translateX(' + late + 'px)',
'box-shadow': '',
transition: ''
})
if ( state.pageType == 'real' ) {
ins.selectComponent('.flip-item-bg_' + currentDataId).setStyle({
transform: 'translateX(' + late + 'px) rotateZ(' + state.rotate + 'deg)',
'box-shadow': '',
transition: ''
})
}
ins.selectComponent('.flip-item-shadow_' + currentDataId).setStyle({
'box-shadow': '',
transition: ''
})
}
ins.requestAnimationFrame(draw)
}
}
state.direction = null
state.isTouch = false
state.disableTouch = false
state.offset = 0
state.touchTime = 0
state.startX = 0
state.startY = 0
}
function animation (offset, duration, ins) {
var state = ins.getState()
var rect = ins.getBoundingClientRect()
var late = state.direction == 'next' ? offset : offset - rect.width;
var currentDataId = state.direction == 'next' ? state.currentDataId : state.prevDataId
var draw = function () {
ins.selectComponent('.flip-item_' + currentDataId).setStyle({
transform: 'translateX(' + late + 'px)',
'box-shadow': state.pageType == 'real' ? '0 0 30px 20px rgba(0,0,0,0.4)' : state.pageType == 'cover' ? '0 0 10px 5px rgba(0,0,0,0.3)' : '',
transition: duration > 0 ? 'transform ' + duration + 'ms' : ''
})
if ( state.pageType == 'real' ) {
ins.selectComponent('.flip-item-wrapper_' + currentDataId).setStyle({
transform: 'translateX(' + (-late) + 'px)',
transition: duration > 0 ? 'transform ' + duration + 'ms' : ''
})
ins.selectComponent('.flip-item-bg_' + currentDataId).setStyle({
transform: 'translateX(' + late + 'px) rotateZ(' + state.rotate + 'deg)',
'box-shadow': '-5px 0 20px rgba(0,0,0,0.1)',
transition: duration > 0 ? 'transform ' + duration + 'ms, ' + 'boxShadow ' + duration + 'ms' : ''
})
ins.selectComponent('.flip-item-shadow_' + currentDataId).setStyle({
'box-shadow': '0 0 60px 30px rgba(0,0,0,0.4)',
transition: duration > 0 ? 'boxShadow ' + duration + 'ms' : ''
})
}
}
ins.requestAnimationFrame(draw)
}
module.exports = {
touchstart: touchstart,
touchmove: touchmove,
touchend: touchend,
propWatcher: propWatcher
}

View File

@ -0,0 +1,194 @@
<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>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,214 @@
<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>

View File

@ -0,0 +1,91 @@
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()
}
}
}

View File

@ -0,0 +1,116 @@
<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>

View File

@ -0,0 +1,119 @@
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 = ''
}
}
}

View File

@ -0,0 +1,125 @@
<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>

View File

@ -0,0 +1,95 @@
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
}

View File

@ -0,0 +1,103 @@
export default {
methods: {
computedNochapter (data) {
const reg = new RegExp(/(第+[一二两三四五六七八九十○零百千万亿0-9※✩★☆]{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)
}
}
}
}

View File

@ -0,0 +1,964 @@
<template>
<view class="page" :id="'page' + dataId" :prop="pageProp" :change:prop="page.pagePropChange">
<!-- 翻页模式 -->
<view class="box">
<view class="content"
:id="'content' + dataId"
v-if="pageType != 'scroll'"
@touchstart="page.pageTouchstart"
@touchmove="page.pageTouchmove"
@touchend="page.pageTouchend"></view>
<view class="content" style="z-index: -1000;" :id="'computed' + dataId"></view>
</view>
<!-- 滚动模式 -->
<view
:id="'scroll-box' + dataId"
class="scroll-box"
:style="{
'color': color,
'padding-left': slide + 'px',
'padding-right': slide + 'px',
'padding-top': topGap + 'px',
'padding-bottom': bottomGap + 'px',
'background': bgColor}"
v-if="pageType == 'scroll'"></view>
<div class="loading" v-if="initLoading" :style="{background: bgColor, 'font-size': fontSize + 'px'}">
<!-- <page-refresh :color="color">正在加载内容</page-refresh> -->
</div>
</view>
</template>
<script>
export default {
props: {
//IDdom
dataId: {
type: String,
default () {
let mydate = new Date();
return 'cms' + mydate.getMinutes() + mydate.getSeconds() + mydate.getMilliseconds() + Math.round(Math.random() * 10000);
}
},
//
color: {
type: String,
default: '#333333'
},
//px
fontSize: {
type: String | Number,
default: 15
},
//
bgColor: {
type: String,
default: '#fcd281'
},
//
pageType: {
type: String,
default: 'scroll'
},
//px
lineHeight: {
type: Number | String,
default: 15
},
//px
slide: {
type: Number | String,
default: 40
},
//px
topGap: {
type: Number | String,
default: 10
},
//px
bottomGap: {
type: Number | String,
default: 10
}
},
data () {
return {
title: '',
content: '',
start: 0,
loading: false,//
upper: false,//
lower: false,//
restart: false,//
preLoading: false,//
initLoading: true,
chapters: [],
pageTo: 0
}
},
computed: {
pageProp () {
return {
content: this.content,
start: this.start,
dataId: this.dataId,
color: this.color,
bgColor: this.bgColor,
slide: this.slide,
topGap: this.topGap,
bottomGap: this.bottomGap,
fontSize: this.fontSize,
pageType: this.pageType,
lineHeight: this.lineHeight,
restart: this.restart,
pageTo: this.pageTo
};
}
},
methods: {
//
init (data) {
this.content = data.content;
this.start = data.start
this.title = data.title
this.restart = true;
this.getCatalog(this.content);
},
//
change (data) {
this.initLoading = true;
this.start = data.start;
this.restart = true;
},
showToast (e) {
uni.showToast({
title: e.title,
icon: 'none'
})
},
resetPage () {
this.restart = true;
},
pagePrev () {
this.pageTo = 0
this.$nextTick(function(){
this.pageTo = -1
})
},
pageNext () {
this.pageTo = 0
this.$nextTick(function(){
this.pageTo = 1
})
},
//
currentChange (e) {
const start = e.currentInfo.start
const chapterIndex = this.chapters.findIndex((chapter, key) => {
if ( key < this.chapters.length - 1 ) {
return start >= chapter.start && start < this.chapters[parseInt(key) + 1].start
} else {
return start >= chapter.start
}
})
if ( chapterIndex > -1 ) {
e.currentInfo.chapter = this.chapters[chapterIndex].chapter
e.currentInfo.title = this.chapters[chapterIndex].title
} else {
e.currentInfo.chapter = this.chapters[0].chapter
e.currentInfo.title = this.chapters[0].title
}
this.$emit('currentChange', e.currentInfo);
},
//便使
resetPageProp () {
this.restart = false;
this.isNewChapter = false;
},
resetInitLoading () {
this.initLoading = false
},
//使
getCatalog (content) {
const reg = new RegExp(/(第?[一二两三四五六七八九十○零百千万亿0-9※✩★☆]{1,6}[章回卷节折篇幕集部]?[、.-\s][^\n]*)[_,-]?/g);
let match = '';
let catalog = [];
let chapter = 0
while ((match = reg.exec(content)) != null) {
chapter++
catalog.push({
title: match[0],
start: match.index,
chapter: chapter
})
}
this.chapters = catalog.length > 0 ? catalog : [{
chapter: 1,
start: 0,
title: this.title || '整章'
}];
this.$emit('setCatalog', catalog);
}
}
}
</script>
<!-- #ifdef H5 || APP-VUE -->
<script lang="renderjs" module="page" type="module">
let myPageDom;
export default {
data () {
return {
viewHeight: 0,
viewWidth: 0,
updownloading: false,
currentInfo: {
start: 0,
end: 0,
text: ''
},
touchstart: {
x: 0,
y: 0
},
pageWating: false,//
moveX: 0,//
pageEl: '',//
pageDirection: 'next',//
touchTime: 0//
}
},
mounted () {
this.initDom.bind(this);
},
methods: {
initDom () {
myPageDom = page.init(document.getElementById('page' + this.pageProp.dataId));
// view 访
myPageDom.setOption(this.pageProp);
},
//
bindScrollEvent () {
let scrollBox = document.getElementById('scroll-box' + this.pageProp.dataId);
if ( scrollBox ) {
scrollBox.onscroll = () => {
let scrollItems = scrollBox.getElementsByClassName('scroll-item');
let scrollTop = scrollBox.scrollTop + this.pageProp.topGap + this.pageProp.bottomGap;
for ( let i = 0; i < scrollItems.length; i++ ) {
let offsetTop1 = scrollItems[i].offsetTop;
let offsetTop2 = i < scrollItems.length - 1 ? scrollItems[i+1].offsetTop : offsetTop1 + 2;
if ( scrollTop >= offsetTop1 && scrollTop < offsetTop2 ) {
let start = parseInt(scrollItems[i].getAttribute('start'));
let end = parseInt(scrollItems[i].getAttribute('end'));
if ( this.currentInfo.start != start ) {
this.currentInfo.start = start;
this.currentInfo.end = end;
this.currentInfo.text = scrollItems[i].innerText;
this.triggerCurrentChange(this.currentInfo)
}
}
}
if ( Math.ceil(scrollBox.scrollTop + scrollBox.offsetHeight) >= scrollBox.scrollHeight ) {//
if ( this.updownloading ) {
return;
}
this.updownloading = true;
let end = parseInt(scrollBox.lastChild.getAttribute('end'));
if ( end < this.pageProp.content.length - 1 ) {
this.drawText(end, 'next');
} else {
this.triggerShowToast('后面已经没有了')
}
this.updownloading = false;
}
if ( scrollBox.scrollTop <= 0 ) {//
if ( this.updownloading ) {
return;
}
this.updownloading = true;
let start = parseInt(scrollBox.firstChild.getAttribute('start'));
if ( start > 0 ) {
this.drawText(start, 'prev');
} else {
this.triggerShowToast('前面已经没有了')
}
this.updownloading = false;
}
};
}
},
//
computedText (start) {
let parent = document.getElementById('computed' + this.pageProp.dataId);
this.viewWidth = parent.offsetWidth;
this.viewHeight = parent.offsetHeight;
let computedCanvas = this.createComputedCanvas(parent);
let context = computedCanvas.getContext('2d');
context.font = this.pageProp.fontSize + 'px 微软雅黑';
context.fillStyle = this.pageProp.color;
context.lineWidth = 1;
let pageHeight = this.pageProp.fontSize + this.pageProp.lineHeight;
let strs = [];
let page = {
start: start,
end: 0,
text: []
}
let length = 0;
const contentSync = this.pageProp.content.substr(start);
let lastIndex = 0;
while ( pageHeight <= this.viewHeight - this.pageProp.topGap - this.pageProp.bottomGap ) {
strs.push('');
let lineWidth = 0;
for ( let i = lastIndex; i < contentSync.length; i++ ) {
lineWidth += context.measureText(contentSync[i]).width;
if ( JSON.stringify(contentSync[i]) == JSON.stringify('\r') || JSON.stringify(contentSync[i]) == JSON.stringify('\n') ) {
length += 1
page.end = page.start + length;
lastIndex = i + 1;
break;
} else if ( lineWidth >= this.viewWidth - (2 * this.pageProp.slide) ) {
lastIndex = i;
break;
} else {
strs[strs.length - 1] += contentSync[i];
length += 1;
page.end = page.start + length;
}
}
pageHeight += this.pageProp.fontSize + this.pageProp.lineHeight;
if ( page.end >= this.pageProp.content.length - 1 ) break;
}
page.text = strs;
return page;
},
//
computedPrevText (end) {
let parent = document.getElementById('computed' + this.pageProp.dataId);
this.viewWidth = parent.offsetWidth;
this.viewHeight = parent.offsetHeight;
let computedCanvas = this.createComputedCanvas(parent);
let context = computedCanvas.getContext('2d');
context.font = this.pageProp.fontSize + 'px 微软雅黑';
context.fillStyle = this.pageProp.color;
context.lineWidth = 1;
let pageHeight = this.pageProp.fontSize + this.pageProp.lineHeight;
let strs = [];
let page = {
start: 0,
end: end,
text: []
}
let length = 0;
let lastIndex1 = 0;
let lastIndex2 = 0;
while ( pageHeight <= this.viewHeight - this.pageProp.topGap - this.pageProp.bottomGap ) {
if ( end - length > 0 ) {
strs.unshift('');
let lineWidth = 0;
let contentSync = this.pageProp.content.substring(0, end);
for ( let i = lastIndex1 || contentSync.length - 1; i >= 0; i-- ) {
lineWidth += context.measureText(contentSync[i]).width;
if ( JSON.stringify(contentSync[i]) == JSON.stringify('\r') || JSON.stringify(contentSync[i]) == JSON.stringify('\n') ) {
lastIndex1 = i - 1;
length += 1
break;
} else if ( lineWidth >= this.viewWidth - (2 * this.pageProp.slide) ) {
lastIndex1 = i;
break;
} else {
strs[0] = contentSync[i] + strs[0];
length += 1
page.start = end - length;
}
if ( page.start == 0 ) break;
}
pageHeight += this.pageProp.fontSize + this.pageProp.lineHeight;
} else {
if ( this.pageProp.pageType != 'scroll' ) {
strs.push('');
let lineWidth = 0;
let contentSync = this.pageProp.content.substr(end);
for ( let i = lastIndex2; i < contentSync.length; i++ ) {
lineWidth += context.measureText(contentSync[i]).width;
if ( JSON.stringify(contentSync[i]) == JSON.stringify('\r') || JSON.stringify(contentSync[i]) == JSON.stringify('\n') ) {
lastIndex2 = i + 1;
length += 1
break;
} else if ( lineWidth >= this.viewWidth - (2 * this.pageProp.slide) ) {
lastIndex2 = i;
break;
} else {
strs[strs.length - 1] += contentSync[i];
length += 1;
page.end = page.start + length;
}
}
pageHeight += this.pageProp.fontSize + this.pageProp.lineHeight;
if ( page.end >= this.pageProp.content.length - 1 ) break;
} else {
break;
}
}
}
page.text = strs;
return page;
},
//
drawText (start, type = 'init') {
if ( this.pageProp.pageType != 'scroll' ) {
let parent = document.getElementById('content' + this.pageProp.dataId);
let page = type == 'prev' ? this.computedPrevText(start) : this.computedText(start);
let pageItem = this.createPageItem(parent, page, type);
let el = {
el: pageItem,
content: pageItem.getElementsByClassName('page-item-canvas')[0],
bg: pageItem.getElementsByClassName('page-item-bg')[0],
shadow: pageItem.getElementsByClassName('page-item-shadow')[0]
}
for ( let i = 0; i < page.text.length; i++ ) {
this.insetScrollText(el.content, page.text[i], this.pageProp.fontSize);
// context.font = this.pageProp.fontSize + 'px ';
// context.fillStyle = this.pageProp.color;
// context.fillText(pages[i].text[j], this.pageProp.slide, ((j + 1) * (this.pageProp.fontSize + this.pageProp.lineHeight)) + this.pageProp.topGap);
}
this.resetProp();
if ( type == 'init' ) {
if ( page.start > 0 ) {
this.drawText(page.start, 'prev')
}
if ( page.end < this.pageProp.content.length - 1 ) {
this.drawText(page.end, 'next')
}
this.currentInfo.start = parseInt(el.el.getAttribute('start'));
this.currentInfo.end = parseInt(el.el.getAttribute('end'));
this.currentInfo.text = el.content.innerText;
this.triggerCurrentChange(this.currentInfo);
this.triggerResetInitLoading();
} else if ( type == 'prev' ) {
this.pageAnimation(-this.viewWidth, 0, el);
if ( parent.getElementsByClassName('page-item').length > 3 ) parent.removeChild(parent.lastChild);
} else {
if ( parent.getElementsByClassName('page-item').length > 3 ) parent.removeChild(parent.firstChild);
}
} else {
let scrollBox = document.getElementById('scroll-box' + this.pageProp.dataId);
let page = type == 'prev' ? this.computedPrevText(start) : this.computedText(start);
let scrollItem = this.createScrollItem(scrollBox, page, type);
for ( let i = 0; i < page.text.length; i++ ) {
this.insetScrollText(scrollItem, page.text[i], this.pageProp.fontSize);
}
this.resetProp();
if ( type == 'init' ) {
if ( page.start > 0 ) {
this.drawText(page.start, 'prev')
}
if ( page.end < this.pageProp.content.length - 1 ) {
this.drawText(page.end, 'next')
}
let scrollItems = scrollBox.getElementsByClassName('scroll-item')
let offsetHeight = 0;
for ( let i = 0; i < scrollItems.length; i++ ) {
offsetHeight += i > 0 ? scrollItems[i - 1].offsetHeight : 0;
if ( this.currentInfo.start >= scrollItems[i].getAttribute('start') && this.currentInfo.start < scrollItems[i].getAttribute('end') ) {
scrollBox.scrollTop = offsetHeight;
this.currentInfo.text = scrollItems[i].innerText;
this.currentInfo.end = scrollItems[i].getAttribute('end');
}
}
this.bindScrollEvent();
this.triggerCurrentChange(this.currentInfo);
this.triggerResetInitLoading();
} else if ( type == 'prev' ) {
scrollBox.scrollTop = scrollItem.offsetHeight;
if ( scrollBox.getElementsByClassName('scroll-item').length > 3 ) scrollBox.removeChild(scrollBox.lastChild);
} else {
if ( scrollBox.getElementsByClassName('scroll-item').length > 3 ) scrollBox.removeChild(scrollBox.firstChild);
scrollBox.scrollTop = scrollBox.scrollHeight - scrollBox.lastChild.offsetHeight - scrollBox.offsetHeight;
}
}
},
//canvas
createComputedCanvas (el) {
if ( document.getElementsByClassName('computedCanvas')[0] ) return document.getElementsByClassName('computedCanvas')[0];
let canvasDom = document.createElement('canvas');
canvasDom.width = this.viewWidth;
canvasDom.height = this.viewHeight;
canvasDom.style.position = 'absolute';
canvasDom.style.top = 0;
canvasDom.style.left = 0;
canvasDom.setAttribute('class', 'computedCanvas');
el.appendChild(canvasDom);
return document.getElementsByClassName('computedCanvas')[0];
},
//
createPageItem (el, info, type) {
let pageItem = document.createElement('div');
pageItem.style.width = '100%';
pageItem.style.height = '100%';
pageItem.style.overflow = 'hidden';
pageItem.style.position = 'absolute';
pageItem.style.top = 0;
pageItem.style.left = 0;
pageItem.style.zIndex = -info.start;
if ( this.currentInfo.start == info.start ) {
pageItem.setAttribute('class', 'page-item page-item-actived page-item-start__' + info.start);
} else {
pageItem.setAttribute('class', 'page-item page-item-start__' + info.start);
}
pageItem.setAttribute('start', info.start);
pageItem.setAttribute('end', info.end);
// let canvas = document.createElement('canvas');
// canvas.width = this.viewWidth;
// canvas.height = this.viewHeight;
// canvas.style.position = 'absolute';
// canvas.style.top = 0;
// canvas.style.left = 0;
// // canvas.style.background = this.pageProp.bgColor;
// canvas.style.zIndex = -1;
// canvas.setAttribute('class', 'page-item-canvas');
let pageContent = document.createElement('div');
pageContent.style.width = this.viewWidth + 'px';
pageContent.style.height = this.viewHeight + 'px';
pageContent.style.position = 'absolute';
pageContent.style.top = 0;
pageContent.style.left = 0;
pageContent.style.background = this.pageProp.bgColor;
pageContent.style.color = this.pageProp.color;
pageContent.style.overflow = 'hidden';
pageContent.style.padding = `${this.pageProp.topGap}px ${this.pageProp.slide}px ${this.pageProp.bottomGap}px ${this.pageProp.slide}px`;
pageContent.style.boxSizing = 'border-box';
pageContent.setAttribute('class', 'page-item-canvas');
pageItem.appendChild(pageContent);
let pageBg = document.createElement('div');
pageBg.style.width = '100%';
pageBg.style.height = Math.sqrt(Math.pow(this.viewHeight, 2) + Math.pow(this.viewWidth, 2)) + 'px';
pageBg.style.boxShadow = '-5px 0 20px rgba(0,0,0,0.2)';
pageBg.style.position = 'absolute';
pageBg.style.top = '50%';
pageBg.style.left = '100%';
pageBg.style.transform = 'translateY(-50%)';
pageBg.style.background = this.pageProp.bgColor;
pageBg.setAttribute('class', 'page-item-bg');
pageItem.appendChild(pageBg);
let pageShadow = document.createElement('div');
pageShadow.style.width = '0';
pageShadow.style.height = '100%';
pageShadow.style.position = 'absolute';
pageShadow.style.top = 0;
pageShadow.style.right = 0;
pageShadow.style.zIndex = '9';
pageShadow.setAttribute('class', 'page-item-shadow');
pageItem.appendChild(pageShadow);
if ( type == 'prev' ) {
el.insertBefore(pageItem, el.firstChild);
} else {
el.appendChild(pageItem);
}
return document.getElementsByClassName('page-item-start__' + info.start)[0];
},
//
createScrollItem (el, info, type) {
let divDom = document.createElement('div');
divDom.style.width = '100%';
divDom.setAttribute('class', 'scroll-item scroll-start__' + info.start);
divDom.setAttribute('start', info.start);
divDom.setAttribute('end', info.end);
if ( type == 'prev' ) {
el.insertBefore(divDom, el.firstChild);
} else {
el.appendChild(divDom);
}
return document.getElementsByClassName('scroll-start__' + info.start)[0]
},
//
insetScrollText (el, text, height = 0) {
let pDom = document.createElement('p');
pDom.style.height = height ? height + 'px' : 'auto';
pDom.style.marginTop = this.pageProp.lineHeight + 'px';
pDom.style.fontSize = this.pageProp.fontSize + 'px';
pDom.style.whiteSpace = 'pre-wrap';
pDom.style.fontFamily = '"Microsoft YaHei",微软雅黑';
pDom.setAttribute('class', 'scroll-text');
pDom.innerHTML = text || ' ';
el.appendChild(pDom);
},
pageTouchstart (e) {
if ( this.pageWating ) {
return;
}
if ( e.touches.length == 1 ) {
this.touchTimer = window.setInterval(() => {
this.touchTime += 50;
}, 50)
let touch = e.touches[0];
this.touchstart.x = touch.pageX;
this.touchstart.y = touch.pageY;
}
},
pageTouchmove (e) {
if ( this.touchstart.x == 0 || (this.pageProp.pageType != 'real' && this.pageProp.pageType != 'cover') ) {
return;
}
if ( e.touches.length == 1 ) {
if ( this.pageEl ) {
let touch = e.touches[0];
let height = this.viewHeight / 2;
let maxDeg = height / 5;
let rotateZ = this.pageDirection == 'next' ? ((touch.pageY - height) / maxDeg) : -((touch.pageY - height) / maxDeg);
this.moveX = touch.pageX - this.touchstart.x;
if ( this.pageDirection == 'next' ) {
this.moveX > 0 ? this.moveX = 0 : null
} else {
this.moveX < 0 ? this.moveX = 0 : null
}
this.pageAnimation(this.moveX, rotateZ);
} else {
let touch = e.touches[0];
if ( touch.pageX < this.touchstart.x ) {
this.pageEl = this.getPageActived(0);
this.pageDirection = 'next'
} else {
this.pageEl = this.getPageActived(-1);
this.pageDirection = 'prev'
}
}
}
},
pageTouchend (e) {
window.clearInterval(this.touchTimer);
this.touchTimer = null
if ( this.touchstart.x == 0 ) {
return;
}
if ( !this.pageEl && this.touchTime <= 200 ) {
//
if (this.touchstart.x > (this.viewWidth / 4) * 3) {
this.pageEl = this.getPageActived(0);
this.pageDirection = 'next'
}
if (this.touchstart.x < (this.viewWidth / 4)) {
this.pageEl = this.getPageActived(-1);
this.pageDirection = 'prev'
}
}
this.touchstart.x = 0
if ( this.pageEl ) {
this.pageWating = true;
if ( this.touchTime <= 200 ) {
let duration = (this.pageProp.pageType == 'real' || this.pageProp.pageType == 'cover') ? 200 : 0
let value = this.pageDirection == 'next' ? 1 : -1;
this.pageDuration(duration);
this.$nextTick(() => {
this.pageAnimation(-value * this.viewWidth);
setTimeout(() => {
this.changePageActived(value);
this.resetPageMove();
}, duration + 50)
})
} else {
let duration = (this.pageProp.pageType == 'real' || this.pageProp.pageType == 'cover') ? 100 : 0
if ( Math.abs(this.moveX) >= this.viewWidth / 4 ) {
let value = this.pageDirection == 'next' ? 1 : -1;
this.pageDuration(duration);
this.$nextTick(() => {
this.pageAnimation(-value * this.viewWidth);
setTimeout(() => {
this.changePageActived(value);
this.resetPageMove();
}, duration + 50)
})
} else {
this.pageDuration(duration);
this.$nextTick(() => {
this.pageAnimation(0);
setTimeout(() => {
this.resetPageMove();
}, duration + 50)
})
}
}
}
},
//
resetPageMove () {
this.pageDuration(0);
this.isStart = false;
this.pageWating = false;
this.moveX = 0;
this.pageEl = '';
this.pageDirection = 'next';
this.touchTime = 0;
this.touchstart.x = 0;
this.touchstart.y = 0;
},
//
pageAnimation (moveX, rotateZ = 0, el) {
let lateX = this.pageDirection == 'next' ? moveX : moveX - this.viewWidth;
let pageEl = el || this.pageEl;
pageEl.el.style.transform = `translateX(${lateX}px)`;
pageEl.content.style.transform = this.pageProp.pageType == 'real' ? `translateX(${-lateX}px)` : pageEl.content.style.transform;
pageEl.bg.style.transform = this.pageProp.pageType == 'real' ? `translate(${lateX}px, -50%) rotateZ(${rotateZ}deg)` : pageEl.bg.style.transform;
pageEl.shadow.style.boxShadow = '0 0 60px ' + (this.pageProp.pageType == 'real' ? Math.abs(lateX) > 30 ? 30 : Math.abs(lateX) : 0) + 'px rgba(0,0,0,0.5)';
},
//
pageDuration (duration) {
this.pageEl.el.style.transition = duration > 0 ? 'transform ' + duration + 'ms' : '';
this.pageEl.content.style.transition = duration > 0 ? 'transform ' + duration + 'ms' : '';
this.pageEl.bg.style.transition = duration > 0 ? 'transform ' + duration + 'ms' : '';
this.pageEl.shadow.style.transition = duration > 0 ? 'box-shadow ' + duration + 'ms' : '';
},
//
getPageActived (value = 0) {
let boxs = document.getElementsByClassName('page-item');
for ( let i = 0; i < boxs.length; i++ ) {
if ( boxs[i].getAttribute('class').indexOf('page-item-actived') > 1 ) {
if ( boxs[i + value + 1] && boxs[i + value] ) {
return {
el: boxs[i + value],
content: boxs[i + value].getElementsByClassName('page-item-canvas')[0],
bg: boxs[i + value].getElementsByClassName('page-item-bg')[0],
shadow: boxs[i + value].getElementsByClassName('page-item-shadow')[0]
};
}
}
}
if ( value < 0 ) {
this.triggerShowToast('前面已经没有了')
} else {
this.triggerShowToast('后面已经没有了')
}
return false;
},
//
changePageActived (value) {
let content = document.getElementById('content' + this.pageProp.dataId);
let boxs = content.getElementsByClassName('page-item');
let index = -1
for ( let i = 0; i < boxs.length; i++ ) {
if ( boxs[i].getAttribute('class').indexOf('page-item-actived') > -1 ) {
index = i;
}
}
boxs[index].setAttribute('class', boxs[index].getAttribute('class').replace('page-item-actived', ''));
boxs[index + value].setAttribute('class', boxs[index + value].getAttribute('class') + ' page-item-actived');
this.currentInfo.start = parseInt(boxs[index + value].getAttribute('start'));
this.currentInfo.end = parseInt(boxs[index + value].getAttribute('end'));
this.currentInfo.text = boxs[index + value].getElementsByClassName('page-item-canvas')[0].innerText;
this.triggerCurrentChange(this.currentInfo);
if ( value < 0 && !boxs[index + value - 1] ) {
if ( this.updownloading ) {
return;
}
this.updownloading = true;
let start = parseInt(content.firstChild.getAttribute('start'));
if ( start > 0 ) {
this.drawText(start, 'prev');
} else {
this.triggerShowToast('前面已经没有了')
}
this.updownloading = false;
}
if ( value > 0 && !boxs[index + value + 1] ) {
if ( this.updownloading ) {
return;
}
this.updownloading = true;
let end = parseInt(content.lastChild.getAttribute('end'));
if ( end < this.pageProp.content.length - 1 ) {
this.drawText(end, 'next');
} else {
this.triggerShowToast('后面已经没有了')
}
this.updownloading = false;
}
if ( value < 0 ) {
if ( boxs[index + value].getAttribute('end') != boxs[index + value + 1].getAttribute('start') ) {
this.restartDrawText();
}
}
},
//
pagePropChange (newValue, oldValue) {
if ( newValue.fontSize != oldValue.fontSize ) {//
this.restartDrawText();
}
if ( newValue.lineHeight != oldValue.lineHeight ) {//
this.restartDrawText();
}
if ( newValue.color != oldValue.color || newValue.bgColor != oldValue.bgColor ) {//
if ( this.pageProp.pageType != 'scroll' ) {
// this.restartDrawText(this.currentInfo.chapter);
this.colorChange();
}
}
if ( newValue.pageType != oldValue.pageType ) {//
this.restartDrawText();
}
if ( newValue.restart != oldValue.restart ) {//
this.restartChange(newValue.restart);
}
if (newValue.pageTo != oldValue.pageTo) {
if ( this.pageProp.pageType != 'scroll' ) {
if ( newValue.pageTo == -1 ) {
if ( !this.pageWating ) {
this.touchstart.x = 1
this.pageEl = this.getPageActived(-1)
this.pageDirection = 'prev'
this.pageTouchend()
}
}
if ( newValue.pageTo == 1 ) {
if ( !this.pageWating ) {
this.touchstart.x = this.viewWidth
this.pageEl = this.getPageActived(0)
this.pageDirection = 'next'
this.pageTouchend()
}
}
} else {
if ( newValue.pageTo == -1 ) {
if ( this.currentInfo.start > 0 ) {
document.getElementsByClassName('scroll-item')[0].scrollIntoView({behavior: 'smooth', 'block': 'end'})
} else {
document.getElementById('scroll-box' + this.pageProp.dataId).scrollTop = 0
}
}
if ( newValue.pageTo == 1 ) {
if ( this.currentInfo.end < this.pageProp.content.length ) {
let items = document.getElementsByClassName('scroll-item')
items[items.length - 1].scrollIntoView({behavior: 'smooth'})
} else {
document.getElementById('scroll-box' + this.pageProp.dataId).scrollTop = document.getElementById('scroll-box' + this.pageProp.dataId).scrollHeight
}
}
}
}
},
//
restartDrawText () {
if ( this.pageProp.pageType == 'scroll' ) {
document.getElementById('scroll-box' + this.pageProp.dataId).innerHTML = '';
this.drawText(this.currentInfo.start);
} else {
document.getElementById('content' + this.pageProp.dataId).innerHTML = '';
this.drawText(this.currentInfo.start);
}
},
restartChange (newValue) {
if ( newValue ) {
this.currentInfo.start = this.pageProp.start;
this.restartDrawText();
}
},
colorChange () {
let items = document.getElementsByClassName('page-item');
for ( let i = 0; i < items.length; i++ ) {
items[i].getElementsByClassName('page-item-canvas')[0].style.background = this.pageProp.bgColor;
items[i].getElementsByClassName('page-item-bg')[0].style.background = this.pageProp.bgColor;
items[i].getElementsByClassName('page-item-canvas')[0].style.color = this.pageProp.color;
}
},
triggerCurrentChange (currentInfo) {
// #ifndef H5
UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', {
cid: this._$id,
method: 'currentChange',
args: {'currentInfo': currentInfo}
})
// #endif
// #ifdef H5
this.currentChange({'currentInfo': currentInfo})
// #endif
},
triggerShowToast (title) {
// #ifndef H5
UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', {
cid: this._$id,
method: 'showToast',
args: {'title': title}
})
// #endif
// #ifdef H5
this.showToast({'title': title})
// #endif
},
//
resetProp () {
// #ifndef H5
UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', {
cid: this._$id,
method: 'resetPageProp'
})
// #endif
// #ifdef H5
this.resetPageProp()
// #endif
},
//
triggerResetInitLoading () {
// #ifndef H5
UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', {
cid: this._$id,
method: 'resetInitLoading'
})
// #endif
// #ifdef H5
this.resetInitLoading()
// #endif
}
}
}
</script>
<!-- #endif -->
<style scoped>
.page {
width: 100vw;
height: 100vh;
position: relative;
}
.scroll {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.scroll-box {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
/* overflow-anchor: auto; */
overflow-y: auto;
}
.box {
width: 100%;
height: 100%;
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
overflow: hidden;
}
.content {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.loading {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,105 @@
<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.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>

View File

@ -0,0 +1,98 @@
.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;
}

View File

@ -0,0 +1,154 @@
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.options.fontSize + this.options.lineHeight, 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.options.fontSize + this.options.lineHeight), true)
this.scrollTimer = setTimeout(() => {
this.scrolling = false
clearTimeout(this.scrollTimer)
this.scrollTimer = null
}, 300)
},
onPulldown (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()
}
},
onLoadmore (callback) {
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('success')
})
}
},
async scrollEnd(e) {
let rate = Math.floor(e.scrollTop / this.viewHeight)
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.$emit('change', pageInfo, this.pages)
}
},
getScrollItemRect (dataId) {
return new Promise(resolve => {
Util.getRect('#scroll-item_' + dataId, Util.getRefs(this, 'scrollItem_' + dataId, 0), this).then(res => {
resolve(res)
})
})
},
onScroll (e) {
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)
}
}
}
}

View File

@ -0,0 +1,724 @@
<template>
<view class="yingbing-read-page" ref="yingbingReadPage" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend">
<!-- <computed ref="computedPage" :options="options"></computed> -->
<!-- 翻页模式 -->
<view class="yingbing-read-page-flip" :style="{
'visibility': pageType != 'scroll' ? 'visible' : 'hidden'
}">
<template v-if="pageType != 'scroll'">
<!-- #ifndef APP-NVUE -->
<view
class="yingbing-flip"
ref="yingbingFlip"
:style="{
'background': options.bgColor
}"
:prop="flipProp"
:change:prop="flip.propWatcher"
@touchstart="flip.touchstart"
@touchmove="flip.touchmove"
@touchend="flip.touchend">
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view
class="yingbing-flip"
ref="yingbingFlip"
:style="{
'background': options.bgColor
}"
@touchstart="onFilpTouchstart"
@touchmove="onFilpTouchmove"
@touchend="onFilpTouchend">
<!-- #endif -->
<template v-if="isShow">
<view
class="flip-item"
v-for="(item, index) in dataReverse"
:key="item.dataId + '_flip'"
:style="{
'visibility': item.dataId == currentDataId || item.dataId == prevDataId || item.dataId == nextDataId ? 'visible' : 'hidden'
}">
<view
v-if="item.dataId == currentDataId || item.dataId == prevDataId || item.dataId == nextDataId"
class="flip-item"
:ref="'flipItem_' + item.dataId"
:class="'flip-item_' + item.dataId"
:style="{
'transform': item.dataId < currentDataId ? `translateX(${-viewWidth}px)` : '',
}">
<view
class="flip-item-wrapper"
:ref="'flipItemWrapper_' + item.dataId"
:class="'flip-item-wrapper_' + item.dataId"
:style="{
'padding-left': options.slide + 'px',
'padding-right': options.slide + 'px',
'padding-top': options.topGap + 'px',
'padding-bottom': options.bottomGap + 'px',
'background': options.bgColor,
'transform': item.dataId < currentDataId ? options.pageType == 'real' ? `translateX(${viewWidth}px)` : 'translateX(0)' : ''
}">
<view class="flip-item-header" v-if="options.headerShow">
<text class="flip-item-header-text" :style="{
color: options.color,
'font-family': options.fontFamily
}">{{item.title}}</text>
</view>
<template v-if="item.type == 'text'">
<view class="flip-item-text flip-item-content"
>
<text class="flip-text"
v-for="(text, i) in item.text" :key="i"
:style="{
'margin-top': options.lineHeight + 'px',
'height': options.fontSize + 'px',
'font-size': options.fontSize + 'px',
'color': options.color,
'font-family': options.fontFamily
}">{{text}}</text>
</view>
</template>
<template v-else-if="item.type == 'custom'">
<read-rich-text style="flex: 1;" :richtext="item.text" :fontFace="fontFace" :pageType="pageType" @customClick="customClick"></read-rich-text>
</template>
<template v-else-if="item.type == 'slot'">
<view class="flip-slot flip-item-content">
<slot :name="item.text"></slot>
</view>
</template>
<template v-else-if="item.type == 'nextLoading' || item.type == 'prevLoading' ">
<view class="flip-loading flip-item-content">
<text :style="{
'color': options.color,
'font-size': options.fontSize + 'px',
'font-family': options.fontFamily
}">正在加载内容</text>
</view>
</template>
<template v-else-if="item.type == 'top' || item.type == 'bottom' ">
<view class="flip-loading flip-item-content">
<text :style="{
'color': options.color,
'font-size': options.fontSize + 'px',
'font-family': options.fontFamily
}">{{item.type == 'top' ? '前面已经没有了' : '后面已经没有了'}}</text>
</view>
</template>
<template v-else>
<view class="flip-loading flip-item-content">
<text :style="{
'color': options.color,
'font-size': options.fontSize + 'px',
'font-family': options.fontFamily
}">未知类型页面</text>
</view>
</template>
<view class="flip-item-footer" v-if="options.footerShow">
<!-- <text class="flip-item-footer-text" :style="{
color: options.color,
'font-family': options.fontFamily
}">{{filterDate()}}</text> -->
<text class="flip-item-footer-text" :style="{
color: options.color,
'font-family': options.fontFamily
}">{{filterPage(item)}}</text>
<!-- <battery :color="options.color" style="opacity: 0.5"></battery> -->
</view>
</view>
<view
class="flip-item-bg"
:ref="'flipItemBg_' + item.dataId"
:class="'flip-item-bg_' + item.dataId"
:style="{
left: viewWidth + 'px',
width: viewWidth + 'px',
height: (viewHeight * 1.5) + 'px',
transform: item.dataId < currentDataId && options.pageType == 'real' ? 'translateX(' + viewWidth + 'px)' : '',
top: (viewHeight / 2 - (viewHeight * 1.5) / 2) + 'px',
background: options.bgColor,
}"></view>
<view
class="flip-item-shadow"
:ref="'flipItemShadow_' + item.dataId"
:class="'flip-item-shadow_' + item.dataId">
</view>
</view>
</view>
</template>
</view>
</template>
</view>
<!-- 翻页模式 -->
<!-- 滚动模式 -->
<view
class="yingbing-scroll"
:style="{
'background': options.bgColor,
'visibility': pageType == 'scroll' ? 'visible' : 'hidden'
}">
<template v-if="pageType == 'scroll'">
<view :style="{height: options.topGap + 'px'}"></view>
<view class="flip-item-header" :style="{
'padding-left': options.slide + 'px',
'padding-right': options.slide + 'px'
}" v-if="options.headerShow">
<text class="flip-item-header-text" :style="{
color: options.color,
'font-family': options.fontFamily
}">{{pageInfo.title || '加载中'}}</text>
</view>
<scroll-list
ref="list"
@scroll="onScroll"
:pulldown="{show: true, color: options.color, endText: '已经到最前面了'}"
:loadmore="{show: true, color: options.color, endText: '已经到最后面了'}"
@pulldown="onPulldown"
@loadmore="onLoadmore"
@scrolltoupper="scrolltoupper">
<view class="scroll-item-wrapper" ref="scrollItemWrapper">
<view
:id="'scroll-item_' + item.dataId"
v-for="(item, index) in pages"
:key="item.dataId + '_scroll'"
class="scroll-item"
:ref="'scrollItem_' + item.dataId"
:style="{
'padding-left': options.slide + 'px',
'padding-right': options.slide + 'px'
}">
<template v-if="item.type == 'text'">
<view class="scroll-item-text scroll-item-content"
>
<text class="scroll-text"
v-for="(text, i) in item.text" :key="i"
:style="{
'margin-top': options.lineHeight + 'px',
'height': options.fontSize + 'px',
'font-size': options.fontSize + 'px',
'color': options.color,
'font-family': options.fontFamily
}">{{text}}</text>
</view>
</template>
<template v-else-if="item.type == 'custom'">
<read-rich-text :richtext="item.text" :fontFace="fontFace" :pageType="pageType" @customClick="customClick"></read-rich-text>
</template>
<template v-else-if="item.type == 'slot'">
<view class="scroll-slot scroll-item-content">
<slot :name="item.text"></slot>
</view>
</template>
</view>
</view>
<view :style="{height: options.bottomGap + 'px'}"></view>
</scroll-list>
<view class="flip-item-footer" :style="{
'padding-left': options.slide + 'px',
'padding-right': options.slide + 'px'
}" v-if="options.footerShow">
<text class="flip-item-footer-text" :style="{
color: options.color,
'font-family': options.fontFamily
}">{{filterDate()}}</text>
<text class="flip-item-footer-text" :style="{
color: options.color,
'font-family': options.fontFamily
}">{{filterPage(pageInfo)}}</text>
<battery ref="scrollBattery" :color="options.color" style="opacity: 0.5"></battery>
</view>
<view :style="{height: options.bottomGap + 'px'}"></view>
<!-- <view class="scroll-item-header" :style="{
'top': options.topGap + 'px',
}" v-if="options.headerShow">
<text class="scroll-item-header-text" :style="{
'font-family': options.fontFamily
}">{{(pageInfo.title || '加载中')}}</text>
</view>
<view class="scroll-item-footer" :style="{
'bottom': options.bottomGap + 'px'
}" v-if="options.footerShow">
<text class="scroll-item-footer-text" :style="{
'font-family': options.fontFamily
}">{{scrollDate}}</text>
<text class="scroll-item-footer-text" :style="{
'font-family': options.fontFamily
}">{{filterPage(pageInfo)}}</text>
<battery ref="scrollBattery" color="#fff"></battery>
</view> -->
</template>
</view>
<view class="yingbing-loading" v-if="initLoading" :style="{background: options.bgColor}" @tap="reload">
<list-loading :size="40":visible="initLoading" :color="options.color" :text="loadingText"></list-loading>
</view>
</view>
</template>
<script>
import NochapterMixin from '../modules/nochapter/nochater.js'
import Battery from '../modules/battery.vue'
import ReadRichText from '../modules/richtext.vue'
import FlipMixin from '../modules/flip/flip.js'
import ScrollMixin from '../modules/scroll/scroll.js'
import ComputedMixin from '../modules/computed/computed.js'
// import Computed from '../modules/computed/computed.vue'
import ScrollList from '../modules/list/list.vue'
import ListLoading from '../modules/list/modules/common/loading.vue'
import Util from '../../js_sdk/util.js'
export default {
mixins: [FlipMixin, ScrollMixin, ComputedMixin, NochapterMixin],
components: {
// Computed,
Battery,
ReadRichText,
ScrollList,
ListLoading
},
props: {
//
color: {
type: String,
default: '#333333'
},
//px
fontSize: {
type: [String, Number],
default: 15
},
//
fontFamily: {
type: String,
default: 'Microsoft YaHei, 微软雅黑'
},
//
fontFace: {
type: Array,
default () {
return new Array
}
},
//
bgColor: {
type: String,
default: '#fcd281'
},
//
pageType: {
type: String,
default: 'real'
},
//px
lineHeight: {
type: [Number, String],
default: 15
},
//px
slide: {
type: [Number, String],
default: 20
},
//px
topGap: {
type: [Number, String],
default: 10
},
//px
bottomGap: {
type: [Number, String],
default: 10
},
//
enablePreload: {
type: Boolean,
default: false
},
//
noChapter: {
type: Boolean,
default: false
},
//
enableClick: {
type: Boolean,
default: false
},
//
headerShow: {
type: Boolean,
default: true
},
//
footerShow: {
type: Boolean,
default: true
},
//
clickOption: {
type: Object,
default () {
return {
width: uni.upx2px(200),
height: uni.upx2px(200),
left: 'auto',
top: 'auto'
}
}
},
},
data () {
return {
pageInfo: {
dataId: -1
},
pages: [],
contents: [],
isClickToTouch: false,
touchstartX: 0,
touchstartY: 0,
touchmoveX: 0,
touchmoveY: 0,
touchTime: 0,
windowWidth: 0,
windowHeight: 0
}
},
computed: {
Util () {
return Util
},
options () {
return {
pageType: this.pageType,
color: this.color,
bgColor: this.bgColor,
enablePreload: this.enablePreload,
headerShow: this.headerShow,
footerShow: this.footerShow,
fontFamily: this.fontFamily,
fontFace: this.fontFace,
slide: this.slide > 0 ? parseInt(this.slide) : 0,
topGap: this.topGap > 0 ? parseInt(this.topGap) : 0,
bottomGap: this.bottomGap > 0 ? parseInt(this.bottomGap) : 0,
fontSize: this.fontSize >= 12 ? parseInt(this.fontSize) : 12,//12px12px
lineHeight: this.lineHeight >= 5 ? parseInt(this.lineHeight) : 5,
}
}
},
beforeDestroy () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
},
mounted () {
this.$nextTick(function () {
setTimeout(() => {
Util.getRect('.yingbing-read-page', this.$refs.yingbingReadPage, this).then(res => {
this.windowWidth = res.width
this.windowHeight = res.height
})
}, 20)
})
this.initFont()
},
methods: {
touchstart (e) {
if ( !this.enableClick ) {
return
}
if ( this.isClickToTouch ) {
return
}
this.resetTouch();
this.touchInter = setTimeout(() => {
this.touchTime = 300
}, 300)
let touch = e.touches[0]
this.touchstartX = touch.pageX;
this.touchstartY = touch.pageY;
},
touchmove (e) {
if ( !this.enableClick ) {
return
}
if ( this.isClickToTouch ) {
return
}
let touch = e.touches[0]
this.touchmoveX = touch.pageX;
this.touchmoveY = touch.pageY;
},
touchend (e) {
if ( this.touchInter ) {
clearTimeout(this.touchInter);
this.touchInter = null
}
if ( !this.enableClick ) {
return
}
if ( this.isClickToTouch ) {
return
}
this.isClickToTouch = true
if ( this.touchTime < 300 && (Math.abs(this.touchmoveX - this.touchmoveX) <= 50 || Math.abs(this.touchmoveY - this.touchmoveY) <= 50) ) {
let left = 0
let top = 0
if ( this.clickOption.left == 'auto' ) {
left = (this.windowWidth / 2) - (this.clickOption.width / 2)
} else if ( typeof this.clickOption.left == 'number' ) {
left = this.clickOption.left
} else {
return
}
if ( this.clickOption.top == 'auto' ) {
top = (this.windowHeight / 2) - (this.clickOption.height / 2)
} else if ( typeof this.clickOption.top == 'number' ) {
top = this.clickOption.top
} else {
return
}
let right = left + this.clickOption.width
let bottom = top + this.clickOption.height
if ( this.touchstartX >= left && this.touchstartX <= right && this.touchstartY >= top && this.touchstartY <= bottom ) {
this.$emit('clickTo')
}
}
setTimeout(() => {
this.isClickToTouch = false
}, 50)
},
resetTouch () {
this.touchstartX = 0
this.touchstartY = 0
this.touchmoveX = 0
this.touchmoveY = 0
this.touchTime = 0
},
customClick (e) {
this.$emit(e.name, ...e.args)
},
setCatalog (e) {
this.$emit('setCatalog', e);
},
//
init (data) {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
if ( !this.noChapter ) {
this.contents = data.contents;
this.initLoading = true;
this.resetPage({
start: parseInt(data.start || 0),
currentChapter: parseInt(data.currentChapter >= 0 ? data.currentChapter : 1)
})
} else {
this.computedNochapter(data);
}
},
//
refresh () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.resetPage({
start: this.pageInfo.start,
currentChapter: this.pageInfo.chapter
})
},
//
change (data) {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
if ( data.contents && data.contents.length > 0 ) {
data.contents.forEach(item => {
let index = this.contents.findIndex(content => content.chapter == item.chapter)
if (index > -1) {
this.contents[index] = item;
} else {
this.contents.push(item);
}
})
}
let index = this.contents.findIndex(content => content.chapter == data.currentChapter)
if ( index > -1 ) {
this.initLoading = true;
this.resetPage({
start: parseInt(data.start || 0),
currentChapter: parseInt(data.currentChapter || 1)
})
} else {
uni.showToast({
title: '未找到该章节内容',
icon: 'none'
})
}
},
initFont () {
this.fontFace.forEach(font => {
// #ifndef APP-NVUE
uni.loadFontFace({
family: font.fontFamily,
source: 'url("' + font.src + '")',
fail (err) {
console.log(err);
}
})
// #endif
// #ifdef APP-NVUE
uni.requireNativePlugin('dom').addRule('fontFace', {
fontFamily: font.fontFamily,
src: "url('" + font.src + "')"
})
// #endif
})
},
},
watch: {
pageType (newVal, oldVal) {
this.$nextTick(function () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.refreshTimer = setTimeout(() => {
if ( newVal != 'scroll' ) {
this.getViewRect().then(res => {
this.refresh()
})
} else {
this.refresh()
}
}, 100)
})
},
fontSize () {
this.$nextTick(function () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.refreshTimer = setTimeout(() => {
this.refresh()
}, 100)
})
},
lineHeight () {
this.$nextTick(function () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.refreshTimer = setTimeout(() => {
this.refresh()
}, 100)
})
},
slide () {
this.$nextTick(function () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.refreshTimer = setTimeout(() => {
this.refresh()
}, 100)
})
},
topGap () {
this.$nextTick(function () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.refreshTimer = setTimeout(() => {
this.refresh()
}, 100)
})
},
bottomGap () {
this.$nextTick(function () {
if ( this.refreshTimer ) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.refreshTimer = setTimeout(() => {
this.refresh()
}, 100)
})
},
fontFace () {
this.initFont()
},
}
}
</script>
<!-- #ifdef H5 || APP-VUE -->
<script lang="renderjs" type="module" module="flipPage">
export default {
mounted() {
window.triggerCustomClick = (name, args) => {
// #ifndef H5
this.$ownerInstance.callMethod('customClick', {
name: name,
args: args
});
// #endif
// #ifdef H5
this.customClick({
name: name,
args: args
});
// #endif
}
}
}
</script>
<!-- #endif -->
<!-- #ifdef APP-VUE || H5 || MP-QQ || MP-WEIXIN -->
<script lang="wxs" module="flip" src="../modules/flip/flip.wxs"></script>
<!-- #endif -->
<style scoped>
@import url(../modules/flip/flip.css);
@import url(../modules/scroll/scroll.css);
.yingbing-read-page {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
position: relative;
}
.yingbing-loading {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
}
.yingbing-slot {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
/* #endif */
flex: 1;
}
</style>

View File

@ -0,0 +1,71 @@
.yb-flex {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
flex-shrink: 0;
background: none;
flex-wrap: nowrap;
/* #endif */
}
.yb-flex-1 {
flex: 1!important;
}
.yb-wrap {
flex-wrap: wrap!important;
}
.yb-row {
flex-direction: row!important;
}
.yb-column {
flex-direction: column!important;
}
.yb-row-reverse {
flex-direction: row-reverse!important;
}
.yb-column-reverse {
flex-direction: column-reverse!important;
}
.yb-align-center {
align-items: center!important;
}
.yb-align-start {
align-items: flex-start!important;
}
.yb-align-end {
align-items: flex-end!important;
}
.yb-align-between {
align-content: space-between!important;
}
.yb-justify-center {
justify-content: center!important;
}
.yb-justify-start {
justify-content: flex-start!important;
}
.yb-justify-end {
justify-content: flex-end!important;
}
.yb-justify-between {
justify-content: space-between!important;
}
/* #ifdef MP */
.scoped-ref {
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
flex-shrink: 0;
background: none;
flex-wrap: nowrap;
flex: 1;
}
.yb-tap view:nth-child(n+2) {
position: relative;
}
.yb-tap text:nth-child(n+1) {
position: relative;
}
/* #endif */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,80 @@
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"
/>
<title>富文本展示</title>
<style type="text/css">
html,body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.content {
color: #333;
width: 100%;
height: 100%;
}
.content img {
max-width: 100%!important;
}
</style>
</head>
<body>
<p id="content" class="content"></p>
</body>
<script type="text/javascript" src="./js/uni-webview-js@1.5.4.js"></script>
<script type="text/javascript">
var obj = {}
window.onload = function () {
var search = window.location.search.slice(1);
var arr = search.split('&');
for ( var i = 0; i < arr.length; i++ ) {
var strs = arr[i].split('=');
obj[strs[0]] = strs[1];
}
if ( obj.rich ) {
setRichText(JSON.parse(decodeURIComponent(obj.rich)));
if ( obj.pageType == 'scroll' ) {
window.setTimeout(function () {
uni.postMessage({
data: {
height: document.getElementById('content').scrollHeight + 100
}
});
}, 100)
}
}
}
function setStyle (attribute, value) {
document.getElementById('content').style[attribute] = value
}
function setRichText (richText) {
document.getElementById('content').innerHTML = richText
}
function setFontFace (fontList) {
let code = fontList.reduce((accumulator, currentValue) => {
return accumulator + `@font-face { font-family: ${currentValue.fontFamily};src: url('${currentValue.src}'); }`;
}, "");
var style = document.createElement("style");
style.type = "text/css";
style.rel = "stylesheet";
style.appendChild(document.createTextNode(code));
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}
function triggerCustomClick (name, args) {
uni.postMessage({
data: {
customClick: {
name: name,
args: args
}
}
});
}
</script>
</html>

View File

@ -0,0 +1,446 @@
export default {
/**
* 补零
* @param {Number} val 数字
**/
zeroize (val) {
return zeroize(val);
},
/**
* 时间格式化
* @param {String} time 时间戳or时间
**/
dateFormat (time, formats = 'yyyy-mm-dd hh:mm:ss') {
let arr = formats.split(' ')
let dateFormats = ''
let timeFormats = ''
arr.forEach(item => {
if ( item.indexOf('yy') > -1 ) {
dateFormats = item
} else {
timeFormats = item
}
})
const d = new Date(time);
let result = ''
if ( dateFormats.indexOf('yyyy') > -1 ) {
result += d.getFullYear() + '-'
}
if ( dateFormats.indexOf('mm') > -1 ) {
result += zeroize(d.getMonth() + 1) + '-'
}
if ( dateFormats.indexOf('dd') > -1 ) {
result += zeroize(d.getDate()) + ' '
}
if ( timeFormats.indexOf('hh') > -1 ) {
result += zeroize(d.getHours()) + ':'
}
if ( timeFormats.indexOf('mm') > -1 ) {
result += zeroize(d.getMinutes()) + ':'
}
if ( timeFormats.indexOf('ss') > -1 ) {
result += zeroize(d.getSeconds()) + ':'
}
return result.substring(0, result.length - 1)
},
/**
* 秒数转化为分秒
* @param {String} value 秒数
**/
minutesFormat (value) {
let minutes = Math.floor(value / 60 % 60) >= 10 ? Math.floor(value / 60 % 60) : '0' + Math.floor(value / 60 % 60);
let seconds = Math.floor(value % 60) >= 10 ? Math.floor(value % 60) : '0' + Math.floor(value % 60);
return minutes + ':' + seconds;
},
/**
* 时间转化为秒数
* @param {String} time 时间HH:mm:ss
**/
time2seconds (time){
const seconds = parseInt(time.split(':')[0]) * 60 + parseInt(time.split(':')[1].split('.')[0]) + parseInt(time.split(':')[1].split('.')[1]) / 1000;
return seconds;
},
/**
* 移除url地址域名
* @param {String} str http地址
**/
removeUrl (url) {
let str = url.replace(/^http:\/\/[^/]+/, '');
return str.substr(1);
},
/**
* 获取文件后缀
* @param {String} name 带后缀的文件名称
**/
suffix (name) {
//获取图片后缀
let fileName = name.lastIndexOf(".");
let fileNameLength = name.length;
let fileFormat = name.substring(fileName + 1, fileNameLength);
return fileFormat;
},
/**
* 清除文件后缀
* @param {String} name 带后缀的文件名称
*/
removeSuffix (name) {
//获取图片后缀
let fileName = name.lastIndexOf(".");
if ( fileName > -1 ) {
let fileNameFormat = name.substring(0, fileName);
return fileNameFormat;
} else {
return name
}
},
/**
* 数组查找符合条件元素并返回下标
* @param {Array} arr 传入数组
* @param {String} value 条件元素
* @param {String} query 对比key值
*/
indexOf (arr, query, value) {
let len = arr.length;
for ( let i = 0; i < len; i++ ) {
if ( arr[i][query] == value ) {
return parseInt(i);
}
}
return -1;
},
/**
* 正则匹配
* @param {String} type 匹配类型
* @param {String} value 匹配值
*/
reg (type, value) {
const regs = {
//身份证证则
idcard: new RegExp(/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/),
//手机正则
mobile: new RegExp(/^1[3456789]\d{9}$/),
//固定电话正则
phone: new RegExp(/^(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14}$/),
//金额验证
price: new RegExp(/^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0.\d{1,2}$/),
//邮箱验证
email: new RegExp(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/),
//银行卡
bankcard: new RegExp(/^([1-9]{1})(\d{15}|\d{18})$/)
}
return regs[type].test(value);
},
/**
* 计算2个时间差的分钟数或者秒钟数或时钟数
* @param {datetime} time1 开始时间
* @param {datetime} time2 结束时间
*/
timeMinuse (time1, time2, type = 'minutes') {
//判断开始时间是否大于结束日期
let date1 = new Date(time1);
let date2 = new Date(time2);
if ( date1 > date2 ) {
console.log("开始时间不能大于结束时间!");
return false;
}
let seconds = date2.getTime() / 1000 - date1.getTime() / 1000;
return type == 'minutes' ? (seconds / 60) : type == 'hours' ? (seconds / 60 / 60) : seconds;
},
/**
* 判断值类型返回字符
* @param {datetime} value 需要判断类型的值
*/
typeof (value) {
let type = Object.prototype.toString.call(value);
return type.slice(8, type.length - 1)
},
/**
* 生成随机字符串
* @param {Number} len 长度
*/
randomString (len) {
len = len || 32;
var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
var maxPos = $chars.length;
var pwd = '';
for (let i = 0; i < len; i++) {
  pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
},
/**
* 生成随机ID
*/
randomID () {
let mydate = new Date();
return mydate.getMinutes() + mydate.getSeconds() + mydate.getMilliseconds() + Math.round(Math.random() * 10000);
},
/**
* 生成随机不重复整数
* @param {Number} len 长度
*/
randomSoleNumber (len) {
let min = 0;
let max = len - 1;
let arr = [];
while ( arr.length < len ) {
let value = Math.floor(Math.random() * (max - min + 1)) + min;
if ( arr.indexOf(value) == -1 ) {
arr.push( value )
}
}
return arr;
},
/**
* 16进制颜色转化为rgb
* @param {String} hex 16进制颜色
*/
hex2rgb (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
},
/**
* 16进制颜色转化为rgba
* @param {String} hex 16进制颜色
*/
hex2rgba (hex, opacity) {
hex = hex.length == 7 ? hex : '#' + hex.slice(1, 4) + hex.slice(1, 4)
let str="rgba("
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+","+opacity+")";
return str
},
/**
* byte转化为文件大小
* @param {Number} byte
*/
byte2Size (byte) {
let sizeString = ''
if(byte == 0){
sizeString = "0B";
}else if(byte < 1024){
sizeString = byte + "B";
}else if(byte < 1048576){
sizeString = (byte/1024).toFixed(2) + "KB";
}else if (byte < 1073741824){
sizeString = (byte/1048576).toFixed(2) + "MB";
}else{
sizeString = (byte/1073741824).toFixed(2) + "GB";
}
return sizeString;
},
// 深度克隆
deepClone (obj) {
if(typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = isArray(obj) ? [] : {};
for(let i in obj) {
if(obj.hasOwnProperty(i)){
o[i] = typeof obj[i] === "object" ? this.deepClone(obj[i]) : obj[i];
}
}
return o;
},
/**
* 将数字转为带中文单位的字符串
* @param {Number} num 数字
*/
numtounit (num) {
let units = [{
label: '万',
value: 10000,
min: 1000
},{
label: '亿',
value: 100000000,
min: 100000000
},{
label: '兆',
value: 10000000000000000,
min: 100000000000000000
}]
let value = num
units.forEach(unit => {
if ( num >= unit.min ) {
value = (num / unit.value).toFixed(2) + unit.label
}
})
return value
},
/**
* 判断像素单位没有则加上rpx
* @param {String} value 像素
*/
pixelunit (value) {
if ( value.toString().indexOf('px') > -1 || value.toString().indexOf('em') > -1 || value.toString().indexOf('auto') > -1 || value.toString().indexOf('%') > -1 ) {
return value
} else {
return value + 'rpx'
}
},
/**
* 判断像素单位全部转为px
* @param {String} value 像素
*/
unitpixel (value) {
if ( value.toString().indexOf('rpx') > -1 ) {
return uni.upx2px(value.replace('rpx', ''))
} else if ( value.toString().indexOf('px') > -1 ) {
return parseFloat(value.replace('px', ''))
} else if ( value.toString().indexOf('em') > -1 || value.toString().indexOf('auto') > -1 || value.toString().indexOf('%') > -1 ) {
return value
} else {
return parseFloat(uni.upx2px(value))
}
},
/**
* 判断像素单位转化为rpx
* @param {String} value
* @param {String} unit 返回结果是否带上单位
*/
anytorpx (value, unit = true) {
if ( value.toString().indexOf('rpx') > -1 ) {
return unit ? value : parseFloat(value.replace('rpx', ''))
} else if ( value.toString().indexOf('px') > -1 ) {
return parseFloat(value.replace('px', '') * (750 / uni.getSystemInfoSync().windowWidth)) + (unit ? 'rpx' : 0)
} else if ( value.toString().indexOf('auto') > -1 ) {
return 'auto'
} else if ( value.toString().indexOf('%') > -1 ) {
return parseFloat((value.replace('%', '') / 100) * 750) + (unit ? 'rpx' : 0)
} else if (value.toString().indexOf('em') > -1 || value.toString().indexOf('rem') > -1 ) {
return parseFloat(value.replace('em', '').replace('rem', '') * 32) + (unit ? 'rpx' : 0)
} else if ( /^\d+$/.test(value) ) {
return parseFloat(value) + (unit ? 'rpx' : 0)
}
},
/**
* 判断像素单位转化为px
* @param {String} value
* @param {String} unit 返回结果是否带上单位
*/
anytopx (value, unit = false) {
if ( value.toString().indexOf('rpx') > -1 ) {
return uni.upx2px(value.replace('rpx', '')) + (unit ? 'px' : 0)
} else if ( value.toString().indexOf('px') > -1 ) {
return parseFloat(value.replace('px', '')) + (unit ? 'px' : 0)
} else if ( value.toString().indexOf('auto') > -1 ) {
return 'auto'
} else if ( value.toString().indexOf('%') > -1 ) {
return parseFloat((value.replace('%', '') / 100) * uni.getSystemInfoSync().windowWidth) + (unit ? 'px' : 0)
} else if (value.toString().indexOf('em') > -1 || value.toString().indexOf('rem') > -1 ) {
return parseFloat(value.replace('em', '').replace('rem', '') * uni.getSystemInfoSync().windowWidth) + (unit ? 'px' : 0)
} else if ( /^\d+$/.test(value) ) {
return parseFloat(value) + (unit ? 'px' : 0)
}
},
getRefs (components, name, current) {
// #ifndef MP
return current >= 0 ? components.$refs[name][current] : components.$refs[name]
// #endif
// #ifdef MP
return {}
// #endif
},
//获取节点
getEl (el) {
if (typeof el === 'string' || typeof el === 'number') return el;
if (WXEnvironment) {
return el.ref;
} else {
return el instanceof HTMLElement ? el : el.$el;
}
},
/**
* 获取指定父节点
* @param {String} components 当前实例
* @param {String} name 父节点名称
*/
getParent(name, components) {
let parent = components.$parent
if (parent) {
let parentName = parent.$options.name
while (parentName !== name) {
parent = parent.$parent
if (parent) {
parentName = parent.$options.name
} else {
return null
}
}
return parent
}
return null
},
/**
* 获取指定子节点
* @param {String} components 当前实例
* @param {String} name 父节点名称
*/
getChildrens(names, components) {
let arr = []
let childs = names.split(',')
const dowhile = (children) => {
if ( this.typeof(children) == 'Array' ) {
children.forEach(child => {
if ( childs.indexOf(child.$options.name) > -1 ) {
arr.push(child)
}
if ( child.$children && child.$children.length > 0 ) {
dowhile(child.$children)
}
})
}
}
dowhile(components.$children)
return arr;
},
/**
* 获取指定子节点
* @param {String} selector 节点class或者id
* @param {String} el 节点
* @param {String} components 当前实例
*/
getRect (selector, el, components) {
return new Promise(resolve => {
// #ifdef APP-NVUE
uni.requireNativePlugin('dom').getComponentRect(el, res => {
resolve(res.size)
})
// #endif
// #ifndef APP-NVUE
uni.createSelectorQuery().in(components).select(selector).boundingClientRect(data => {
resolve(data)
}).exec();
// #endif
})
}
}
// 判断arr是否为一个数组返回一个bool值
function isArray (arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function zeroize (val) {
return val >= 10 ? val : '0' + val;
}

View File

@ -0,0 +1,80 @@
{
"id": "yingbing-ReadPage",
"displayName": "小说阅读分页插件",
"version": "1.4.3",
"description": "给小说分页的插件,包含翻页",
"keywords": [
"小说",
"阅读",
"翻页",
"分页"
],
"repository": "https://gitee.com/yingbing-developer/yingbing-read-page.git",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": "1014295211"
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "y",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var __UniViewStartTime__ = Date.now();
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title>View</title>
<link rel="stylesheet" href="view.css" />
</head>
<body>
<div id="app"></div>
<script src="__uniappes6.js"></script>
<script src="view.umd.min.js"></script>
<script src="app-view.js"></script>
</body>
</html>

View File

@ -0,0 +1,8 @@
var isReady=false;var onReadyCallbacks=[];
var isServiceReady=false;var onServiceReadyCallbacks=[];
var __uniConfig = {"pages":["pages/bookCity/bookCity/index","pages/bookshelf/bookshelf/index","pages/login/login","pages/loginMobile/loginMobile","pages/classification/classification/index","pages/myInfo/myInfo/index","pages/myInfo/aboutMy/index","pages/myInfo/mySetUp/index","pages/myInfo/problemList/index","pages/myInfo/problemDetail/index","pages/voucherCenter/index","pages/readingRecords/index","pages/signInBookCurrency/index","pages/booksListAll/index","pages/booksTheCharts/index","pages/voucherCenterDetail/index","pages/bookCoinDetail/bookCoinDetail","pages/giveCoinDetail/giveCoinDetail","pages/booksSearchList/booksSearchList","pages/novelReading/novelReading","pages/booksReadingDetail/booksReadingDetail","pages/bookRecommendList/bookRecommendList"],"window":{"navigationBarTextStyle":"black","navigationBarTitleText":"uni-app","navigationBarBackgroundColor":"#F8F8F8","backgroundColor":"#F8F8F8"},"tabBar":{"backgroundColor":"#FFFFFF","borderStyle":"#F5F5F5","selectedColor":"#FF5000","iconWidth":"24px","color":"#666666","fontSize":"10px","height":"68px","list":[{"pagePath":"pages/bookshelf/bookshelf/index","text":"书架","iconPath":"/static/images/tabbar/bookshelf.png","selectedIconPath":"/static/images/tabbar/bookshelfActive.png"},{"pagePath":"pages/bookCity/bookCity/index","text":"书城","iconPath":"/static/images/tabbar/bookCity.png","selectedIconPath":"/static/images/tabbar/bookCityActive.png"},{"pagePath":"pages/classification/classification/index","text":"分类","iconPath":"/static/images/tabbar/classification.png","selectedIconPath":"/static/images/tabbar/classificationActive.png"},{"pagePath":"pages/myInfo/myInfo/index","text":"我的","iconPath":"/static/images/tabbar/myInfo.png","selectedIconPath":"/static/images/tabbar/myInfoActive.png"}]},"darkmode":false,"nvueCompiler":"uni-app","nvueStyleCompiler":"uni-app","renderer":"auto","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":false},"appname":"app_ancientSayings","compilerVersion":"3.8.12","entryPagePath":"pages/bookCity/bookCity/index","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000}};
var __uniRoutes = [{"path":"/pages/bookCity/bookCity/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationBarTitleText":"书城","navigationStyle":"custom"}},{"path":"/pages/bookshelf/bookshelf/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationBarTitleText":"书架","navigationStyle":"custom"}},{"path":"/pages/login/login","meta":{},"window":{"navigationBarTitleText":"","navigationStyle":"custom"}},{"path":"/pages/loginMobile/loginMobile","meta":{},"window":{"navigationBarTitleText":"","navigationStyle":"custom"}},{"path":"/pages/classification/classification/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationBarTitleText":"分类","navigationStyle":"custom"}},{"path":"/pages/myInfo/myInfo/index","meta":{"isQuit":true,"isTabBar":true},"window":{"navigationBarTitleText":"我的"}},{"path":"/pages/myInfo/aboutMy/index","meta":{},"window":{"navigationBarTitleText":"关于我们"}},{"path":"/pages/myInfo/mySetUp/index","meta":{},"window":{"navigationBarTitleText":"设置"}},{"path":"/pages/myInfo/problemList/index","meta":{},"window":{"navigationBarTitleText":"常见问题"}},{"path":"/pages/myInfo/problemDetail/index","meta":{},"window":{"navigationBarTitleText":"常见问题"}},{"path":"/pages/voucherCenter/index","meta":{},"window":{"navigationBarTitleText":"充值中心"}},{"path":"/pages/readingRecords/index","meta":{},"window":{"navigationBarTitleText":"阅读记录"}},{"path":"/pages/signInBookCurrency/index","meta":{},"window":{"navigationBarTitleText":"签到领取书币","navigationStyle":"custom","navigationBarTextStyle":"white"}},{"path":"/pages/booksListAll/index","meta":{},"window":{"navigationBarTitleText":""}},{"path":"/pages/booksTheCharts/index","meta":{},"window":{"navigationBarTitleText":"排行榜","navigationBarBackgroundColor":"transparent","navigationStyle":"custom","titleNView":{"backgroundColor":"transparent","background":"transparent"}}},{"path":"/pages/voucherCenterDetail/index","meta":{},"window":{"navigationBarTitleText":"充值记录"}},{"path":"/pages/bookCoinDetail/bookCoinDetail","meta":{},"window":{"navigationBarTitleText":"书币明细"}},{"path":"/pages/giveCoinDetail/giveCoinDetail","meta":{},"window":{"navigationBarTitleText":"赠币明细"}},{"path":"/pages/booksSearchList/booksSearchList","meta":{},"window":{"navigationBarTitleText":"搜索"}},{"path":"/pages/novelReading/novelReading","meta":{},"window":{"navigationBarTitleText":"","navigationStyle":"custom","enablePullDownRefresh":false}},{"path":"/pages/booksReadingDetail/booksReadingDetail","meta":{},"window":{"navigationBarTitleText":""}},{"path":"/pages/bookRecommendList/bookRecommendList","meta":{},"window":{"navigationBarTitleText":""}}];
__uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
__uniConfig.onServiceReady=function(callback){if(__uniConfig.serviceReady){callback()}else{onServiceReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"serviceReady",{get:function(){return isServiceReady},set:function(val){isServiceReady=val;if(!isServiceReady){return}const callbacks=onServiceReadyCallbacks.slice(0);onServiceReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:Math.round(f/20)})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,global:void 0,window:void 0,document:void 0,frames:void 0,self:void 0,location:void 0,navigator:void 0,localStorage:void 0,history:void 0,Caches:void 0,screen:void 0,alert:void 0,confirm:void 0,prompt:void 0,fetch:void 0,XMLHttpRequest:void 0,WebSocket:void 0,webkit:void 0,print:void 0}}}});

View File

@ -0,0 +1,154 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/
/******/ return result;
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "app-config": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/";
/******/
/******/ var jsonpArray = this["webpackJsonp"] = this["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // run deferred modules from other chunks
/******/ checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ([]);

24255
unpackage/dist/dev/app-plus/app-service.js vendored Normal file

File diff suppressed because one or more lines are too long

26959
unpackage/dist/dev/app-plus/app-view.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__474F351","name":"app_ancientSayings","version":{"name":"1.0.0","code":"100"},"description":"","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":true,"delay":0},"popGesture":"close","launchwebview":{"id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"safearea":{"background":"#F5F6F9","bottom":{"offset":"none|auto"}},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"google":{"permissions":["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>","<uses-feature android:name=\"android.hardware.camera\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"]},"apple":{},"plugins":{"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}}},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"3.8.12","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"tabBar":{"backgroundColor":"#FFFFFF","borderStyle":"#F5F5F5","selectedColor":"#FF5000","iconWidth":"24px","color":"#666666","fontSize":"10px","height":"68px","list":[{"pagePath":"pages/bookshelf/bookshelf/index","text":"书架","iconPath":"/static/images/tabbar/bookshelf.png","selectedIconPath":"/static/images/tabbar/bookshelfActive.png"},{"pagePath":"pages/bookCity/bookCity/index","text":"书城","iconPath":"/static/images/tabbar/bookCity.png","selectedIconPath":"/static/images/tabbar/bookCityActive.png"},{"pagePath":"pages/classification/classification/index","text":"分类","iconPath":"/static/images/tabbar/classification.png","selectedIconPath":"/static/images/tabbar/classificationActive.png"},{"pagePath":"pages/myInfo/myInfo/index","text":"我的","iconPath":"/static/images/tabbar/myInfo.png","selectedIconPath":"/static/images/tabbar/myInfoActive.png"}],"child":["lauchwebview"],"selected":1},"launch_path":"__uniappview.html"}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Some files were not shown because too many files have changed in this diff Show More