|
|
@@ -0,0 +1,403 @@
|
|
|
+layui.config({
|
|
|
+ base: '/assets/lib/jplus/treeTable/'
|
|
|
+}).use(['form', 'layer', 'table', 'laytpl', 'treeTable'], function () {
|
|
|
+ var form = layui.form,
|
|
|
+ layer = parent.layer === undefined ? layui.layer : top.layer,
|
|
|
+ $ = layui.jquery,
|
|
|
+ laytpl = layui.laytpl,
|
|
|
+ table = layui.table,
|
|
|
+ treeTable = layui.treeTable;
|
|
|
+
|
|
|
+ // 状态管理
|
|
|
+ const state = {
|
|
|
+ currentChapterIndex: 0,
|
|
|
+ fontSize: 'medium',
|
|
|
+ theme: 'light',
|
|
|
+ lineHeight: 'normal',
|
|
|
+ fontFamily: 'system',
|
|
|
+ chapters: [],
|
|
|
+ isChaptersLoaded: false, // 新增:标记章节是否已加载
|
|
|
+ isRendering: false // 新增:防止重复渲染
|
|
|
+ };
|
|
|
+
|
|
|
+ // 配置常量
|
|
|
+ const CONFIG = {
|
|
|
+ table: {
|
|
|
+ elem: '#list',
|
|
|
+ url: '/silos/cms/book/contents/queryPage',
|
|
|
+ method: 'POST',
|
|
|
+ contentType: 'application/json;charset=utf-8',
|
|
|
+ page: true,
|
|
|
+ limits: [10, 15, 20, 25],
|
|
|
+ limit: 15,
|
|
|
+ id: "listTable",
|
|
|
+ request: {
|
|
|
+ pageName: "pageNo",
|
|
|
+ limitName: "pageRows"
|
|
|
+ },
|
|
|
+ response: {
|
|
|
+ statusName: 'code',
|
|
|
+ statusCode: 200,
|
|
|
+ msgName: 'msg',
|
|
|
+ countName: 'totalRows',
|
|
|
+ dataName: 'data'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ pageNo: 1,
|
|
|
+ pageRows: 100
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // DOM 元素缓存
|
|
|
+ const DOM = {
|
|
|
+ chaptersList: document.getElementById('chaptersList'),
|
|
|
+ chapterNumberDisplay: document.getElementById('chapterNumberDisplay'),
|
|
|
+ chapterTitleDisplay: document.getElementById('chapterTitleDisplay'),
|
|
|
+ chapterContent: document.getElementById('chapterContent'),
|
|
|
+ readingArea: document.getElementById('readingArea'),
|
|
|
+ toggleSidebar: document.getElementById('toggleSidebar'),
|
|
|
+ prevChapter: document.getElementById('prevChapter'),
|
|
|
+ nextChapter: document.getElementById('nextChapter'),
|
|
|
+ prevNavBtn: document.getElementById('prevNavBtn'),
|
|
|
+ nextNavBtn: document.getElementById('nextNavBtn'),
|
|
|
+ settingsBtn: document.getElementById('settingsBtn'),
|
|
|
+ closeSettings: document.getElementById('closeSettings'),
|
|
|
+ fontSelect: document.getElementById('fontSelect'),
|
|
|
+ bookmarkBtn: document.getElementById('bookmarkBtn'),
|
|
|
+ searchBtn: document.getElementById('searchBtn'),
|
|
|
+ progressPercent: document.getElementById('progressPercent'),
|
|
|
+ sidebar: document.getElementById('sidebar'),
|
|
|
+ mainContent: document.getElementById('mainContent'),
|
|
|
+ settingsPanel: document.getElementById('settingsPanel')
|
|
|
+ };
|
|
|
+
|
|
|
+ // 工具函数
|
|
|
+ const utils = {
|
|
|
+ showError: function(title, msg) {
|
|
|
+ console.error(title, msg);
|
|
|
+ // 这里可以添加更友好的错误提示,如使用layer
|
|
|
+ },
|
|
|
+
|
|
|
+ getFontFamily: function(font) {
|
|
|
+ const fontMap = {
|
|
|
+ 'serif': "'Noto Serif SC', 'Times New Roman', serif",
|
|
|
+ 'sans-serif': "'Segoe UI', 'Microsoft YaHei', sans-serif",
|
|
|
+ 'monospace': "'Courier New', monospace",
|
|
|
+ 'system': "system-ui, -apple-system, sans-serif"
|
|
|
+ };
|
|
|
+ return fontMap[font] || fontMap.system;
|
|
|
+ },
|
|
|
+
|
|
|
+ debounce: function(func, wait) {
|
|
|
+ let timeout;
|
|
|
+ return function executedFunction(...args) {
|
|
|
+ const later = () => {
|
|
|
+ clearTimeout(timeout);
|
|
|
+ func(...args);
|
|
|
+ };
|
|
|
+ clearTimeout(timeout);
|
|
|
+ timeout = setTimeout(later, wait);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 章节管理
|
|
|
+ const chapterManager = {
|
|
|
+ // 加载章节数据
|
|
|
+ loadChapters: function() {
|
|
|
+ if (state.isChaptersLoaded) {
|
|
|
+ return Promise.resolve(state.chapters);
|
|
|
+ }
|
|
|
+
|
|
|
+ const requestData = $.extend({}, CONFIG.grid, {
|
|
|
+ bookInfoId: $('#bookId').val()
|
|
|
+ });
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ $.ajax({
|
|
|
+ url: "/silos/cms/book/contents/queryPage",
|
|
|
+ type: "POST",
|
|
|
+ dataType: "json",
|
|
|
+ contentType: 'application/json;charset=UTF-8',
|
|
|
+ data: JSON.stringify(requestData),
|
|
|
+ success: function (res) {
|
|
|
+ if (res.code === 200) {
|
|
|
+ state.chapters = res.data.dataList.map(chapter => ({
|
|
|
+ id: chapter.id,
|
|
|
+ number: chapter.chapterNumber,
|
|
|
+ title: chapter.chapterTitle,
|
|
|
+ content: chapter.contentHtml
|
|
|
+ }));
|
|
|
+ state.isChaptersLoaded = true;
|
|
|
+ resolve(state.chapters);
|
|
|
+ } else {
|
|
|
+ utils.showError("加载失败", res.msg);
|
|
|
+ reject(res.msg);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: function (xhr, status, error) {
|
|
|
+ console.error("请求失败:", error);
|
|
|
+ utils.showError("请求失败", "网络错误或服务器异常");
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 渲染章节列表(只渲染一次,后续只更新激活状态)
|
|
|
+ renderChaptersList: function() {
|
|
|
+ if (state.isRendering || !state.isChaptersLoaded) return;
|
|
|
+
|
|
|
+ state.isRendering = true;
|
|
|
+
|
|
|
+ DOM.chaptersList.innerHTML = state.chapters.map((chapter, index) => `
|
|
|
+ <div class="chapter-item ${index === state.currentChapterIndex ? 'active' : ''}"
|
|
|
+ data-index="${index}">
|
|
|
+ <div class="chapter-number">${chapter.number}</div>
|
|
|
+ <div class="chapter-title">${chapter.title}</div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+
|
|
|
+ state.isRendering = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新章节激活状态(避免重新渲染整个列表)
|
|
|
+ updateActiveChapter: function() {
|
|
|
+ if (!DOM.chaptersList) return;
|
|
|
+
|
|
|
+ // 移除所有激活状态
|
|
|
+ const items = DOM.chaptersList.querySelectorAll('.chapter-item');
|
|
|
+ items.forEach(item => item.classList.remove('active'));
|
|
|
+
|
|
|
+ // 添加当前激活状态
|
|
|
+ const currentItem = DOM.chaptersList.querySelector(`.chapter-item[data-index="${state.currentChapterIndex}"]`);
|
|
|
+ if (currentItem) {
|
|
|
+ currentItem.classList.add('active');
|
|
|
+
|
|
|
+ // 确保激活的章节在可视区域内
|
|
|
+ currentItem.scrollIntoView({
|
|
|
+ behavior: 'smooth',
|
|
|
+ block: 'nearest'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示指定章节
|
|
|
+ showChapter: function(index) {
|
|
|
+ if (index < 0 || index >= state.chapters.length) return;
|
|
|
+
|
|
|
+ state.currentChapterIndex = index;
|
|
|
+ const chapter = state.chapters[index];
|
|
|
+
|
|
|
+ // 更新显示内容
|
|
|
+ DOM.chapterNumberDisplay.textContent = chapter.number;
|
|
|
+ DOM.chapterTitleDisplay.textContent = chapter.title;
|
|
|
+ DOM.chapterContent.innerHTML = chapter.content;
|
|
|
+
|
|
|
+ // 更新导航按钮状态
|
|
|
+ DOM.prevNavBtn.disabled = index === 0;
|
|
|
+ DOM.nextNavBtn.disabled = index === state.chapters.length - 1;
|
|
|
+
|
|
|
+ // 更新章节列表激活状态(不重新渲染整个列表)
|
|
|
+ chapterManager.updateActiveChapter();
|
|
|
+
|
|
|
+ // 更新阅读进度
|
|
|
+ chapterManager.updateProgress();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新阅读进度
|
|
|
+ updateProgress: function() {
|
|
|
+ const progress = ((state.currentChapterIndex + 1) / state.chapters.length * 100).toFixed(0);
|
|
|
+ DOM.progressPercent.textContent = `${progress}%`;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 阅读设置管理
|
|
|
+ const settingsManager = {
|
|
|
+ // 应用阅读设置
|
|
|
+ applyReadingSettings: function() {
|
|
|
+ // 字体大小
|
|
|
+ DOM.readingArea.className = DOM.readingArea.className.replace(/\bfont-\w+\b/g, '');
|
|
|
+ DOM.readingArea.classList.add(`font-${state.fontSize}`);
|
|
|
+
|
|
|
+ // 主题
|
|
|
+ document.body.className = document.body.className.replace(/\btheme-\w+\b/g, '');
|
|
|
+ document.body.classList.add(`theme-${state.theme}`);
|
|
|
+ if (state.theme === 'dark') {
|
|
|
+ document.body.classList.add('dark-mode');
|
|
|
+ } else {
|
|
|
+ document.body.classList.remove('dark-mode');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 行高
|
|
|
+ DOM.readingArea.className = DOM.readingArea.className.replace(/\bline-\w+\b/g, '');
|
|
|
+ DOM.readingArea.classList.add(`line-${state.lineHeight}`);
|
|
|
+
|
|
|
+ // 字体
|
|
|
+ DOM.chapterContent.style.fontFamily = utils.getFontFamily(state.fontFamily);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化设置面板
|
|
|
+ initSettingsPanel: function() {
|
|
|
+ // 字体大小设置
|
|
|
+ document.querySelectorAll('.font-size-btn').forEach(btn => {
|
|
|
+ btn.addEventListener('click', function() {
|
|
|
+ document.querySelectorAll('.font-size-btn').forEach(b => b.classList.remove('active'));
|
|
|
+ this.classList.add('active');
|
|
|
+ state.fontSize = this.getAttribute('data-size');
|
|
|
+ settingsManager.applyReadingSettings();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 主题设置
|
|
|
+ document.querySelectorAll('.theme-btn').forEach(btn => {
|
|
|
+ btn.addEventListener('click', function() {
|
|
|
+ document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active'));
|
|
|
+ this.classList.add('active');
|
|
|
+ state.theme = this.getAttribute('data-theme');
|
|
|
+ settingsManager.applyReadingSettings();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 行高设置
|
|
|
+ document.querySelectorAll('[data-lineheight]').forEach(btn => {
|
|
|
+ btn.addEventListener('click', function() {
|
|
|
+ document.querySelectorAll('[data-lineheight]').forEach(b => b.classList.remove('active'));
|
|
|
+ this.classList.add('active');
|
|
|
+ state.lineHeight = this.getAttribute('data-lineheight');
|
|
|
+ settingsManager.applyReadingSettings();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 字体设置
|
|
|
+ DOM.fontSelect.addEventListener('change', function() {
|
|
|
+ state.fontFamily = this.value;
|
|
|
+ settingsManager.applyReadingSettings();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 事件处理
|
|
|
+ const eventHandler = {
|
|
|
+ initEvents: function() {
|
|
|
+ // 切换侧边栏
|
|
|
+ DOM.toggleSidebar.addEventListener('click', function() {
|
|
|
+ DOM.sidebar.classList.toggle('collapsed');
|
|
|
+ DOM.mainContent.classList.toggle('expanded');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 章节导航
|
|
|
+ DOM.prevChapter.addEventListener('click', () => {
|
|
|
+ if (state.currentChapterIndex > 0) {
|
|
|
+ chapterManager.showChapter(state.currentChapterIndex - 1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ DOM.nextChapter.addEventListener('click', () => {
|
|
|
+ if (state.currentChapterIndex < state.chapters.length - 1) {
|
|
|
+ chapterManager.showChapter(state.currentChapterIndex + 1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ DOM.prevNavBtn.addEventListener('click', () => {
|
|
|
+ if (state.currentChapterIndex > 0) {
|
|
|
+ chapterManager.showChapter(state.currentChapterIndex - 1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ DOM.nextNavBtn.addEventListener('click', () => {
|
|
|
+ if (state.currentChapterIndex < state.chapters.length - 1) {
|
|
|
+ chapterManager.showChapter(state.currentChapterIndex + 1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 章节列表点击事件(使用事件委托)
|
|
|
+ DOM.chaptersList.addEventListener('click', utils.debounce(function(e) {
|
|
|
+ const chapterItem = e.target.closest('.chapter-item');
|
|
|
+ if (chapterItem) {
|
|
|
+ const index = parseInt(chapterItem.getAttribute('data-index'));
|
|
|
+ chapterManager.showChapter(index);
|
|
|
+
|
|
|
+ // 在移动端点击章节后自动关闭侧边栏
|
|
|
+ if (window.innerWidth <= 768) {
|
|
|
+ DOM.sidebar.classList.remove('open');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 100));
|
|
|
+
|
|
|
+ // 设置面板
|
|
|
+ DOM.settingsBtn.addEventListener('click', function() {
|
|
|
+ DOM.settingsPanel.classList.add('open');
|
|
|
+ });
|
|
|
+
|
|
|
+ DOM.closeSettings.addEventListener('click', function() {
|
|
|
+ DOM.settingsPanel.classList.remove('open');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 书签功能
|
|
|
+ DOM.bookmarkBtn.addEventListener('click', function() {
|
|
|
+ const chapter = state.chapters[state.currentChapterIndex];
|
|
|
+ layer.msg(`已将 "${chapter.number} ${chapter.title}" 添加到书签`);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 搜索功能
|
|
|
+ DOM.searchBtn.addEventListener('click', function() {
|
|
|
+ layer.msg('搜索功能即将推出');
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加CSS类来处理字体大小和行高
|
|
|
+ const addStyle = function() {
|
|
|
+ const style = document.createElement('style');
|
|
|
+ style.textContent = `
|
|
|
+ .font-small { font-size: 0.9rem; }
|
|
|
+ .font-medium { font-size: 1.1rem; }
|
|
|
+ .font-large { font-size: 1.3rem; }
|
|
|
+ .font-xlarge { font-size: 1.5rem; }
|
|
|
+
|
|
|
+ .line-tight { line-height: 1.5; }
|
|
|
+ .line-normal { line-height: 1.8; }
|
|
|
+ .line-loose { line-height: 2.2; }
|
|
|
+
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ .font-small { font-size: 0.85rem; }
|
|
|
+ .font-medium { font-size: 1rem; }
|
|
|
+ .font-large { font-size: 1.15rem; }
|
|
|
+ .font-xlarge { font-size: 1.3rem; }
|
|
|
+ }
|
|
|
+ `;
|
|
|
+ document.head.appendChild(style);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化
|
|
|
+ const init = function() {
|
|
|
+ addStyle();
|
|
|
+
|
|
|
+ // 加载章节数据
|
|
|
+ chapterManager.loadChapters()
|
|
|
+ .then(() => {
|
|
|
+ // 渲染章节列表(只执行一次)
|
|
|
+ chapterManager.renderChaptersList();
|
|
|
+
|
|
|
+ // 显示第一个章节
|
|
|
+ if (state.chapters.length > 0) {
|
|
|
+ chapterManager.showChapter(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 应用初始设置
|
|
|
+ settingsManager.applyReadingSettings();
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('初始化失败:', error);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化事件
|
|
|
+ eventHandler.initEvents();
|
|
|
+ settingsManager.initSettingsPanel();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 启动初始化
|
|
|
+ init();
|
|
|
+});
|