page.js 8.4 KB

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