book-min/miniprogram/components/reader/reader.ts

886 lines
24 KiB
TypeScript

import request from '../../utils/request';
// 定义组件方法接口
interface IComponentMethods {
loadChapter: (chapter: number) => Promise<void>;
splitPages: () => Promise<void>;
prevPage: () => void;
nextPage: () => void;
touchStart: (e: WechatMiniprogram.TouchEvent) => void;
touchEnd: (e: WechatMiniprogram.TouchEvent) => void;
toggleToolbar: () => void;
toggleMenu: () => void;
prevChapter: () => void;
nextChapter: () => void;
jumpToChapter: (e: WechatMiniprogram.TouchEvent) => void;
goBack: () => void;
onScroll: (e: WechatMiniprogram.TouchEvent) => void;
toggleSettings: () => void;
decreaseFontSize: () => void;
increaseFontSize: () => void;
startTimeUpdate: () => void;
calculateProgress: () => void;
closeAllPanels: () => void;
switchTheme: (e: WechatMiniprogram.TouchEvent) => void;
adjustBrightness: (e: WechatMiniprogram.SliderChange) => void;
toggleAutoReading: (e: WechatMiniprogram.SwitchChange) => void;
setAutoReadingInterval: (e: WechatMiniprogram.PickerChange) => void;
startAutoReading: () => void;
stopAutoReading: () => void;
toggleBookmarks: () => void;
addBookmark: () => void;
loadBookmarks: () => void;
jumpToBookmark: (e: WechatMiniprogram.TouchEvent) => void;
toggleNotes: () => void;
showTextMenu: (e: WechatMiniprogram.TouchEvent) => void;
addNote: () => void;
loadNotes: () => void;
copyText: () => void;
startReadingTimer: () => void;
updateReadingTime: () => void;
loadTodayReadingTime: () => void;
saveReadingProgress: () => void;
restoreSettings: () => void;
toggleMore: () => void;
onSelectText: (e: any) => void;
addBookmarkWithSelection: () => void;
addNoteWithSelection: () => void;
copySelectedText: () => void;
hideTextMenu: () => void;
startReadingTimer: () => void;
stopReadingTimer: () => void;
saveReadingRecord: (duration: number) => void;
}
// 定义组件数据接口
interface IComponentData {
bookTitle: string;
content: string;
currentChapter: number;
totalChapters: number;
showToolbar: boolean;
showMenu: boolean;
scrollTop: number;
chapters: number[];
lastTapTime: number;
pages: string[];
currentPage: number;
pageHeight: number;
lineHeight: number;
fontSize: number;
showSettings: boolean;
minFontSize: number;
maxFontSize: number;
currentTime: string;
readingProgress: number;
themes: Array<{id: string; name: string; bg: string; color: string}>;
currentTheme: string;
brightness: number;
autoReading: boolean;
autoReadingInterval: number;
showBookmarks: boolean;
showNotes: boolean;
showTextMenu: boolean;
textMenuTop: number;
textMenuLeft: number;
selectedText: string;
bookmarks: any[];
notes: any[];
readingStartTime: number;
todayReadingTime: number;
showMore: boolean;
selectionStart: number;
selectionEnd: number;
readingDuration: number;
isReading: boolean;
readingTimer: any;
}
// 定义组件属性接口
interface IComponentProperties {
bookId: string;
bookUrl: string;
title: string;
}
Component<IComponentData, IComponentProperties, IComponentMethods>({
data: {
bookTitle: '',
content: '',
currentChapter: 1,
totalChapters: 2541,
showToolbar: true,
showMenu: false,
scrollTop: 0,
chapters: [] as number[],
lastTapTime: 0,
pages: [] as string[],
currentPage: 0,
pageHeight: 0,
lineHeight: 36,
fontSize: 32,
showSettings: false,
minFontSize: 24,
maxFontSize: 48,
currentTime: '',
readingProgress: 0,
themes: [
{ id: 'default', name: '默认', bg: '#f4ecd8', color: '#333' },
{ id: 'night', name: '夜间', bg: '#222', color: '#999' },
{ id: 'green', name: '护眼', bg: '#cce8cf', color: '#333' },
{ id: 'paper', name: '纸张', bg: '#e8e2d3', color: '#333' }
],
currentTheme: 'default',
brightness: 100,
autoReading: false,
autoReadingInterval: 5000,
showBookmarks: false,
showNotes: false,
showTextMenu: false,
textMenuTop: 0,
textMenuLeft: 0,
selectedText: '',
bookmarks: [] as any[],
notes: [] as any[],
readingStartTime: 0,
todayReadingTime: 0,
showMore: false,
selectionStart: -1,
selectionEnd: -1,
readingDuration: 0,
isReading: false,
readingTimer: null as any,
},
properties: {
bookId: String,
bookUrl: String,
title: String
},
lifetimes: {
attached() {
this.setData({
bookTitle: this.properties.title || '大爱仙尊',
chapters: Array.from({length: 2541}, (_, i) => i + 1)
});
this.loadChapter(1).then(() => {
this.calculateProgress();
});
const savedFontSize = wx.getStorageSync('reader_font_size');
if (savedFontSize) {
this.setData({
fontSize: savedFontSize
});
}
this.startTimeUpdate();
this.startReadingTimer();
this.loadBookmarks();
this.loadNotes();
this.restoreSettings();
// 获取当前屏幕亮度
wx.getScreenBrightness({
success: (res) => {
this.setData({
brightness: Math.round(res.value * 100)
});
}
});
},
detached() {
if (this.data.readingTimer) {
clearInterval(this.data.readingTimer);
}
this.updateReadingTime();
this.saveReadingProgress();
if (this.autoReadingTimer) {
clearInterval(this.autoReadingTimer);
}
this.stopReadingTimer();
}
},
pageLifetimes: {
hide() {
this.stopReadingTimer();
},
show() {
if (!this.data.readingDuration) {
this.startReadingTimer();
}
}
},
methods: {
// 加载章节内容
async loadChapter(chapter: number) {
try {
wx.showLoading({ title: '加载中...' });
const res = await request.get('/common/getBookContent', {
bookName: this.data.bookTitle,
id: chapter
});
if (res.code === 200) {
// 处理换行符
const content = res.data.replace(/\\r\\n/g, '\n');
this.setData({
content,
currentChapter: chapter // 更新当前章节号
});
// 分页处理
await this.splitPages();
this.calculateProgress();
}
} catch (error) {
console.error('加载章节失败:', error);
wx.showToast({
title: '加载失败',
icon: 'none'
});
} finally {
wx.hideLoading();
}
},
// 分页处理
async splitPages() {
// 获取容器尺寸
const query = this.createSelectorQuery();
query.select('.content').boundingClientRect();
const rect = await new Promise(resolve => query.exec(resolve));
const containerHeight = rect[0].height;
// 计算每页能显示的行数
const lineHeight = this.data.lineHeight;
const linesPerPage = Math.floor((containerHeight - 160) / (lineHeight * 2 / 750 * wx.getSystemInfoSync().windowWidth));
// 按自然段落分割内容
const paragraphs = this.data.content.split('\n').filter(p => p.trim());
// 分页
const pages = [];
let currentPage = [];
let currentLines = 0;
for (const paragraph of paragraphs) {
// 计算段落需要的行数
const paragraphLines = Math.ceil(paragraph.length * (this.data.fontSize / 32) / 20);
if (currentLines + paragraphLines > linesPerPage) {
// 当前页放不下这段,新建一页
if (currentPage.length > 0) {
pages.push(currentPage.join('\n'));
currentPage = [];
currentLines = 0;
}
// 如果单个段落超过一页
if (paragraphLines > linesPerPage) {
const chars = Math.floor(20 * linesPerPage);
let p = paragraph;
while (p.length > 0) {
const pageContent = p.slice(0, chars);
pages.push(pageContent);
p = p.slice(chars);
}
} else {
currentPage.push(paragraph);
currentLines = paragraphLines;
}
} else {
// 当前页能放下这段
currentPage.push(paragraph);
currentLines += paragraphLines;
}
}
// 保存最后一页
if (currentPage.length > 0) {
pages.push(currentPage.join('\n'));
}
this.setData({
pages,
currentPage: 0,
pageHeight: containerHeight
}, () => {
this.calculateProgress();
});
},
// 上一页
prevPage() {
if (this.data.currentPage > 0) {
this.setData({
currentPage: this.data.currentPage - 1
}, () => {
this.calculateProgress();
});
} else if (this.data.currentChapter > 1) {
// 上一章最后一页
const prevChapter = this.data.currentChapter - 1;
this.loadChapter(prevChapter).then(() => {
this.setData({
currentPage: this.data.pages.length - 1
}, () => {
this.calculateProgress();
});
});
}
},
// 下一页
nextPage() {
if (this.data.currentPage < this.data.pages.length - 1) {
this.setData({
currentPage: this.data.currentPage + 1
}, () => {
this.calculateProgress();
});
} else if (this.data.currentChapter < this.data.totalChapters) {
// 下一章第一页
const nextChapter = this.data.currentChapter + 1;
this.loadChapter(nextChapter).then(() => {
this.calculateProgress();
});
}
},
// 触摸开始
touchStart(e: any) {
this.touchStartX = e.touches[0].pageX;
},
// 触摸结束
touchEnd(e: any) {
const touchEndX = e.changedTouches[0].pageX;
const diff = touchEndX - this.touchStartX;
if (Math.abs(diff) > 50) { // 滑动距离大于50px才触发翻页
if (diff > 0) {
this.prevPage();
} else {
this.nextPage();
}
}
},
// 切换工具栏显示
toggleToolbar() {
const now = Date.now();
if (now - this.data.lastTapTime < 300) {
// 双击
this.setData({
showToolbar: !this.data.showToolbar
});
}
this.setData({ lastTapTime: now });
},
// 切换目录显示
toggleMenu() {
this.setData({
showMenu: !this.data.showMenu
});
},
// 上一章
prevChapter() {
if (this.data.currentChapter > 1) {
this.loadChapter(this.data.currentChapter - 1);
}
},
// 下一章
nextChapter() {
if (this.data.currentChapter < this.data.totalChapters) {
this.loadChapter(this.data.currentChapter + 1);
}
},
// 跳转到指定章节
jumpToChapter(e: any) {
const chapter = e.currentTarget.dataset.chapter;
this.loadChapter(Number(chapter)); // 确保 chapter 是数字
this.toggleMenu();
},
// 返回
goBack() {
this.stopReadingTimer(); // 停止计时
// 打印阅读时间
const minutes = Math.floor(this.data.readingDuration / 60);
const seconds = this.data.readingDuration % 60;
console.log(`本次阅读时长: ${minutes}${seconds}`);
console.log('详细信息:', {
开始时间: new Date(this.data.readingStartTime).toLocaleString(),
结束时间: new Date().toLocaleString(),
总秒数: this.data.readingDuration,
: `${minutes}${seconds}`
});
wx.navigateBack();
},
// 滚动处理
onScroll(e: any) {
// 可以在这里处理阅读进度保存等逻辑
},
// 切换设置面板
toggleSettings() {
console.log('切换设置面板', this.data.showSettings);
this.setData({
showSettings: !this.data.showSettings,
showMenu: false // 确保目录菜单关闭
});
},
// 减小字体
decreaseFontSize() {
if (this.data.fontSize > this.data.minFontSize) {
const newSize = this.data.fontSize - 2;
this.setData({
fontSize: newSize
}, () => {
// 重新计算分页
this.splitPages();
// 保存设置到本地
wx.setStorageSync('reader_font_size', newSize);
});
}
},
// 增大字体
increaseFontSize() {
if (this.data.fontSize < this.data.maxFontSize) {
const newSize = this.data.fontSize + 2;
this.setData({
fontSize: newSize
}, () => {
// 重新计算分页
this.splitPages();
// 保存设置到本地
wx.setStorageSync('reader_font_size', newSize);
});
}
},
// 更新时间
startTimeUpdate() {
const updateTime = () => {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
this.setData({
currentTime: `${hours}:${minutes}`
});
};
// 立即更新一次
updateTime();
// 每分钟更新一次
this.timeInterval = setInterval(updateTime, 60000);
},
// 计算阅读进度
calculateProgress() {
// 当前章节进度
const currentChapterProgress = this.data.pages.length > 0
? (this.data.currentPage + 1) / this.data.pages.length
: 0;
// 总进度计算:(当前章节 - 1 + 当前章节阅读进度) / 总章节数
const totalProgress = Math.floor(
((this.data.currentChapter - 1 + currentChapterProgress) / this.data.totalChapters) * 100
);
console.log('Progress calculation:', {
currentChapter: this.data.currentChapter,
totalChapters: this.data.totalChapters,
currentPage: this.data.currentPage,
totalPages: this.data.pages.length,
chapterProgress: currentChapterProgress,
totalProgress
});
this.setData({
readingProgress: totalProgress
});
},
// 关闭所有面板
closeAllPanels() {
this.setData({
showSettings: false,
showMenu: false
});
},
// 切换主题
switchTheme(e: any) {
const themeId = e.currentTarget.dataset.theme;
this.setData({
currentTheme: themeId,
showMore: false // 选择后关闭面板
});
wx.setStorageSync('reader_theme', themeId);
},
// 调整屏幕亮度
adjustBrightness(e: WechatMiniprogram.SliderChange) {
const value = e.detail.value;
console.log('调整亮度:', value); // 添加调试日志
wx.setScreenBrightness({
value: value / 100,
success: () => {
this.setData({ brightness: value });
wx.setStorageSync('reader_brightness', value);
console.log('亮度设置成功:', value);
},
fail: (error) => {
console.error('设置亮度失败:', error);
wx.showToast({
title: '设置亮度失败',
icon: 'none'
});
}
});
},
// 自动阅读相关
toggleAutoReading(e: any) {
const isChecked = e.detail.value;
this.setData({ autoReading: isChecked });
if (isChecked) {
this.startAutoReading();
} else {
this.stopAutoReading();
}
},
setAutoReadingInterval(e: any) {
const interval = [3000, 5000, 8000, 10000][e.detail.value];
this.setData({ autoReadingInterval: interval });
if (this.data.autoReading) {
this.stopAutoReading();
this.startAutoReading();
}
},
startAutoReading() {
this.autoReadingTimer = setInterval(() => {
this.nextPage();
}, this.data.autoReadingInterval);
},
stopAutoReading() {
if (this.autoReadingTimer) {
clearInterval(this.autoReadingTimer);
}
},
// 书签相关
toggleBookmarks() {
this.setData({
showBookmarks: !this.data.showBookmarks,
showSettings: false,
showNotes: false
});
},
addBookmark() {
const bookmark = {
chapter: this.data.currentChapter,
page: this.data.currentPage,
content: this.data.pages[this.data.currentPage].slice(0, 50),
time: new Date().toLocaleString()
};
const bookmarks = [...this.data.bookmarks, bookmark];
this.setData({ bookmarks });
wx.setStorageSync(`bookmarks_${this.data.bookTitle}`, bookmarks);
wx.showToast({ title: '添加书签成功', icon: 'success' });
},
loadBookmarks() {
const bookmarks = wx.getStorageSync(`bookmarks_${this.data.bookTitle}`) || [];
this.setData({ bookmarks });
},
jumpToBookmark(e: any) {
const bookmark = e.currentTarget.dataset.bookmark;
this.loadChapter(bookmark.chapter).then(() => {
this.setData({
currentPage: bookmark.page,
showBookmarks: false
});
});
},
// 笔记相关
toggleNotes() {
this.setData({
showNotes: !this.data.showNotes,
showSettings: false,
showBookmarks: false
});
},
showTextMenu(e: any) {
const text = this.data.pages[this.data.currentPage];
const start = 0;
const end = text.length;
this.setData({
selectionStart: start,
selectionEnd: end,
selectedText: text.substring(start, end),
showTextMenu: true,
textMenuTop: e.touches[0].clientY,
textMenuLeft: e.touches[0].clientX
});
},
addNote() {
wx.showModal({
title: '添加笔记',
editable: true,
placeholderText: '请输入笔记内容',
success: (res) => {
if (res.confirm && res.content) {
const note = {
chapter: this.data.currentChapter,
page: this.data.currentPage,
content: res.content,
selectedText: this.data.selectedText,
time: new Date().toLocaleString()
};
const notes = [...this.data.notes, note];
this.setData({
notes,
showTextMenu: false
});
wx.setStorageSync(`notes_${this.data.bookTitle}`, notes);
wx.showToast({ title: '添加笔记成功', icon: 'success' });
}
}
});
},
loadNotes() {
const notes = wx.getStorageSync(`notes_${this.data.bookTitle}`) || [];
this.setData({ notes });
},
copyText() {
wx.setClipboardData({
data: this.data.selectedText,
success: () => {
this.setData({ showTextMenu: false });
wx.showToast({ title: '复制成功', icon: 'success' });
}
});
},
// 阅读统计相关
startReadingTimer() {
this.setData({
readingStartTime: Date.now(),
isReading: true,
readingDuration: 0
});
// 每秒更新阅读时长
this.data.readingTimer = setInterval(() => {
const duration = Math.floor((Date.now() - this.data.readingStartTime) / 1000);
this.setData({ readingDuration: duration });
}, 1000);
},
updateReadingTime() {
const duration = Math.floor((Date.now() - this.data.readingStartTime) / 1000);
const today = new Date().toDateString();
const newTodayTime = this.data.todayReadingTime + duration;
this.setData({ todayReadingTime: newTodayTime });
let statistics = wx.getStorageSync('reading_statistics') || {};
statistics[today] = newTodayTime;
wx.setStorageSync('reading_statistics', statistics);
},
loadTodayReadingTime() {
const today = new Date().toDateString();
const statistics = wx.getStorageSync('reading_statistics') || {};
this.setData({ todayReadingTime: statistics[today] || 0 });
},
// 保存和恢复设置
saveReadingProgress() {
const progress = {
chapter: this.data.currentChapter,
page: this.data.currentPage,
timestamp: Date.now()
};
wx.setStorageSync(`reading_progress_${this.data.bookTitle}`, progress);
},
restoreSettings() {
// 恢复主题
const theme = wx.getStorageSync('reader_theme');
if (theme) {
this.setData({ currentTheme: theme });
}
// 恢复亮度
const brightness = wx.getStorageSync('reader_brightness');
if (brightness !== undefined) {
this.setData({ brightness });
wx.setScreenBrightness({ value: brightness / 100 });
}
// 恢复阅读进度
const progress = wx.getStorageSync(`reading_progress_${this.data.bookTitle}`);
if (progress) {
this.loadChapter(progress.chapter).then(() => {
this.setData({ currentPage: progress.page });
});
}
},
toggleMore() {
this.setData({
showMore: !this.data.showMore,
showSettings: false,
showMenu: false
});
},
// 处理文本选择事件
onSelectText(e: any) {
console.log('Selected text:', e.detail);
const selectedText = e.detail.text;
if (selectedText && selectedText.trim()) {
this.setData({ selectedText });
}
},
// 隐藏文本菜单
hideTextMenu() {
this.setData({
showTextMenu: false,
selectionStart: -1,
selectionEnd: -1
});
},
// 添加书签
addBookmarkWithSelection() {
const bookmark = {
chapter: this.data.currentChapter,
page: this.data.currentPage,
content: this.data.selectedText,
time: new Date().toLocaleString()
};
const bookmarks = [...this.data.bookmarks, bookmark];
this.setData({
bookmarks,
showTextMenu: false
});
wx.setStorageSync(`bookmarks_${this.data.bookTitle}`, bookmarks);
wx.showToast({
title: '添加书签成功',
icon: 'success'
});
},
// 添加笔记
addNoteWithSelection() {
wx.showModal({
title: '添加笔记',
editable: true,
placeholderText: '请输入笔记内容',
success: (res) => {
if (res.confirm && res.content) {
const note = {
chapter: this.data.currentChapter,
page: this.data.currentPage,
selectedText: this.data.selectedText,
content: res.content,
time: new Date().toLocaleString()
};
const notes = [...this.data.notes, note];
this.setData({
notes,
showTextMenu: false
});
wx.setStorageSync(`notes_${this.data.bookTitle}`, notes);
wx.showToast({
title: '添加笔记成功',
icon: 'success'
});
}
}
});
},
// 复制选中文本
copySelectedText() {
if (this.data.selectedText) {
wx.setClipboardData({
data: this.data.selectedText,
success: () => {
this.setData({ showTextMenu: false });
wx.showToast({
title: '复制成功',
icon: 'success'
});
}
});
}
},
// 结束计时并保存
stopReadingTimer() {
if (!this.data.isReading) return;
if (this.data.readingTimer) {
clearInterval(this.data.readingTimer);
}
const endTime = Date.now();
const duration = Math.floor((endTime - this.data.readingStartTime) / 1000);
this.setData({
readingDuration: duration,
isReading: false
});
// 保存阅读记录
this.saveReadingRecord(duration);
},
// 保存阅读记录
saveReadingRecord(duration: number) {
const readingRecord = {
bookId: this.data.bookId,
startTime: this.data.readingStartTime,
duration: duration,
endTime: Date.now()
};
// 可以选择保存到本地或发送到服务器
wx.setStorageSync('lastReadingRecord', readingRecord);
wx.showToast({
title: `本次阅读 ${Math.floor(duration / 60)} 分钟`,
icon: 'none',
duration: 2000
});
}
}
});