page.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import React from 'react';
  2. import { Tooltip } from 'antd';
  3. import './index.less';
  4. import Page from '@src/containers/Page';
  5. import Icon from '../../../components/Icon';
  6. import Progress from '../../../components/Progress';
  7. import Assets from '../../../../../src/components/Assets';
  8. import { Sentence } from '../../../stores/sentence';
  9. export default class extends Page {
  10. constructor(props) {
  11. super(props);
  12. this.page = 0;
  13. this.inited = false;
  14. this.timeout = null;
  15. this.articleMap = {};
  16. this.pageLine = 100;
  17. }
  18. init() {
  19. this.lastTime = new Date();
  20. }
  21. initState() {
  22. return { showJump: false, showMenu: false, currentPage: 0, totalPage: 0 };
  23. }
  24. initData() {
  25. const { chapter = 0, part = 0 } = this.state.search;
  26. let { page = 0 } = this.state.search;
  27. const handler = this.refreshSentence();
  28. handler.then(() => {
  29. if (chapter > 0) {
  30. if (!part) {
  31. ({ page } = this.searchArticle(chapter, 1));
  32. } else {
  33. ({ page } = this.searchArticle(chapter, part));
  34. }
  35. }
  36. this.jumpPage(page);
  37. });
  38. }
  39. refreshSentence() {
  40. if (this.inited) return Promise.resolve();
  41. return Sentence.listArticle().then(result => {
  42. const articleMap = {};
  43. let totalPage = 0;
  44. let maxChapter = 0;
  45. let introduction = null;
  46. result.forEach((article) => {
  47. if (article.chapter === 0) introduction = article;
  48. if (!articleMap[article.chapter]) {
  49. articleMap[article.chapter] = [];
  50. if (article.chapter > maxChapter) maxChapter = article.chapter;
  51. }
  52. article.startPage = totalPage + 1;
  53. article.endPage = totalPage + article.pages;
  54. totalPage += article.pages;
  55. articleMap[article.chapter].push(article);
  56. });
  57. this.setState({ articleMap, totalPage, maxChapter });
  58. return introduction;
  59. }).then((introduction) => {
  60. return Sentence.getInfo().then(result => {
  61. const map = {};
  62. // 添加前言
  63. if (introduction) {
  64. result.chapters.unshift({
  65. title: introduction.title,
  66. value: 0,
  67. });
  68. }
  69. result.chapters.forEach((row) => {
  70. map[row.value] = row;
  71. });
  72. this.setState({ sentence: result, chapterMap: map });
  73. });
  74. }).then(() => {
  75. this.inited = true;
  76. });
  77. }
  78. refreshArticle(articleId) {
  79. if (this.articleMap[articleId]) return Promise.resolve(this.articleMap[articleId]);
  80. return Sentence.detailArticle(articleId).then(result => {
  81. this.articleMap[articleId] = result;
  82. return result;
  83. });
  84. }
  85. prevPage() {
  86. const { currentPage } = this.state;
  87. if (currentPage > 1) {
  88. this.jumpPage(currentPage - 1);
  89. }
  90. }
  91. nextPage() {
  92. const { currentPage, totalPage } = this.state;
  93. if (currentPage < totalPage) {
  94. this.jumpPage(currentPage + 1);
  95. }
  96. }
  97. jumpPage(targetPage) {
  98. // 计算哪篇文章
  99. const { target, index, allow } = this.computeArticle(targetPage);
  100. console.log(targetPage, target, index);
  101. if (!allow) {
  102. // todo 无法访问:非试用
  103. return;
  104. }
  105. this.page = targetPage;
  106. this.setState({ inputPage: targetPage });
  107. const { article = {} } = this.state;
  108. this.updateProgress(target, index, article);
  109. this.refreshArticle(target.id).then((row) => {
  110. this.setState({ article: row, index, currentPage: targetPage });
  111. });
  112. }
  113. computeArticle(page) {
  114. const { articleMap, maxChapter, sentence } = this.state;
  115. let target = null;
  116. let index = 0;
  117. const allow = true || sentence.code || page <= sentence.trailPages;
  118. for (let i = 0; i <= maxChapter; i += 1) {
  119. const list = articleMap[i];
  120. if (!list || list.length === 0) continue;
  121. for (let j = 0; j < list.length; j += 1) {
  122. const article = list[j];
  123. if (article.endPage >= page) {
  124. target = article;
  125. index = page - article.startPage;
  126. // if (!sentence.code) {
  127. // if (!article.isTrail) {
  128. // allow = false;
  129. // } else if (index < article.trailStart - 1) {
  130. // allow = false;
  131. // } else if (index > article.trailEnd - 1) {
  132. // allow = false;
  133. // }
  134. // }
  135. return { target, index, allow };
  136. }
  137. }
  138. }
  139. return { allow };
  140. }
  141. searchArticle(chapter, part) {
  142. const { articleMap } = this.state;
  143. let target = null;
  144. let page = 0;
  145. if (!part) part = 1;
  146. const list = articleMap[chapter];
  147. for (let j = 0; j < list.length; j += 1) {
  148. const article = list[j];
  149. if (article.part === Number(part)) {
  150. target = article;
  151. page = article.startPage;
  152. // if (sentence.code) {
  153. // page = article.startPage;
  154. // } else {
  155. // // 试用章节页码
  156. // page = article.startPage + article.trailPage - 1;
  157. // }
  158. break;
  159. }
  160. }
  161. return { target, page };
  162. }
  163. updateProgress(target, index, current) {
  164. if (this.timeout) {
  165. clearTimeout(this.timeout);
  166. this.timeout = null;
  167. }
  168. const now = new Date();
  169. const time = (now.getTime() - this.lastTime.getTime()) / 1000;
  170. this.lastTime = now;
  171. const progress = index + 1 * 100 / target.pages;
  172. Sentence.updateProgress(target.chapter, target.part, progress, time, current.chapter, current.page);
  173. this.timeout = setTimeout(() => {
  174. // 最长5分钟阅读时间
  175. Sentence.updateProgress(0, 0, 0, 5 * 60, target.chapter, target.part);
  176. }, 5 * 60 * 1000);
  177. }
  178. renderView() {
  179. return (
  180. <div className="layout">
  181. {this.renderBody()}
  182. {this.renderRight()}
  183. {this.renderBottom()}
  184. {this.renderProgress()}
  185. </div>
  186. );
  187. }
  188. renderBody() {
  189. const { showMenu, article, index, chapterMap = {} } = this.state;
  190. return article && (
  191. <div className="layout-body">
  192. <div className="crumb">千行长难句解析 >> {(chapterMap[article.chapter] || {}).title}</div>
  193. {article.chapter > 0 && <div className="title">{article.title}</div>}
  194. <div className="overload" style={{ top: index * -20 * this.pageLine }}>
  195. <div className="text" dangerouslySetInnerHTML={{ __html: article.content }} />
  196. </div>
  197. {showMenu && this.renderMenu()}
  198. </div>
  199. );
  200. }
  201. renderMenu() {
  202. const { sentence = {}, articleMap } = this.state;
  203. const { chapters = [] } = sentence;
  204. let page = 1;
  205. const code = !!sentence.code;
  206. // todo 鼠标移上显示需要购买后访问
  207. const message = '购买后访问';
  208. return (
  209. <div className="layout-menu">
  210. <div className="title">目录</div>
  211. <div className="close" onClick={() => {
  212. this.setState({ showMenu: false });
  213. }}>x</div>
  214. <div className="chapter">
  215. {chapters.map(chapter => {
  216. if (chapter.exercise) {
  217. return [<div className={'chapter-item trail'}>
  218. {chapter.title}
  219. </div>];
  220. }
  221. chapter.startPage = page;
  222. const list = code ? [<div className={'chapter-item'} onClick={() => {
  223. this.jumpPage(chapter.startPage);
  224. }}>
  225. {chapter.title}<div className="page">{chapter.startPage}</div>
  226. </div>] : [<Tooltip title={message}><div className={'chapter-item trail'}>
  227. {chapter.title}<div className="page">{chapter.startPage}</div>
  228. </div></Tooltip>];
  229. if (chapter.value) {
  230. (articleMap[chapter.value] || []).forEach((article) => {
  231. // 得到下一章节page
  232. page += article.pages;
  233. const item = code ? <div className={'part-item'} onClick={() => {
  234. if (code) this.jumpPage(article.startPage);
  235. }}>
  236. {article.title}<div className="page">{article.startPage}</div>
  237. </div> : <Tooltip title={message}><div className={'part-item trail'}>
  238. {article.title}<div className="page">{article.startPage}</div>
  239. </div></Tooltip>;
  240. list.push(item);
  241. });
  242. }
  243. return list;
  244. })}
  245. </div>
  246. </div>
  247. );
  248. }
  249. renderRight() {
  250. return (
  251. <div className="layout-right">
  252. <div className="m-b-1" onClick={() => {
  253. this.setState({ showMenu: true });
  254. }}>
  255. <Icon name="menu" />
  256. </div>
  257. <div className="m-b-1" onClick={() => {
  258. this.prevPage();
  259. }}>
  260. <Icon name="down_turn" />
  261. </div>
  262. <div className="m-b-1" onClick={() => {
  263. this.nextPage();
  264. }}>
  265. <Icon name="up_turn" />
  266. </div>
  267. </div>
  268. );
  269. }
  270. renderBottom() {
  271. const { showJump, currentPage, totalPage } = this.state;
  272. return (
  273. <div className="layout-bottom">
  274. <span className="per">{totalPage ? parseInt(currentPage * 100 / totalPage, 10) : 0}%</span>
  275. <span className="num">{currentPage}/{totalPage}</span>
  276. <span className="btn">
  277. <Assets name={showJump ? 'unfold_icon_down' : 'unfold_icon_up'} onClick={() => {
  278. this.setState({ showJump: !showJump });
  279. }} />
  280. <div hidden={!showJump} className="jump">
  281. <span className="text">当前页</span>
  282. <input className="input" value={this.state.inputPage} onChange={(e) => {
  283. this.page = Number(e.target.value);
  284. this.setState({ inputPage: e.target.value });
  285. }} />
  286. <Assets name="yes_icon" onClick={() => {
  287. if (this.page < 1 || this.page > totalPage) return;
  288. this.jumpPage(this.page);
  289. }} />
  290. </div>
  291. </span>
  292. </div>
  293. );
  294. }
  295. renderProgress() {
  296. const { currentPage, totalPage } = this.state;
  297. return (
  298. <div className="layout-progress">
  299. <Progress size="small" theme="theme" radius={false} progress={totalPage ? parseInt(currentPage * 100 / totalPage, 10) : 0} />
  300. </div>
  301. );
  302. }
  303. }