reader.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. "use strict";
  2. const common_vendor = require("../../common/vendor.js");
  3. const utils_api = require("../../utils/api.js");
  4. const _sfc_main = {
  5. data() {
  6. return {
  7. bookInfo: {
  8. id: null,
  9. title: "",
  10. author: "",
  11. image: ""
  12. },
  13. currentChapter: null,
  14. contentLines: [],
  15. chapterList: [],
  16. isLoading: false,
  17. showToolbar: false,
  18. showChapterModal: false,
  19. showNotesModal: false,
  20. showFontSizeModal: false,
  21. isDarkMode: false,
  22. fontSize: 32,
  23. fontSizeOptions: [
  24. { label: "小", value: 28 },
  25. { label: "中", value: 32 },
  26. { label: "大", value: 36 },
  27. { label: "特大", value: 40 }
  28. ],
  29. notes: [],
  30. newNote: "",
  31. showSelectionMenu: false,
  32. selectionMenuTop: 0,
  33. selectionMenuLeft: 0,
  34. selectedText: "",
  35. selectedLine: "",
  36. selectedLineIndex: -1
  37. };
  38. },
  39. onLoad(options) {
  40. if (options.bookId) {
  41. this.bookInfo.id = parseInt(options.bookId);
  42. }
  43. if (options.title) {
  44. this.bookInfo.title = decodeURIComponent(options.title);
  45. }
  46. if (options.image) {
  47. this.bookInfo.image = decodeURIComponent(options.image);
  48. }
  49. if (options.author) {
  50. this.bookInfo.author = decodeURIComponent(options.author);
  51. }
  52. if (this.bookInfo.id) {
  53. this.loadBookInfo();
  54. this.loadChapterList();
  55. }
  56. this.loadReadingProgress();
  57. this.loadSettings();
  58. this.loadNotes();
  59. },
  60. methods: {
  61. goBack() {
  62. common_vendor.index.navigateBack();
  63. },
  64. handleShare() {
  65. common_vendor.index.showToast({
  66. title: "分享功能",
  67. icon: "none"
  68. });
  69. },
  70. handleContentTap(e) {
  71. if (!this.showSelectionMenu) {
  72. this.toggleToolbar();
  73. }
  74. this.hideSelectionMenu();
  75. },
  76. toggleToolbar() {
  77. this.showToolbar = !this.showToolbar;
  78. },
  79. handleScroll(e) {
  80. if (this.showSelectionMenu) {
  81. this.hideSelectionMenu();
  82. }
  83. },
  84. // 文本选择功能
  85. handleLongPress(e, line, index) {
  86. if (line && line.trim()) {
  87. const touch = e.touches && e.touches[0] || e.changedTouches && e.changedTouches[0];
  88. if (touch) {
  89. this.selectedText = line;
  90. this.selectedLine = line;
  91. this.selectedLineIndex = index;
  92. setTimeout(() => {
  93. const query = common_vendor.index.createSelectorQuery().in(this);
  94. query.select(".content-area").boundingClientRect((rect) => {
  95. if (rect) {
  96. const menuWidth = 200;
  97. const menuHeight = 100;
  98. let left = touch.clientX - menuWidth / 2;
  99. if (left < 20)
  100. left = 20;
  101. if (left + menuWidth > rect.width - 20) {
  102. left = rect.width - menuWidth - 20;
  103. }
  104. let top = touch.clientY - menuHeight - 20;
  105. if (top < rect.top + 20) {
  106. top = touch.clientY + 20;
  107. }
  108. this.selectionMenuTop = top;
  109. this.selectionMenuLeft = left;
  110. this.showSelectionMenu = true;
  111. }
  112. }).exec();
  113. }, 50);
  114. }
  115. }
  116. },
  117. handleTouchStart(e) {
  118. },
  119. handleTouchEnd(e) {
  120. },
  121. hideSelectionMenu() {
  122. this.showSelectionMenu = false;
  123. this.selectedText = "";
  124. this.selectedLine = "";
  125. this.selectedLineIndex = -1;
  126. },
  127. copySelectedText() {
  128. if (this.selectedText) {
  129. common_vendor.index.setClipboardData({
  130. data: this.selectedText,
  131. success: () => {
  132. common_vendor.index.showToast({
  133. title: "已复制到剪贴板",
  134. icon: "success"
  135. });
  136. }
  137. });
  138. this.hideSelectionMenu();
  139. }
  140. },
  141. fallbackCopy(text) {
  142. const textarea = document.createElement("textarea");
  143. textarea.value = text;
  144. textarea.style.position = "fixed";
  145. textarea.style.opacity = "0";
  146. document.body.appendChild(textarea);
  147. textarea.select();
  148. try {
  149. document.execCommand("copy");
  150. common_vendor.index.showToast({
  151. title: "已复制到剪贴板",
  152. icon: "success"
  153. });
  154. } catch (err) {
  155. common_vendor.index.showToast({
  156. title: "复制失败",
  157. icon: "none"
  158. });
  159. }
  160. document.body.removeChild(textarea);
  161. },
  162. writeNoteFromSelection() {
  163. if (this.selectedText) {
  164. this.newNote = `"${this.selectedText}"`;
  165. this.hideSelectionMenu();
  166. this.showNotesModal = true;
  167. }
  168. },
  169. // 目录功能
  170. showChapterList() {
  171. this.showChapterModal = true;
  172. },
  173. closeChapterModal() {
  174. this.showChapterModal = false;
  175. },
  176. selectChapter(chapter) {
  177. this.currentChapter = chapter;
  178. this.loadChapterContent(chapter.id);
  179. this.closeChapterModal();
  180. this.showToolbar = false;
  181. },
  182. async loadBookInfo() {
  183. if (!this.bookInfo.id)
  184. return;
  185. try {
  186. const res = await utils_api.getBookById(this.bookInfo.id);
  187. if (res && res.code === 200 && res.data) {
  188. const book = res.data;
  189. this.bookInfo.title = book.title || this.bookInfo.title;
  190. this.bookInfo.author = book.author || this.bookInfo.author;
  191. this.bookInfo.image = book.image || book.cover || this.bookInfo.image;
  192. }
  193. } catch (e) {
  194. common_vendor.index.__f__("error", "at pages/reader/reader.vue:410", "加载书籍信息失败:", e);
  195. }
  196. },
  197. async loadChapterList() {
  198. if (!this.bookInfo.id)
  199. return;
  200. try {
  201. this.isLoading = true;
  202. const res = await utils_api.getBookChapters(this.bookInfo.id);
  203. if (res && res.code === 200 && res.data) {
  204. this.chapterList = res.data.map((ch) => ({
  205. id: ch.id,
  206. title: ch.title,
  207. chapterNumber: ch.chapterNumber
  208. }));
  209. if (this.chapterList.length > 0 && !this.currentChapter) {
  210. this.currentChapter = this.chapterList[0];
  211. this.loadChapterContent(this.currentChapter.id);
  212. }
  213. }
  214. } catch (e) {
  215. common_vendor.index.__f__("error", "at pages/reader/reader.vue:433", "加载章节列表失败:", e);
  216. common_vendor.index.showToast({
  217. title: "加载章节列表失败",
  218. icon: "none"
  219. });
  220. } finally {
  221. this.isLoading = false;
  222. }
  223. },
  224. async loadChapterContent(chapterId) {
  225. if (!chapterId)
  226. return;
  227. try {
  228. this.isLoading = true;
  229. const res = await utils_api.getBookChapterDetail(chapterId);
  230. if (res && res.code === 200 && res.data) {
  231. const chapter = res.data;
  232. this.currentChapter = {
  233. id: chapter.id,
  234. title: chapter.title,
  235. chapterNumber: chapter.chapterNumber
  236. };
  237. if (chapter.content && chapter.content.trim()) {
  238. const lines = chapter.content.split(/\r?\n/);
  239. this.contentLines = lines && lines.length > 0 ? lines : [chapter.content];
  240. } else {
  241. const fallback = (this.bookInfo.introduction || this.bookInfo.desc || this.bookInfo.brief || "").trim();
  242. this.contentLines = fallback ? fallback.split(/\r?\n/) : ["暂无内容"];
  243. }
  244. this.saveReadingProgress();
  245. } else {
  246. common_vendor.index.showToast({
  247. title: res && res.message ? res.message : "加载章节内容失败",
  248. icon: "none"
  249. });
  250. }
  251. } catch (e) {
  252. common_vendor.index.__f__("error", "at pages/reader/reader.vue:476", "加载章节内容失败:", e);
  253. common_vendor.index.showToast({
  254. title: "加载章节内容失败,请重试",
  255. icon: "none"
  256. });
  257. } finally {
  258. this.isLoading = false;
  259. }
  260. },
  261. // 笔记功能
  262. showNotes() {
  263. this.showNotesModal = true;
  264. },
  265. closeNotesModal() {
  266. this.showNotesModal = false;
  267. this.newNote = "";
  268. },
  269. addNote() {
  270. if (!this.newNote.trim()) {
  271. common_vendor.index.showToast({
  272. title: "请输入笔记内容",
  273. icon: "none"
  274. });
  275. return;
  276. }
  277. const note = {
  278. content: this.newNote,
  279. time: this.getCurrentTime(),
  280. chapter: this.currentChapter.title
  281. };
  282. this.notes.unshift(note);
  283. this.newNote = "";
  284. this.saveNotes();
  285. common_vendor.index.showToast({
  286. title: "笔记已添加",
  287. icon: "success"
  288. });
  289. },
  290. deleteNote(index) {
  291. common_vendor.index.showModal({
  292. title: "提示",
  293. content: "确定要删除这条笔记吗?",
  294. success: (res) => {
  295. if (res.confirm) {
  296. this.notes.splice(index, 1);
  297. this.saveNotes();
  298. common_vendor.index.showToast({
  299. title: "已删除",
  300. icon: "success"
  301. });
  302. }
  303. }
  304. });
  305. },
  306. getCurrentTime() {
  307. const now = /* @__PURE__ */ new Date();
  308. const month = now.getMonth() + 1;
  309. const day = now.getDate();
  310. const hour = now.getHours();
  311. const minute = now.getMinutes();
  312. return `${month}-${day} ${hour}:${minute < 10 ? "0" + minute : minute}`;
  313. },
  314. // 夜间模式
  315. toggleTheme() {
  316. this.isDarkMode = !this.isDarkMode;
  317. this.saveSettings();
  318. common_vendor.index.showToast({
  319. title: this.isDarkMode ? "已切换夜间模式" : "已切换日间模式",
  320. icon: "none",
  321. duration: 1500
  322. });
  323. },
  324. // 字号功能
  325. showFontSize() {
  326. this.showFontSizeModal = true;
  327. },
  328. closeFontSizeModal() {
  329. this.showFontSizeModal = false;
  330. },
  331. selectFontSize(size) {
  332. this.fontSize = size;
  333. this.saveSettings();
  334. this.closeFontSizeModal();
  335. common_vendor.index.showToast({
  336. title: "字号已调整",
  337. icon: "success",
  338. duration: 1e3
  339. });
  340. },
  341. // 保存和加载设置
  342. loadSettings() {
  343. try {
  344. const settings = common_vendor.index.getStorageSync(`reading_settings_${this.bookInfo.id}`);
  345. if (settings) {
  346. if (settings.fontSize)
  347. this.fontSize = settings.fontSize;
  348. if (settings.isDarkMode !== void 0)
  349. this.isDarkMode = settings.isDarkMode;
  350. }
  351. } catch (e) {
  352. common_vendor.index.__f__("error", "at pages/reader/reader.vue:574", "加载设置失败", e);
  353. }
  354. },
  355. saveSettings() {
  356. try {
  357. common_vendor.index.setStorageSync(`reading_settings_${this.bookInfo.id}`, {
  358. fontSize: this.fontSize,
  359. isDarkMode: this.isDarkMode
  360. });
  361. } catch (e) {
  362. common_vendor.index.__f__("error", "at pages/reader/reader.vue:584", "保存设置失败", e);
  363. }
  364. },
  365. loadNotes() {
  366. try {
  367. const notes = common_vendor.index.getStorageSync(`reading_notes_${this.bookInfo.id}`);
  368. if (notes && Array.isArray(notes)) {
  369. this.notes = notes;
  370. }
  371. } catch (e) {
  372. common_vendor.index.__f__("error", "at pages/reader/reader.vue:594", "加载笔记失败", e);
  373. }
  374. },
  375. saveNotes() {
  376. try {
  377. common_vendor.index.setStorageSync(`reading_notes_${this.bookInfo.id}`, this.notes);
  378. } catch (e) {
  379. common_vendor.index.__f__("error", "at pages/reader/reader.vue:601", "保存笔记失败", e);
  380. }
  381. },
  382. loadReadingProgress() {
  383. try {
  384. const progress = common_vendor.index.getStorageSync(`reading_progress_${this.bookInfo.id}`);
  385. if (progress) {
  386. if (progress.chapter)
  387. this.currentChapter = progress.chapter;
  388. }
  389. } catch (e) {
  390. common_vendor.index.__f__("error", "at pages/reader/reader.vue:612", "加载阅读进度失败", e);
  391. }
  392. },
  393. saveReadingProgress() {
  394. try {
  395. common_vendor.index.setStorageSync(`reading_progress_${this.bookInfo.id}`, {
  396. chapter: this.currentChapter,
  397. bookId: this.bookInfo.id
  398. });
  399. } catch (e) {
  400. common_vendor.index.__f__("error", "at pages/reader/reader.vue:623", "保存阅读进度失败", e);
  401. }
  402. }
  403. },
  404. onUnload() {
  405. this.saveReadingProgress();
  406. }
  407. };
  408. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  409. return common_vendor.e({
  410. a: $data.showToolbar
  411. }, $data.showToolbar ? {
  412. b: common_vendor.o((...args) => $options.goBack && $options.goBack(...args)),
  413. c: common_vendor.t($data.bookInfo.title),
  414. d: common_vendor.o((...args) => $options.handleShare && $options.handleShare(...args))
  415. } : {}, {
  416. e: $data.isLoading && $data.contentLines.length === 0
  417. }, $data.isLoading && $data.contentLines.length === 0 ? {} : {}, {
  418. f: $data.currentChapter && !$data.isLoading
  419. }, $data.currentChapter && !$data.isLoading ? {
  420. g: common_vendor.t($data.currentChapter.title)
  421. } : {}, {
  422. h: !$data.isLoading || $data.contentLines.length > 0
  423. }, !$data.isLoading || $data.contentLines.length > 0 ? {
  424. i: common_vendor.f($data.contentLines, (line, index, i0) => {
  425. return {
  426. a: common_vendor.t(line),
  427. b: index,
  428. c: common_vendor.o(($event) => $options.handleLongPress($event, line, index), index),
  429. d: common_vendor.o((...args) => $options.handleTouchStart && $options.handleTouchStart(...args), index),
  430. e: common_vendor.o((...args) => $options.handleTouchEnd && $options.handleTouchEnd(...args), index)
  431. };
  432. }),
  433. j: $data.fontSize + "rpx"
  434. } : {}, {
  435. k: !$data.isLoading && $data.contentLines.length === 0 && $data.chapterList.length === 0
  436. }, !$data.isLoading && $data.contentLines.length === 0 && $data.chapterList.length === 0 ? {} : {}, {
  437. l: common_vendor.o((...args) => $options.handleScroll && $options.handleScroll(...args)),
  438. m: common_vendor.o((...args) => $options.handleContentTap && $options.handleContentTap(...args)),
  439. n: $data.showSelectionMenu
  440. }, $data.showSelectionMenu ? {
  441. o: common_vendor.o((...args) => $options.copySelectedText && $options.copySelectedText(...args)),
  442. p: common_vendor.o((...args) => $options.writeNoteFromSelection && $options.writeNoteFromSelection(...args)),
  443. q: $data.selectionMenuTop + "px",
  444. r: $data.selectionMenuLeft + "px"
  445. } : {}, {
  446. s: $data.showSelectionMenu
  447. }, $data.showSelectionMenu ? {
  448. t: common_vendor.o((...args) => $options.hideSelectionMenu && $options.hideSelectionMenu(...args))
  449. } : {}, {
  450. v: $data.showToolbar
  451. }, $data.showToolbar ? {
  452. w: common_vendor.o((...args) => $options.showChapterList && $options.showChapterList(...args)),
  453. x: common_vendor.o((...args) => $options.showNotes && $options.showNotes(...args)),
  454. y: common_vendor.o((...args) => $options.toggleTheme && $options.toggleTheme(...args)),
  455. z: common_vendor.o((...args) => $options.showFontSize && $options.showFontSize(...args)),
  456. A: $data.isDarkMode ? 1 : ""
  457. } : {}, {
  458. B: $data.showChapterModal
  459. }, $data.showChapterModal ? {
  460. C: common_vendor.o((...args) => $options.closeChapterModal && $options.closeChapterModal(...args)),
  461. D: common_vendor.f($data.chapterList, (chapter, index, i0) => {
  462. return {
  463. a: common_vendor.t(chapter.title),
  464. b: index,
  465. c: $data.currentChapter.id === chapter.id ? 1 : "",
  466. d: common_vendor.o(($event) => $options.selectChapter(chapter), index)
  467. };
  468. }),
  469. E: $data.isDarkMode ? 1 : "",
  470. F: common_vendor.o(() => {
  471. }),
  472. G: common_vendor.o((...args) => $options.closeChapterModal && $options.closeChapterModal(...args))
  473. } : {}, {
  474. H: $data.showNotesModal
  475. }, $data.showNotesModal ? common_vendor.e({
  476. I: common_vendor.o((...args) => $options.closeNotesModal && $options.closeNotesModal(...args)),
  477. J: $data.newNote,
  478. K: common_vendor.o(($event) => $data.newNote = $event.detail.value),
  479. L: common_vendor.o((...args) => $options.addNote && $options.addNote(...args)),
  480. M: common_vendor.f($data.notes, (note, index, i0) => {
  481. return {
  482. a: common_vendor.t(note.time),
  483. b: common_vendor.o(($event) => $options.deleteNote(index), index),
  484. c: common_vendor.t(note.content),
  485. d: index
  486. };
  487. }),
  488. N: $data.notes.length === 0
  489. }, $data.notes.length === 0 ? {} : {}, {
  490. O: $data.isDarkMode ? 1 : "",
  491. P: common_vendor.o(() => {
  492. }),
  493. Q: common_vendor.o((...args) => $options.closeNotesModal && $options.closeNotesModal(...args))
  494. }) : {}, {
  495. R: $data.showFontSizeModal
  496. }, $data.showFontSizeModal ? {
  497. S: common_vendor.f($data.fontSizeOptions, (size, index, i0) => {
  498. return {
  499. a: common_vendor.t(size.label),
  500. b: index,
  501. c: $data.fontSize === size.value ? 1 : "",
  502. d: common_vendor.o(($event) => $options.selectFontSize(size.value), index)
  503. };
  504. }),
  505. T: $data.isDarkMode ? 1 : "",
  506. U: common_vendor.o(() => {
  507. }),
  508. V: common_vendor.o((...args) => $options.closeFontSizeModal && $options.closeFontSizeModal(...args))
  509. } : {}, {
  510. W: $data.isDarkMode ? 1 : ""
  511. });
  512. }
  513. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-7912e6ab"]]);
  514. wx.createPage(MiniProgramPage);
  515. //# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/reader/reader.js.map