四零语境前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

743 lines
25 KiB

  1. <template>
  2. <view class="yingbing-reader" ref="yingbingReader" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend">
  3. <view class="yingbing-scroll-reader" :class="{'yingbing-hidden': pageType != 'scroll'}" :style="[wrapperStyle]">
  4. <reader-header :progress="currentProgress" :title="currentTitle" v-if="headerShow && currentTitle"></reader-header>
  5. <view class="yingbing-scroll yingbing-reader-content" ref="yingbingReaderContent">
  6. <reader-scroller
  7. ref="scroll"
  8. :autoplay="autoplaySync"
  9. :loadmoreable="!isLoading && !autoplaySync && pages.length > 0"
  10. :pulldownable="!isLoading && !autoplaySync"
  11. @pulldown="handlePulldown"
  12. @loadmore="handlePullup"
  13. @scroll="handleScroll"
  14. :data="pageType == 'scroll' ? pages : []">
  15. <!-- #ifndef MP-->
  16. <template v-slot="{item, index}">
  17. <!-- #endif -->
  18. <!-- #ifdef MP -->
  19. <template v-for="(item, index) in pages" :slot="`wx:${index}`">
  20. <!-- #endif -->
  21. <view :style="{position: 'relative', 'height': contentHeight + 'px'}" v-if="item.type == 'slot' && pageType == 'scroll'">
  22. <!-- 微信小程序vue2虽然支持在v-for中嵌套同名插槽但会一直报错引起页面卡顿vue3不支持在v-for中嵌套同名插槽所以增加2种微信小程序使用方式 -->
  23. <!-- #ifdef MP -->
  24. <slot :name="item.content + ':' + item.index"></slot>
  25. <!-- #endif -->
  26. <!-- #ifndef MP -->
  27. <slot :name="item.content" :item="item" :index="index"></slot>
  28. <!-- #endif -->
  29. </view>
  30. <read-content :item="item" v-if="item.type == 'text'"></read-content>
  31. <view class="yingbing-reader-content-loading" :style="{'height': contentHeight + 'px'}" v-if="item.type == 'error'" @touchstart.stop.prevent @touchmove.stop.prevent @touchend.stop.prevent="handleReload(item)">
  32. <text class="yingbing-reader-content-loading-text" :style="{color: color}">{{errorText}}</text>
  33. </view>
  34. <view class="yingbing-reader-content-loading" :style="{'height': contentHeight + 'px'}" v-if="item.type == 'loading'">
  35. <reader-loading size="20px" :color="color || ''"></reader-loading>
  36. <text class="yingbing-reader-content-loading-text" :style="{color: color}">{{loadingText}}</text>
  37. </view>
  38. </template>
  39. </reader-scroller>
  40. </view>
  41. <reader-footer ref="footer" :total="currentTotal" :current="currentPage" v-if="footerShow && currentTotal"></reader-footer>
  42. </view>
  43. <yingbing-flip
  44. :class="{'yingbing-hidden': pageType == 'scroll'}"
  45. class="yingbing-flip-reader yingbing-reader-absolute"
  46. ref="flip"
  47. :autoplay="autoplaySync"
  48. :interval="interval"
  49. :current="current"
  50. :type="pageType"
  51. :data="pageType != 'scroll' ? pages : []"
  52. :background="background"
  53. :duration="300"
  54. :unableClickPage="unableClickPage"
  55. :pulldownable="!isLoading"
  56. :pullupable="!isLoading"
  57. @change="handleChange"
  58. @pulldown="handlePulldown"
  59. @pullup="handlePullup">
  60. <!-- #ifndef MP-->
  61. <template v-slot="{item, index}">
  62. <!-- #endif -->
  63. <!-- #ifdef MP -->
  64. <template v-for="(item, index) in pages" :slot="`wx:${index}`">
  65. <!-- #endif -->
  66. <view class="yingbing-reader-absolute yingbing-flip-reader-wrapper" :style="[wrapperStyle]">
  67. <reader-header :progress="item.progress" :title="item.title || title" v-if="getFlipHeaderShow(item)"></reader-header>
  68. <view class="yingbing-flip-reader-content">
  69. <template v-if="item.type == 'slot' && pageType != 'scroll'">
  70. <!-- 微信小程序vue2虽然支持在v-for中嵌套同名插槽但会一直报错引起页面卡顿vue3不支持在v-for中嵌套同名插槽所以增加2种微信小程序使用方式 -->
  71. <!-- #ifdef MP -->
  72. <slot :name="item.content + ':' + item.index"></slot>
  73. <!-- #endif -->
  74. <!-- #ifndef MP -->
  75. <slot :name="item.content" :item="item" :index="index"></slot>
  76. <!-- #endif -->
  77. </template>
  78. <read-content class="yingbing-reader-absolute" :item="item" v-if="item.type == 'text'"></read-content>
  79. <view class="yingbing-reader-absolute yingbing-reader-content-loading" v-if="item.type == 'error'" @touchstart.stop.prevent @touchmove.stop.prevent @touchend.stop.prevent="handleReload(item)">
  80. <text class="yingbing-reader-content-loading-text" :style="{color: color}">{{errorText}}</text>
  81. </view>
  82. <view class="yingbing-reader-absolute yingbing-reader-content-loading" v-if="item.type == 'loading'">
  83. <reader-loading size="20px" :color="color || ''"></reader-loading>
  84. <text class="yingbing-reader-content-loading-text" :style="{color: color}">{{loadingText}}</text>
  85. </view>
  86. </view>
  87. <reader-footer :total="item.total" :current="item.current" v-if="getFlipFooterShow(item)"></reader-footer>
  88. </view>
  89. </template>
  90. <template #pulldownDefault>
  91. <view class="loading-box">
  92. <text class="loading-text">{{isPulldownEnd ? prevChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : prevChapterDefaultText.split('').join('\n') }}</text>
  93. </view>
  94. </template>
  95. <template #pulldownReady>
  96. <view class="loading-box">
  97. <text class="loading-text">{{isPulldownEnd ? prevChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterReadyText.split('').join('\n') }}</text>
  98. </view>
  99. </template>
  100. <template #pulldownLoading>
  101. <view class="loading-box">
  102. <text class="loading-text">{{isPulldownEnd ? prevChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterLoadingText.split('').join('\n') }}</text>
  103. </view>
  104. </template>
  105. <template #pulldownSuccess>
  106. <view class="loading-box">
  107. <text class="loading-text">{{isPulldownEnd ? prevChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterSuccessText.split('').join('\n') }}</text>
  108. </view>
  109. </template>
  110. <template #pulldownFail>
  111. <view class="loading-box">
  112. <text class="loading-text">{{isPulldownEnd ? prevChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterFailText.split('').join('\n') }}</text>
  113. </view>
  114. </template>
  115. <template #pullupDefault>
  116. <view class="loading-box">
  117. <text class="loading-text">{{isPullupEnd ? nextChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : nextChapterDefaultText.split('').join('\n') }}</text>
  118. </view>
  119. </template>
  120. <template #pullupReady>
  121. <view class="loading-box">
  122. <text class="loading-text">{{isPullupEnd ? nextChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterReadyText.split('').join('\n') }}</text>
  123. </view>
  124. </template>
  125. <template #pullupLoading>
  126. <view class="loading-box">
  127. <text class="loading-text">{{isPullupEnd ? nextChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterLoadingText.split('').join('\n') }}</text>
  128. </view>
  129. </template>
  130. <template #pullupSuccess>
  131. <view class="loading-box">
  132. <text class="loading-text">{{isPullupEnd ? nextChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterSuccessText.split('').join('\n') }}</text>
  133. </view>
  134. </template>
  135. <template #pullupFail>
  136. <view class="loading-box">
  137. <text class="loading-text">{{isPullupEnd ? nextChapterEndText.split('').join('\n') : chapterLoading ? chapterLoadingText.split('').join('\n') : chapterFailText.split('').join('\n') }}</text>
  138. </view>
  139. </template>
  140. </yingbing-flip>
  141. <!-- 循环computed计算组件避免计算冲突 -->
  142. <computed
  143. :window-width="windowWidth"
  144. :window-height="windowHeight"
  145. ref="computed" v-for="(c, i) in computeds"
  146. :key="c"></computed>
  147. </view>
  148. </template>
  149. <script>
  150. // #ifdef H5 || MP-WEIXIN
  151. import ReadContent from './h5/content.vue'
  152. import Computed from './h5/computed.vue'
  153. // #endif
  154. // #ifdef APP-VUE
  155. import Computed from './app-vue/computed.vue'
  156. import ReadContent from './h5/content.vue'
  157. // #endif
  158. import ReaderLoading from '../loading/loading.vue'
  159. import ReaderHeader from '../header/header.vue'
  160. import ReaderFooter from '../footer/footer.vue'
  161. import ReaderScroller from '../scroller/scroller.vue'
  162. import FlipReaderMixin from './flip-reader.js'
  163. import ScrollReaderMixin from './scroll-reader.js'
  164. import TouchClickMixin from '../mixin/touch-click.js'
  165. import TipMixin from '../mixin/tip.js'
  166. const threshold = 50//点击事件阙值
  167. export default {
  168. options: {
  169. addGlobalClass: true,
  170. virtualHost: true, // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
  171. },
  172. mixins: [FlipReaderMixin, ScrollReaderMixin, TouchClickMixin, TipMixin],
  173. components: {
  174. ReadContent,
  175. ReaderHeader,
  176. ReaderFooter,
  177. ReaderLoading,
  178. ReaderScroller,
  179. Computed
  180. },
  181. provide () {
  182. return {
  183. getColor: () => this.color,
  184. getFontSize: () => this.fontSize,
  185. getFontFamily: () => this.fontFamily,
  186. getLineHeight: () => this.lineHeightSync,
  187. getTopGap: () => this.topGap,
  188. getBottomGap: () => this.bottomGap,
  189. getSlide: () => this.slide,
  190. getHeaderShow: () => this.headerShow,
  191. getFooterShow: () => this.footerShow,
  192. getBackShow: () => this.backShow,
  193. getSplit: () => this.split,
  194. getTotalChapter: () => this.totalChapter,
  195. getSelectable: () => this.selectable,
  196. getPageType: () => this.pageType,
  197. getMeasureSize: () => this.measureSize,
  198. getCharSize: () => this.charSize
  199. }
  200. },
  201. props: {
  202. //自动翻页/滚动
  203. autoplay: {
  204. type: Boolean,
  205. default: false
  206. },
  207. //自动翻页/滚动周期
  208. interval: {
  209. type: [String, Number],
  210. default: 5000
  211. },
  212. //字体颜色
  213. color: {
  214. type: String,
  215. default: '#333333'
  216. },
  217. //字体大小(单位px)
  218. fontSize: {
  219. type: [String, Number],
  220. default: 15
  221. },
  222. //行高(单位px)
  223. lineHeight: {
  224. type: [Number, String],
  225. default: ''
  226. },
  227. //页面左右边距(单位px)
  228. slide: {
  229. type: [Number, String],
  230. default: 20
  231. },
  232. //页面上边距(单位px)
  233. topGap: {
  234. type: [Number, String],
  235. default: 10
  236. },
  237. //页面下边距(单位px)
  238. bottomGap: {
  239. type: [Number, String],
  240. default: 10
  241. },
  242. //展示头部
  243. headerShow: {
  244. type: Boolean,
  245. default: true
  246. },
  247. //展示底部
  248. footerShow: {
  249. type: Boolean,
  250. default: true
  251. },
  252. //展示返回图标
  253. backShow: {
  254. type: Boolean,
  255. default: false
  256. },
  257. //总章节数量 用于计算进度
  258. totalChapter: {
  259. type: [Number, String],
  260. default: 0
  261. },
  262. //字体名称
  263. fontFamily: {
  264. type: String,
  265. default: 'Arial'
  266. },
  267. //背景颜色
  268. background: {
  269. type: String,
  270. default: '#fcd281'
  271. },
  272. //开启预加载
  273. preloadable: {
  274. type: Boolean,
  275. default: false
  276. },
  277. //开启文本选择
  278. selectable: {
  279. type: Boolean,
  280. default: false
  281. },
  282. //还剩多少页时,开始加载下一章节
  283. loadPageNum: {
  284. type: [Number, String],
  285. default: 4
  286. },
  287. //是否关闭点击左右2侧位置翻页
  288. unableClickPage: {
  289. type: Boolean,
  290. default: false
  291. },
  292. //翻页方式
  293. pageType: {
  294. type: String,
  295. default: 'scroll'
  296. },
  297. //分隔符
  298. split: {
  299. type: String,
  300. default: ''
  301. }
  302. },
  303. computed: {
  304. //行高计算
  305. lineHeightSync () {
  306. return this.lineHeight || (parseInt(this.fontSize) + 15)
  307. },
  308. wrapperStyle () {
  309. return {
  310. 'padding-top': this.topGap + 'px',
  311. 'padding-bottom': this.bottomGap + 'px',
  312. 'background': this.background,
  313. 'padding-left': this.slide + 'px',
  314. 'padding-right': this.slide + 'px'
  315. }
  316. },
  317. //是否正在初始化或者跳转章节内容
  318. isLoading () {
  319. return this.pages.some(p => p.type == 'loading')
  320. },
  321. //是否渲染到第一章
  322. isPulldownEnd () {
  323. return this.pages.findIndex(c => c.isStart) > -1
  324. },
  325. //是否渲染到最后一章
  326. isPullupEnd () {
  327. return this.pages.findIndex(c => c.isEnd) > -1
  328. }
  329. },
  330. data () {
  331. return {
  332. computeds: [],//计算集合,为了避免计算冲突
  333. pages: [],//渲染页面集合
  334. chapters: [],//章节列表集合
  335. chapterLoading: false,//章节请求loading
  336. current: 0,//当前页面索引
  337. title: '',//小说标题
  338. contentHeight: 0//内容区域高度
  339. }
  340. },
  341. beforeDestroy () {
  342. this.abort()
  343. uni.$off('yingbing-reader-back')//清楚back监听
  344. },
  345. beforeCreate() {
  346. uni.$on('yingbing-reader-back', () => {
  347. this.$emit('back')
  348. })
  349. },
  350. methods: {
  351. //翻往上一页
  352. prev () {
  353. if ( this.current <= 0 ) {
  354. uni.showToast({
  355. title: '前面没有了',
  356. icon: 'none'
  357. })
  358. return
  359. }
  360. this.$refs.scroll && this.$refs.scroll.scrollToIndex(this.current - 1, true)//滚动模式
  361. this.$refs.flip && this.$refs.flip.flipToPrev()//翻页模式
  362. },
  363. //翻往下一页
  364. next () {
  365. if ( this.current >= this.pages.length - 1 ) {
  366. uni.showToast({
  367. title: '后面没有了',
  368. icon: 'none'
  369. })
  370. return
  371. }
  372. this.$refs.scroll && this.$refs.scroll.scrollToIndex(this.current + 1, true)//滚动模式
  373. this.$refs.flip && this.$refs.flip.flipToNext()//翻页模式
  374. },
  375. //页面刷新
  376. refresh () {
  377. this.abort()
  378. if ( this.pages.length == 0 ) return//如果页面数量为0不做操作
  379. const page = this.pages[this.current]//当前页面
  380. const current = page.index//当前页面章节索引
  381. const start = page.start//当前页面开始位置
  382. this.refreshTimer = setTimeout(() => {
  383. this.render({current, start})//调用change事件
  384. }, 300)
  385. },
  386. //初始化
  387. init ({chapters, title, current, start = 0}) {
  388. if ( !chapters || chapters.length == 0 ) {
  389. uni.showToast({
  390. title: '至少传入一个章节内容',
  391. icon: 'none'
  392. })
  393. return
  394. }
  395. this.abort()
  396. this.autoplaySync = false//关闭自动播放
  397. this.title = title || ''//存储小说标题
  398. this.chapters = chapters//存储传入的章节内容
  399. // this.pages = [{index: current, type: 'loading'}]//显示loading
  400. setTimeout(() => {
  401. const chapterIndex = this.chapters.findIndex(c => c.index == current)
  402. if ( chapterIndex == -1 ) current = this.chapters[0].index//强制转换类型为int
  403. this.render({current, start})
  404. }, 20)
  405. },
  406. //跳转章节
  407. change({chapters, current, start = 0}) {
  408. if ( !current ) {//current必、传
  409. uni.showToast({
  410. title: 'current必传',
  411. icon: 'none'
  412. })
  413. return
  414. }
  415. this.abort()
  416. this.autoplaySync = false//关闭自动播放
  417. if ( chapters && chapters.length > 0 ) {//如果传入章节内容
  418. chapters.forEach(chapter => {
  419. const index = this.chapters.findIndex(c => c.index == chapter.index)//是否已经包含相同章节
  420. if (index > -1) this.chapters[index] = chapter//如果包含则更新
  421. else this.chapters.push(chapter)//否则添加新章节
  422. })
  423. }
  424. setTimeout(() => {
  425. const chapterIndex = this.chapters.findIndex(c => c.index == current)//查找对应current的章节内容
  426. if ( chapterIndex > -1 ) this.render({current, start})//如果过包含开始渲染内容
  427. else this.handleChangeLoadmore(current, start)//如果不包含,抛出loadmore事件,去获取内容
  428. }, 20)
  429. },
  430. async render (data) {
  431. const rect = await this.getRect()//获取组件尺寸
  432. this.windowWidth = rect.width//记录组件宽度
  433. this.windowHeight = rect.height//记录组件高度
  434. const contentRect = await this.getContentRect()//获取内容尺寸
  435. this.contentHeight = contentRect.height//记录内容区域高度
  436. if ( this.pageType == 'scroll' ) this.scrollRender(data)
  437. else this.flipRender(data)
  438. },
  439. //设置指定页面的内容
  440. setContent (index, content) {
  441. this.$set(this.pages[index], 'content', content)//更新页面内容
  442. this.$refs.flip && this.$refs.flip.forceUpdate(index)
  443. },
  444. //重加载事件
  445. handleReload (item) {
  446. this.pages = [{
  447. type: 'loading',
  448. index: item.index,
  449. start: item.start || undefined//记录章节定位
  450. }]//显示刷新效果
  451. this.handleChangeLoadmore(item.index, item.start)//触发loadmore
  452. },
  453. //change事件触发loadmore加载更多章节并渲染
  454. handleChangeLoadmore (index, start) {
  455. this.loadmoreFunc = (status, chapter) => {
  456. //请求完成后删除请求集合中的章节索引
  457. if (status == 'success') {//获取内容成功
  458. this.render({chapter, current: index, start})
  459. } else {
  460. this.pages = [{
  461. type: 'error',
  462. index: index,
  463. start: start || undefined//记录章节定位
  464. }]//显示错误页面
  465. this.current = 0
  466. this.$refs.flip && this.$refs.flip.refresh()
  467. }
  468. }
  469. this.$emit('loadmore', index, this.loadmoreFunc.bind(this))
  470. },
  471. //翻页改变事件
  472. handleChange (e) {
  473. this.current = e.current//记录当前定位
  474. this.$emit('change', e)//抛出change事件
  475. if ( e.current >= this.pages.length - this.loadPageNum && !this.chapterLoading ) {//如果翻到了最后loadPageNum页,并且没有加载章节
  476. const page = this.pages[this.pages.length-1]//回去最后页面
  477. if ( page.isEnd ) return
  478. const index = page.index + 1//
  479. const chapterIndex = this.chapters.findIndex(c => c.index == index)//查找对应index的章节内容
  480. if ( chapterIndex > -1 ) {//如果过包含开始渲染内容
  481. this.chapterLoading = true//开启章节加载等待
  482. this.handleLoadRender('success', this.chapters[chapterIndex])//渲染章节
  483. } else {
  484. if ( this.preloadable ) {//开启了预加载功能
  485. this.chapterLoading = true//开启章节加载等待
  486. this.loadmoreFunc = (status, chapter) => {//如果不包含,抛出loadmore事件,去获取内容
  487. this.handleLoadRender(status, chapter)//渲染章节
  488. }
  489. this.$emit('loadmore', index, this.loadmoreFunc.bind(this))
  490. }
  491. }
  492. }
  493. },
  494. //渲染下一章节
  495. handleLoadRender (status, chapter, callback, isPrev) {
  496. if ( this.pageType == 'scroll' ) this.handleScrollLoadRender(status, chapter, callback, isPrev)
  497. else this.handleFlipLoadRender(status, chapter, callback, isPrev)
  498. },
  499. //上拉事件
  500. handlePullup (callback) {
  501. // if ( this.isPullupEnd || this.chapterLoading || this.isLoading || this.current < this.pages.length - 1 ) {//如果最后页面是结束章节或者当前正在等待加载新章节或者当前页面不是最后一页则返回
  502. // callback( this.isPullupEnd && this.pageType == 'scroll' ? 'end' : 'success')//如果是滚动模式并且章节全部加载关闭则返回end否则success
  503. // return
  504. // }
  505. if ( this.pageType == 'scroll' ) {
  506. if ( this.isPullupEnd || this.chapterLoading || this.isLoading ) {
  507. callback(this.isPullupEnd ? 'end' : 'ready')
  508. return
  509. }
  510. } else {
  511. if ( this.isPullupEnd || this.chapterLoading || this.isLoading || this.current < this.pages.length - 1 ) {//如果最后页面是结束章节或者当前正在等待加载新章节或者当前页面不是最后一页则返回
  512. callback('success')//如果是滚动模式并且章节全部加载关闭则返回end否则success
  513. return
  514. }
  515. }
  516. const index = this.pages[this.pages.length-1].index + 1
  517. const chapterIndex = this.chapters.findIndex(c => c.index == index)//查找对应index的章节内容
  518. this.chapterLoading = true//开启章节加载等待
  519. if ( chapterIndex > -1 ) {//如果过包含开始渲染内容
  520. this.handleLoadRender('success', this.chapters[chapterIndex], callback)//渲染章节
  521. } else {
  522. this.loadmoreFunc = (status, chapter) => {//如果不包含,抛出loadmore事件,去获取内容
  523. this.handleLoadRender(status, chapter, callback)//渲染章节
  524. }
  525. this.$emit('loadmore', index, this.loadmoreFunc.bind(this))
  526. }
  527. },
  528. //下拉事件
  529. handlePulldown (callback) {
  530. if ( this.isPulldownEnd || this.chapterLoading || this.isLoading ) {
  531. callback( this.isPullupEnd && this.pageType == 'scroll' ? 'end' : 'success')//如果是滚动模式并且章节全部加载关闭则返回end否则success
  532. return
  533. }
  534. const index = this.pages[0].index - 1
  535. const chapterIndex = this.chapters.findIndex(c => c.index == index)//查找对应index的章节内容
  536. this.chapterLoading = true//开启章节加载等待
  537. if ( chapterIndex > -1 ) {//如果过包含开始渲染内容
  538. this.handleLoadRender('success', this.chapters[chapterIndex], true)//渲染章节
  539. } else {
  540. this.loadmoreFunc = (status, chapter) => {//如果不包含,抛出loadmore事件,去获取内容
  541. this.handleLoadRender(status, chapter, callback, true)//渲染章节
  542. }
  543. this.$emit('loadmore', index, this.loadmoreFunc)
  544. }
  545. },
  546. //处理页面
  547. handlePages (arr) {
  548. arr = arr.filter(item => item.type != 'loading')//去掉加载页
  549. arr.sort((a, b) => a.index - b.index)//根据章节索引排序
  550. return arr
  551. },
  552. //清楚刷新定时器
  553. clearRefreshTimer () {
  554. if ( this.refreshTimer ) {
  555. clearTimeout(this.refreshTimer)
  556. this.refreshTimer = null
  557. }
  558. },
  559. //清楚loadmore回调
  560. clearLoadmoreFunc () {
  561. if ( this.loadmoreFunc ) this.loadmoreFunc = null
  562. },
  563. //中断正在进行的计算和请求
  564. abort () {
  565. this.clearRefreshTimer()//清空刷新定时器
  566. this.clearLoadmoreFunc()//清空请求回调
  567. this.computeds = []
  568. },
  569. //获取计算组件id
  570. getComputedId () {
  571. return (new Date().getTime() / 1000).toString() + (Math.random() * 100)
  572. },
  573. getRect () {
  574. return new Promise(resolve => {
  575. // #ifdef APP-NVUE
  576. uni.requireNativePlugin('dom').getComponentRect(this.$refs.yingbingReader, res => {
  577. resolve(res.size)
  578. })
  579. // #endif
  580. // #ifndef APP-NVUE
  581. uni.createSelectorQuery().in(this).select('.yingbing-reader').boundingClientRect(data => {
  582. resolve(data)
  583. }).exec();
  584. // #endif
  585. })
  586. },
  587. //获取内容区域高度
  588. getContentRect () {
  589. return new Promise(resolve => {
  590. // #ifdef APP-NVUE
  591. uni.requireNativePlugin('dom').getComponentRect(this.$refs.yingbingReaderContent, res => {
  592. resolve(res.size)
  593. })
  594. // #endif
  595. // #ifndef APP-NVUE
  596. uni.createSelectorQuery().in(this).select('.yingbing-reader-content').boundingClientRect(data => {
  597. resolve(data)
  598. }).exec();
  599. // #endif
  600. })
  601. }
  602. },
  603. watch: {
  604. pageType (newVal, oldVal) {
  605. if ( newVal == 'scroll' ) {//当翻页模式变为滚动模式时
  606. this.autoplaySync = false//暂时关闭自动播放
  607. this.$nextTick(function () {
  608. this.$refs.scroll && this.$refs.scroll.scrollToIndex(this.current)//定位滚动位置
  609. setTimeout(() => {
  610. this.autoplaySync = this.autoplay//开启自动播放
  611. }, 100)
  612. })
  613. }
  614. },
  615. //颜色改变时刷新当前页面,否则APP-VUE可能不会立即生效
  616. color () {
  617. if ( this.$refs.flip && this.pages[this.current] ) this.$refs.flip.forceUpdate(this.current)
  618. },
  619. fontSize () {
  620. this.$nextTick(function () {
  621. this.refresh()
  622. })
  623. },
  624. lineHeight () {
  625. this.$nextTick(function () {
  626. this.refresh()
  627. })
  628. },
  629. slide () {
  630. this.$nextTick(function () {
  631. this.refresh()
  632. })
  633. },
  634. topGap () {
  635. this.$nextTick(function () {
  636. this.refresh()
  637. })
  638. },
  639. bottomGap () {
  640. this.$nextTick(function () {
  641. this.refresh()
  642. })
  643. },
  644. fontFamily () {
  645. this.$nextTick(function () {
  646. this.refresh()
  647. })
  648. },
  649. autoplay (newVal) {
  650. if ( this.pageType == 'scroll' ) {
  651. if ( !this.isLoading ) this.autoplaySync = newVal
  652. } else {
  653. this.autoplaySync = newVal
  654. }
  655. }
  656. }
  657. }
  658. </script>
  659. <style scoped>
  660. .yingbing-reader {
  661. /* #ifndef APP-NVUE */
  662. width: 100%;
  663. height: 100%;
  664. /* #endif */
  665. /* #ifdef APP-NVUE */
  666. flex: 1;
  667. /* #endif */
  668. position: relative;
  669. overflow: hidden;
  670. }
  671. .yingbing-reader-content-loading {
  672. display: flex;
  673. flex-direction: column;
  674. align-items: center;
  675. justify-content: center;
  676. }
  677. .yingbing-reader-content-loading-text {
  678. margin-top: 10px;
  679. font-size: 15px;
  680. }
  681. .yingbing-reader-content-loading-tip {
  682. margin-top: 10px;
  683. font-size: 12px;
  684. }
  685. .yingbing-hidden {
  686. visibility: hidden;
  687. }
  688. /* 滚动模式样式 */
  689. .yingbing-scroll-reader {
  690. /* #ifndef APP-NVUE */
  691. width: 100%;
  692. height: 100%;
  693. display: flex;
  694. flex-direction: column;
  695. box-sizing: border-box;
  696. /* #endif */
  697. /* #ifdef APP-NVUE */
  698. flex: 1;
  699. /* #endif */
  700. position: relative;
  701. }
  702. .yingbing-scroll {
  703. flex: 1;
  704. position: relative;
  705. }
  706. /* 翻页模式样式 */
  707. .yingbing-reader-absolute {
  708. position: absolute;
  709. left: 0;
  710. top: 0;
  711. right: 0;
  712. bottom: 0;
  713. }
  714. .yingbing-flip-reader .loading-box {
  715. background-color: rgba(0,0,0,.2);
  716. align-items: center;
  717. justify-content: center;
  718. /* #ifdef APP-NVUE */
  719. flex: 1;
  720. /* #endif */
  721. /* #ifndef APP-NVUE */
  722. display: flex;
  723. flex-direction: column;
  724. height: 100%;
  725. /* #endif */
  726. }
  727. .yingbing-flip-reader .loading-text {
  728. color: #ffffff;
  729. }
  730. .yingbing-flip-reader-wrapper {
  731. /* #ifndef APP-NVUE */
  732. display: flex;
  733. flex-direction: column;
  734. /* #endif */
  735. }
  736. .yingbing-flip-reader-content {
  737. position: relative;
  738. flex: 1;
  739. }
  740. </style>