四零语境前端代码仓库
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.

439 lines
12 KiB

  1. <template>
  2. <view class="yingbing-whole-reader"
  3. id="yingbing-whole-reader"
  4. :data-start="start"
  5. :data-color="color"
  6. :data-title="title"
  7. :data-background="background"
  8. :data-split="split"
  9. :data-fontSize="fontSize"
  10. :data-fontFamily="fontFamily"
  11. :data-lineGap="lineGap"
  12. :data-topGap="topGap"
  13. :data-bottomGap="bottomGap"
  14. :data-slide="slide"
  15. :data-pageType="pageType"
  16. :data-backShow="backShow"
  17. :data-headerShow="headerShow"
  18. :data-footerShow="footerShow"
  19. :data-autoplay="autoplaySync"
  20. :data-interval="interval"
  21. :data-unableClickPage="unableClickPage"
  22. :data-selectable="selectable"
  23. :selectable="selectable" :change:selectable="wholeReader.selectableWatcher"
  24. :unableClickPage="unableClickPage" :change:unableClickPage="wholeReader.unableClickPageWatcher"
  25. :autoplay="autoplaySync" :change:autoplay="wholeReader.autoplayWatcher"
  26. :interval="interval" :change:interval="wholeReader.intervalWatcher"
  27. :content="contentSync" :change:content="wholeReader.contentWatcher"
  28. :fontSize="fontSize" :change:fontSize="wholeReader.fontSizeWatcher"
  29. :fontFamily="fontFamily" :change:fontFamily="wholeReader.fontFamilyWatcher"
  30. :split="split" :change:split="wholeReader.splitWatcher"
  31. :lineGap="lineGap" :change:lineGap="wholeReader.lineGapWatcher"
  32. :topGap="topGap" :change:topGap="wholeReader.topGapWatcher"
  33. :bottomGap="bottomGap" :change:bottomGap="wholeReader.bottomGapWatcher"
  34. :slide="slide" :change:slide="wholeReader.slideWatcher"
  35. :pageType="pageType" :change:pageType="wholeReader.pageTypeWatcher"
  36. :backShow="backShow" :change:backShow="wholeReader.backShowWatcher"
  37. :headerShow="headerShow" :change:headerShow="wholeReader.headerShowWatcher"
  38. :footerShow="footerShow" :change:footerShow="wholeReader.footerShowWatcher"
  39. :background="background" :change:background="wholeReader.backgroundWatcher"
  40. :color="color" :change:color="wholeReader.colorWatcher"
  41. :isRender="isRender" :change:isRender="wholeReader.renderWatcher"
  42. :isRefresh="isRefresh" :change:isRefresh="wholeReader.refreshWatcher"
  43. :pageTo="pageTo" :change:pageTo="wholeReader.pageToWatcher"
  44. @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend">
  45. </view>
  46. </template>
  47. <script>
  48. import TouchClickMixin from '../mixin/touch-click.js'
  49. export default {
  50. options: {
  51. addGlobalClass: true,
  52. virtualHost: true, // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
  53. },
  54. mixins: [TouchClickMixin],
  55. props: {
  56. //自动播放
  57. autoplay: {
  58. type: Boolean,
  59. default: false
  60. },
  61. interval: {
  62. type: [Number, String],
  63. default: 5000
  64. },
  65. //字体颜色
  66. color: {
  67. type: String,
  68. default: '#333333'
  69. },
  70. //字体大小(单位px)
  71. fontSize: {
  72. type: [Number, String],
  73. default: 15
  74. },
  75. fontFamily: {
  76. type: String,
  77. default: 'Arial'
  78. },
  79. //背景颜色
  80. background: {
  81. type: String,
  82. default: '#fcd281'
  83. },
  84. //分隔符
  85. split: {
  86. type: String,
  87. default: ''
  88. },
  89. //翻页方式
  90. pageType: {
  91. type: String,
  92. default: 'scroll'
  93. },
  94. //行间距(单位px)
  95. lineGap: {
  96. type: [Number, String],
  97. default: 15
  98. },
  99. //页面左右边距(单位px)
  100. slide: {
  101. type: [Number, String],
  102. default: 20
  103. },
  104. //页面上边距(单位px)
  105. topGap: {
  106. type: [Number, String],
  107. default: 10
  108. },
  109. //页面下边距(单位px)
  110. bottomGap: {
  111. type: [Number, String],
  112. default: 10
  113. },
  114. backShow: {
  115. type: Boolean,
  116. default: false
  117. },
  118. headerShow: {
  119. type: Boolean,
  120. default: true
  121. },
  122. footerShow: {
  123. type: Boolean,
  124. default: true
  125. },
  126. //是否关闭点击左右2侧位置翻页
  127. unableClickPage: {
  128. type: Boolean,
  129. default: false
  130. },
  131. //开启文本选择
  132. selectable: {
  133. type: Boolean,
  134. default: false
  135. }
  136. },
  137. computed: {
  138. contentSync () {
  139. return {content: this.content}
  140. }
  141. },
  142. data () {
  143. return {
  144. title: '',
  145. content: '',
  146. start: 0,
  147. isRender: false,//是否渲染页面
  148. isRefresh: false,//是否刷新页面
  149. pageTo: 0
  150. }
  151. },
  152. created() {
  153. this.autoplaySync = this.autoplay
  154. },
  155. beforeDestroy() {
  156. if ( this.touchTimer ) {//清楚定时任务
  157. clearTimeout(this.touchTimer)
  158. this.touchTimer = null
  159. }
  160. if ( this.longTimer ) {//清楚定时任务
  161. clearTimeout(this.longTimer)
  162. this.longTimer = null
  163. }
  164. },
  165. methods: {
  166. //初始化
  167. init ({content, title, start}) {
  168. this.content = content
  169. this.title = title
  170. this.$emit('setCatalog', this.getCatalog(this.content));
  171. this.change(start)
  172. },
  173. //跳转
  174. async change (start) {
  175. const rect = await this.getRect()
  176. this.windowWidth = rect.width
  177. this.windowHeight = rect.height
  178. this.start = parseInt(start)
  179. this.isRender = false
  180. this.$nextTick(function () {
  181. this.isRender = true
  182. })
  183. },
  184. //刷新
  185. async refresh () {
  186. const rect = await this.getRect()
  187. this.windowWidth = rect.width
  188. this.windowHeight = rect.height
  189. this.isRefresh = false
  190. this.$nextTick(function () {
  191. this.isRefresh = true
  192. })
  193. },
  194. prev () {
  195. this.pageTo = 0
  196. this.$nextTick(function(){
  197. this.pageTo = -1
  198. })
  199. },
  200. next () {
  201. this.pageTo = 0
  202. this.$nextTick(function(){
  203. this.pageTo = 1
  204. })
  205. },
  206. //抛出阅读页面改变事件
  207. handleChange (e) {
  208. this.$emit('change', e)
  209. },
  210. //往后翻页完成事件
  211. handleEnded () {
  212. this.$emit('ended')
  213. },
  214. //往前翻页完成事件
  215. handleStarted () {
  216. this.$emit('started')
  217. },
  218. handleBack () {
  219. this.$emit('back')
  220. },
  221. //使用正则获取章节目录 并抛出事件
  222. getCatalog (content) {
  223. // const reg = new RegExp(/(第?[一二两三四五六七八九十○零百千万亿0-91234567890※✩★☆]{1,6}[章回卷节折篇幕集部]?[、.-\s][^\n]*)[_,-]?/g);
  224. const reg = new RegExp(/(第+[一二两三四五六七八九十○零百千万亿0-91234567890※✩★☆]{1,6}[章回卷节折篇幕集部]?[、.-\s::,,][^\n]*)[_,-]?/g)
  225. let match = '';
  226. let catalog = [];
  227. let chapter = 0
  228. while ((match = reg.exec(content)) != null) {
  229. chapter++
  230. catalog.push({
  231. title: match[0],
  232. start: match.index
  233. })
  234. }
  235. return catalog.length > 0 ? catalog : [{
  236. start: 0,
  237. title: this.title || '整章'
  238. }]
  239. },
  240. getRect () {
  241. return new Promise(resolve => {
  242. uni.createSelectorQuery().in(this).select('.yingbing-whole-reader').boundingClientRect(data => {
  243. resolve(data)
  244. }).exec();
  245. })
  246. }
  247. },
  248. watch: {
  249. autoplay (newVal) {
  250. this.autoplaySync = newVal
  251. }
  252. }
  253. }
  254. </script>
  255. <!-- #ifdef H5 || APP-VUE -->
  256. <script lang="renderjs" module="wholeReader" type="module">
  257. import Wholereader from "./wholereader.js"
  258. export default {
  259. data () {
  260. return {
  261. content: '',
  262. whole: null
  263. }
  264. },
  265. methods: {
  266. renderWatcher (newVal) {
  267. if ( newVal && this.content ) {
  268. if ( this.whole ) this.whole.destroy()
  269. this.whole = new Wholereader({
  270. container: document.querySelector('#yingbing-whole-reader'),
  271. autoplay: this.getData('autoplay') == 'true' ? true : false,
  272. interval: this.getData('interval'),
  273. content: this.getData('content'),
  274. title: this.getData('title'),
  275. color: this.getData('color'),
  276. background: this.getData('background'),
  277. fontSize: this.getData('fontSize'),
  278. fontFamily: this.getData('fontFamily'),
  279. slide: this.getData('slide'),
  280. topGap: this.getData('topGap'),
  281. bottomGap: this.getData('bottomGap'),
  282. lineGap: this.getData('lineGap'),
  283. split: this.getData('split'),
  284. headerShow: this.getData('headerShow') == 'true' ? true : false,
  285. footerShow: this.getData('footerShow') == 'true' ? true : false,
  286. backShow: this.getData('backShow') == 'true' ? true : false,
  287. pageType: this.getData('pageType'),
  288. unableClickPage: this.getData('unableClickPage') == 'true' ? true : false,
  289. selectable: this.getData('selectable') == 'true' ? true : false
  290. })
  291. this.whole.render(this.getData('start'))//开始渲染页面
  292. this.whole.on('change', e => {//注册翻页改变事件
  293. this.triggerMethod('handleChange', e)
  294. })
  295. this.whole.on('ended', () => {//注册往后翻页完成事件
  296. this.triggerMethod('handleEnded')
  297. })
  298. this.whole.on('started', () => {//注册往前翻页完成事件
  299. this.triggerMethod('handleStarted')
  300. })
  301. this.whole.on('back', () => {//点击返回按钮事件
  302. this.triggerMethod('handleBack')
  303. })
  304. }
  305. },
  306. refreshWatcher (newVal) {
  307. if ( this.refreshTimer ) {
  308. window.clearTimeout(this.refreshTimer)
  309. this.refreshTimer = null
  310. }
  311. if ( newVal && this.whole ) {
  312. this.refreshTimer = window.setTimeout(() => {
  313. this.whole.refresh()
  314. }, 200)
  315. }
  316. },
  317. pageToWatcher (newVal) {
  318. if ( newVal == -1 ) this.whole && this.whole.prev()
  319. if ( newVal == 1 ) this.whole && this.whole.next()
  320. },
  321. autoplayWatcher (newVal) {
  322. this.whole && this.whole.setConfig('autoplay', newVal)
  323. },
  324. intervalWatcher (newVal) {
  325. this.whole && this.whole.setConfig('interval', newVal)
  326. },
  327. contentWatcher (newVal, oldVal) {
  328. this.content = newVal.content
  329. },
  330. fontSizeWatcher (newVal) {
  331. this.whole && this.whole.setConfig('fontSize', newVal)
  332. this.refreshWatcher(true)
  333. },
  334. fontFamilyWatcher (newVal) {
  335. this.whole && this.whole.setConfig('fontFamily', newVal)
  336. this.refreshWatcher(true)
  337. },
  338. splitWatcher (newVal) {
  339. this.whole && this.whole.setConfig('split', newVal)
  340. this.refreshWatcher(true)
  341. },
  342. lineGapWatcher (newVal) {
  343. this.whole && this.whole.setConfig('lineGap', newVal)
  344. this.refreshWatcher(true)
  345. },
  346. topGapWatcher (newVal) {
  347. this.whole && this.whole.setConfig('topGap', newVal)
  348. this.refreshWatcher(true)
  349. },
  350. bottomGapWatcher (newVal) {
  351. this.whole && this.whole.setConfig('bottomGap', newVal)
  352. this.refreshWatcher(true)
  353. },
  354. slideWatcher (newVal) {
  355. this.whole && this.whole.setConfig('slide', newVal)
  356. this.refreshWatcher(true)
  357. },
  358. pageTypeWatcher (newVal) {
  359. this.whole && this.whole.setConfig('pageType', newVal)
  360. this.refreshWatcher(true)
  361. },
  362. backShowWatcher (newVal) {
  363. this.whole && this.whole.setConfig('backShow', newVal)
  364. this.refreshWatcher(true)
  365. },
  366. headerShowWatcher (newVal) {
  367. this.whole && this.whole.setConfig('headerShow', newVal)
  368. this.refreshWatcher(true)
  369. },
  370. footerShowWatcher (newVal) {
  371. this.whole && this.whole.setConfig('footerShow', newVal)
  372. this.refreshWatcher(true)
  373. },
  374. backgroundWatcher (newVal) {
  375. this.whole && this.whole.setConfig('background', newVal)
  376. this.refreshWatcher(true)
  377. },
  378. colorWatcher (newVal) {
  379. this.whole && this.whole.setConfig('color', newVal)
  380. this.refreshWatcher(true)
  381. },
  382. selectableWatcher (newVal) {
  383. this.whole && this.whole.setConfig('selectable', newVal)
  384. this.refreshWatcher(true)
  385. },
  386. unableClickPageWatcher (newVal) {
  387. this.whole && this.whole.setConfig('unableClickPage', newVal)
  388. },
  389. getData (name) {
  390. const dom = document.getElementById('yingbing-whole-reader')
  391. if ( name == 'content' ) {
  392. return this.content.replace(/\t/g, ' ').replace(/ /g, ' ')
  393. } else if ( ['fontSize', 'lineGap', 'topGap', 'bottomGap', 'slide', 'start', 'interval'].includes(name) ) {
  394. return parseInt(dom.getAttribute('data-' + name))
  395. } else {
  396. return dom.getAttribute('data-' + name)
  397. }
  398. },
  399. triggerMethod (name, args) {
  400. // #ifndef H5
  401. // UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', {
  402. // cid: this._$id,
  403. // method: name,
  404. // args: args
  405. // })
  406. this.$ownerInstance.callMethod(name, args)
  407. // #endif
  408. // #ifdef H5
  409. this[name](args)
  410. // #endif
  411. }
  412. }
  413. }
  414. </script>
  415. <!-- #endif -->
  416. <style scoped>
  417. @import url(/uni_modules/yingbing-ReadPage/components/yingbing-whole-reader/wholereader.css);
  418. .yingbing-whole-reader {
  419. width: 100%;
  420. height: 100%;
  421. box-sizing: border-box;
  422. overflow: hidden;
  423. }
  424. .yingbing-reader-content-loading {
  425. display: flex;
  426. flex-direction: column;
  427. align-items: center;
  428. justify-content: center;
  429. }
  430. .yingbing-reader-content-loading-text {
  431. margin-top: 10px;
  432. font-size: 15px;
  433. }
  434. .yingbing-reader-content-loading-tip {
  435. margin-top: 10px;
  436. font-size: 12px;
  437. }
  438. </style>