page.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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. import { Main } from '../../../stores/main';
  10. import { formatMoney, formatDate } from '../../../../../src/services/Tools';
  11. export default class extends Page {
  12. constructor(props) {
  13. super(props);
  14. this.page = 0;
  15. this.inited = false;
  16. this.timeout = null;
  17. this.articleMap = {};
  18. this.pageLine = 100;
  19. }
  20. initState() {
  21. return { article: {}, showJump: false, showMenu: false, currentPage: 0, totalPage: 0 };
  22. }
  23. init() {
  24. this.lastTime = new Date();
  25. Main.getSentence().then(result => {
  26. this.setState({ info: result });
  27. });
  28. }
  29. initData() {
  30. const { chapter = 0, part = 0 } = this.state.search;
  31. let { page = 0 } = this.state.search;
  32. const handler = this.refreshSentence();
  33. handler.then(() => {
  34. if (chapter > 0) {
  35. if (!part) {
  36. ({ page } = this.searchArticle(chapter, 1));
  37. } else {
  38. ({ page } = this.searchArticle(chapter, part));
  39. }
  40. }
  41. this.jumpPage(page);
  42. });
  43. }
  44. refreshSentence() {
  45. if (this.inited) return Promise.resolve();
  46. return Sentence.listArticle()
  47. .then(result => {
  48. const articleMap = {};
  49. let totalPage = 0;
  50. let maxChapter = 0;
  51. let introduction = null;
  52. result.forEach(article => {
  53. if (article.chapter === 0) introduction = article;
  54. if (!articleMap[article.chapter]) {
  55. articleMap[article.chapter] = [];
  56. if (article.chapter > maxChapter) maxChapter = article.chapter;
  57. }
  58. article.startPage = totalPage + 1;
  59. article.endPage = totalPage + article.pages;
  60. totalPage += article.pages;
  61. articleMap[article.chapter].push(article);
  62. });
  63. this.setState({ articleMap, totalPage, maxChapter });
  64. return introduction;
  65. })
  66. .then(introduction => {
  67. return Sentence.getInfo().then(result => {
  68. const map = {};
  69. // 添加前言
  70. if (introduction) {
  71. result.chapters.unshift({
  72. title: introduction.title,
  73. value: 0,
  74. });
  75. }
  76. result.chapters.forEach(row => {
  77. map[row.value] = row;
  78. });
  79. this.setState({ sentence: result, chapterMap: map });
  80. });
  81. })
  82. .then(() => {
  83. this.inited = true;
  84. });
  85. }
  86. refreshArticle(articleId) {
  87. if (this.articleMap[articleId]) return Promise.resolve(this.articleMap[articleId]);
  88. return Sentence.detailArticle(articleId).then(result => {
  89. this.articleMap[articleId] = result;
  90. return result;
  91. });
  92. }
  93. prevPage() {
  94. const { currentPage } = this.state;
  95. if (currentPage > 1) {
  96. this.jumpPage(currentPage - 1);
  97. }
  98. }
  99. nextPage() {
  100. const { sentence = {} } = this.state;
  101. const { currentPage, totalPage } = this.state;
  102. if (currentPage < totalPage) {
  103. this.jumpPage(currentPage + 1);
  104. } else {
  105. const code = !!sentence.code;
  106. if (!code) {
  107. this.setState({ article: null });
  108. }
  109. }
  110. }
  111. jumpPage(targetPage) {
  112. // 计算哪篇文章
  113. const { target, index, allow } = this.computeArticle(targetPage);
  114. if (!allow) {
  115. this.setState({ article: null });
  116. return;
  117. }
  118. this.page = targetPage;
  119. this.setState({ inputPage: targetPage });
  120. const { article = {} } = this.state;
  121. this.updateProgress(target, index, article);
  122. this.refreshArticle(target.id).then(row => {
  123. this.setState({ article: row, index, currentPage: targetPage });
  124. });
  125. }
  126. computeArticle(page) {
  127. const { articleMap, maxChapter, sentence } = this.state;
  128. let target = null;
  129. let index = 0;
  130. const allow = true || sentence.code || page <= sentence.trailPages;
  131. for (let i = 0; i <= maxChapter; i += 1) {
  132. const list = articleMap[i];
  133. if (!list || list.length === 0) continue;
  134. for (let j = 0; j < list.length; j += 1) {
  135. const article = list[j];
  136. if (article.endPage >= page) {
  137. target = article;
  138. index = page - article.startPage;
  139. // if (!sentence.code) {
  140. // if (!article.isTrail) {
  141. // allow = false;
  142. // } else if (index < article.trailStart - 1) {
  143. // allow = false;
  144. // } else if (index > article.trailEnd - 1) {
  145. // allow = false;
  146. // }
  147. // }
  148. return { target, index, allow };
  149. }
  150. }
  151. }
  152. return { allow };
  153. }
  154. searchArticle(chapter, part) {
  155. const { articleMap } = this.state;
  156. let target = null;
  157. let page = 0;
  158. if (!part) part = 1;
  159. const list = articleMap[chapter];
  160. for (let j = 0; j < list.length; j += 1) {
  161. const article = list[j];
  162. if (article.part === Number(part)) {
  163. target = article;
  164. page = article.startPage;
  165. // if (sentence.code) {
  166. // page = article.startPage;
  167. // } else {
  168. // // 试用章节页码
  169. // page = article.startPage + article.trailPage - 1;
  170. // }
  171. break;
  172. }
  173. }
  174. return { target, page };
  175. }
  176. updateProgress(target, index, current) {
  177. if (this.timeout) {
  178. clearTimeout(this.timeout);
  179. this.timeout = null;
  180. }
  181. const progress = ((index + 1) * 100) / target.pages;
  182. if (target.chapter === current.chapter && target.part === current.part) {
  183. Sentence.updateProgress(target.chapter, target.part, progress);
  184. } else {
  185. const now = new Date();
  186. const time = (now.getTime() - this.lastTime.getTime()) / 1000;
  187. this.lastTime = now;
  188. Sentence.updateProgress(target.chapter, target.part, progress, time, current.chapter, current.part);
  189. }
  190. this.timeout = setTimeout(() => {
  191. // 最长5分钟阅读时间
  192. Sentence.updateProgress(0, 0, 0, 5 * 60, target.chapter, target.part);
  193. this.lastTime = null;
  194. }, 5 * 60 * 1000);
  195. }
  196. renderView() {
  197. return (
  198. <div className="layout">
  199. {this.renderBody()}
  200. {this.renderRight()}
  201. {this.renderBottom()}
  202. {this.renderProgress()}
  203. </div>
  204. );
  205. }
  206. renderBody() {
  207. const { showMenu, article, index, chapterMap = {}, info = {}, sentence = {} } = this.state;
  208. return article ? (
  209. <div className="layout-body">
  210. <div className="crumb">千行长难句解析 >> {(chapterMap[article.chapter] || {}).title}</div>
  211. {article.chapter > 0 && <div className="title">{article.title}</div>}
  212. <div className="overload" style={{ top: index * -20 * this.pageLine }}>
  213. <div className="text" dangerouslySetInnerHTML={{ __html: article.content }} />
  214. </div>
  215. {showMenu && this.renderMenu()}
  216. </div>
  217. ) : (<div className="layout-body">
  218. <div className="free-over">
  219. <div className="free-over-title">试读已结束,购买后可继续阅读。</div>
  220. <div className="free-over-btn" onClick={() => {
  221. openLink(info.link);
  222. }}>{formatMoney(info.price)} | 立即购买</div>
  223. <div className="free-over-desc">
  224. {(sentence.comments || []).map(row => {
  225. return [
  226. <div className="free-over-desc-title">{row.nickname} {formatDate(row.createTime, 'YYYY-MM-DD')}</div>,
  227. <div className="free-over-desc-content">{row.content}</div>,
  228. ];
  229. })}
  230. </div>
  231. </div>
  232. </div>);
  233. }
  234. renderMenu() {
  235. const { sentence = {}, articleMap } = this.state;
  236. const { chapters = [] } = sentence;
  237. let page = 1;
  238. const code = !!sentence.code;
  239. const message = '购买后访问';
  240. return (
  241. <div className="layout-menu">
  242. <div className="title">目录</div>
  243. <div
  244. className="close"
  245. onClick={() => {
  246. this.setState({ showMenu: false });
  247. }}
  248. >
  249. x
  250. </div>
  251. <div className="chapter">
  252. {chapters.map(chapter => {
  253. if (chapter.exercise) {
  254. return [<div className={'chapter-item trail'}>{chapter.title}</div>];
  255. }
  256. chapter.startPage = page;
  257. const _item = code ? (
  258. <div
  259. className={'chapter-item'}
  260. onClick={() => {
  261. this.jumpPage(chapter.startPage);
  262. }}
  263. >
  264. {chapter.title}
  265. <div className="page">{chapter.startPage}</div>
  266. </div>
  267. ) : (<Tooltip title={message}>
  268. <div className={'chapter-item trail'}>
  269. {chapter.title}
  270. <div className="page">{chapter.startPage}</div>
  271. </div>
  272. </Tooltip>);
  273. const list = [_item];
  274. if (chapter.value) {
  275. (articleMap[chapter.value] || []).forEach(article => {
  276. // 得到下一章节page
  277. page += article.pages;
  278. const item = code ? (
  279. <div
  280. className={'part-item'}
  281. onClick={() => {
  282. if (code) this.jumpPage(article.startPage);
  283. }}
  284. >
  285. {article.title}
  286. <div className="page">{article.startPage}</div>
  287. </div>
  288. ) : (<Tooltip title={message}>
  289. <div className={'part-item trail'}>
  290. {article.title}
  291. <div className="page">{article.startPage}</div>
  292. </div>
  293. </Tooltip>);
  294. list.push(item);
  295. });
  296. }
  297. return list;
  298. })}
  299. </div>
  300. </div>
  301. );
  302. }
  303. renderRight() {
  304. return (
  305. <div className="layout-right">
  306. <div
  307. className="m-b-1"
  308. onClick={() => {
  309. this.setState({ showMenu: true });
  310. }}
  311. >
  312. <Icon name="menu" />
  313. </div>
  314. <div
  315. className="m-b-1"
  316. onClick={() => {
  317. this.prevPage();
  318. }}
  319. >
  320. <Icon name="down_turn" />
  321. </div>
  322. <div
  323. className="m-b-1"
  324. onClick={() => {
  325. this.nextPage();
  326. }}
  327. >
  328. <Icon name="up_turn" />
  329. </div>
  330. </div>
  331. );
  332. }
  333. renderBottom() {
  334. const { showJump, currentPage, totalPage } = this.state;
  335. return (
  336. <div className="layout-bottom">
  337. <span className="per">{totalPage ? parseInt((currentPage * 100) / totalPage, 10) : 0}%</span>
  338. <span className="num">
  339. {currentPage}/{totalPage}
  340. </span>
  341. <span className="btn">
  342. <Assets
  343. name={showJump ? 'unfold_icon_down' : 'unfold_icon_up'}
  344. onClick={() => {
  345. this.setState({ showJump: !showJump });
  346. }}
  347. />
  348. <div hidden={!showJump} className="jump">
  349. <span className="text">当前页</span>
  350. <input
  351. className="input"
  352. value={this.state.inputPage}
  353. onChange={e => {
  354. this.page = Number(e.target.value);
  355. this.setState({ inputPage: e.target.value });
  356. }}
  357. />
  358. <Assets
  359. name="yes_icon"
  360. onClick={() => {
  361. if (this.page < 1 || this.page > totalPage) return;
  362. this.jumpPage(this.page);
  363. }}
  364. />
  365. </div>
  366. </span>
  367. </div>
  368. );
  369. }
  370. renderProgress() {
  371. const { currentPage, totalPage } = this.state;
  372. return (
  373. <div className="layout-progress">
  374. <Progress
  375. size="small"
  376. theme="theme"
  377. radius={false}
  378. progress={totalPage ? parseInt((currentPage * 100) / totalPage, 10) : 0}
  379. />
  380. </div>
  381. );
  382. }
  383. }