Initial Commit

This commit is contained in:
unknown 2024-12-18 16:15:08 +08:00
commit 1939755975
90 changed files with 63474 additions and 0 deletions

31
.eslintrc.js Normal file
View File

@ -0,0 +1,31 @@
/*
* Eslint config file
* Documentation: https://eslint.org/docs/user-guide/configuring/
* Install the Eslint extension before using this feature.
*/
module.exports = {
env: {
es6: true,
browser: true,
node: true,
},
ecmaFeatures: {
modules: true,
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
globals: {
wx: true,
App: true,
Page: true,
getCurrentPages: true,
getApp: true,
Component: true,
requirePlugin: true,
requireMiniProgram: true,
},
// extends: 'eslint:recommended',
rules: {},
}

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# Windows
[Dd]esktop.ini
Thumbs.db
$RECYCLE.BIN/
# macOS
.DS_Store
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
# Node.js
node_modules/

45
miniprogram/app.json Normal file
View File

@ -0,0 +1,45 @@
{
"pages": [
"pages/login/index",
"pages/index/index",
"pages/bookshelf/add",
"pages/logs/logs",
"pages/bookshelf/list",
"pages/notes/notes",
"pages/profile/profile",
"pages/bookshelf/reader/reader"
],
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "阅读管理",
"navigationBarBackgroundColor": "#ffffff"
},
"style": "v2",
"componentFramework": "glass-easel",
"lazyCodeLoading": "requiredComponents",
"tabBar": {
"color": "#999999",
"selectedColor": "#1989fa",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/tabbar/home.png",
"selectedIconPath": "images/tabbar/home.png"
},
{
"pagePath": "pages/bookshelf/list",
"text": "书架"
},
{
"pagePath": "pages/notes/notes",
"text": "笔记"
},
{
"pagePath": "pages/profile/profile",
"text": "我的"
}
]
}
}

41
miniprogram/app.ts Normal file
View File

@ -0,0 +1,41 @@
// app.ts
App<IAppOption>({
globalData: {
userInfo: null,
isLoggedIn: false
},
onLaunch() {
// 检查登录状态
const token = wx.getStorageSync('token');
const userInfo = wx.getStorageSync('userInfo');
if (token && userInfo) {
this.globalData.isLoggedIn = true;
this.globalData.userInfo = userInfo;
} else {
// 未登录则跳转到登录页
wx.redirectTo({
url: '/pages/login/index'
});
}
// 登录
wx.login({
success: res => {
console.log('微信登录成功code:', res.code);
},
});
},
// 检查登录状态的方法
checkLogin() {
if (!this.globalData.isLoggedIn) {
wx.redirectTo({
url: '/pages/login/index'
});
return false;
}
return true;
}
});

10
miniprogram/app.wxss Normal file
View File

@ -0,0 +1,10 @@
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,886 @@
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
});
}
}
});

View File

@ -0,0 +1,120 @@
<view class="reader" style="background: {{themes[currentTheme].bg}}; color: {{themes[currentTheme].color}}">
<!-- 顶部工具栏 -->
<view class="toolbar {{showToolbar ? '' : 'hidden'}}">
<view class="back" bindtap="goBack">
<text class="back-icon">←</text>
</view>
<view class="title">{{bookTitle}}</view>
<view class="menu" bindtap="toggleMenu">
<text class="menu-icon">≡</text>
</view>
<view class="reading-time" wx:if="{{isReading}}">
已阅读 {{Math.floor(readingDuration / 60)}}分{{readingDuration % 60}}秒
</view>
</view>
<!-- 阅读区域 -->
<view class="content"
bindtap="hideTextMenu"
bindtouchstart="touchStart"
bindtouchend="touchEnd">
<view class="chapter-title">第{{currentChapter}}章</view>
<view class="page-content" style="font-size: {{fontSize}}rpx;">
<text user-select="true"
selection-start="{{selectionStart}}"
selection-end="{{selectionEnd}}"
bindlongpress="showTextMenu"
catch:tap="toggleToolbar">{{pages[currentPage]}}</text>
</view>
<view class="page-number">{{currentPage + 1}}/{{pages.length}}</view>
</view>
<!-- 底部工具栏 -->
<view class="bottom-toolbar {{showToolbar ? '' : 'hidden'}}">
<view class="toolbar-top">
<view class="prev" bindtap="prevPage">上一页</view>
<view class="progress">{{currentChapter}}/{{totalChapters}}</view>
<view class="settings" bindtap="toggleSettings">设置</view>
<view class="next" bindtap="nextPage">下一页</view>
</view>
<view class="toolbar-bottom">
<view class="current-time">{{currentTime}}</view>
<view class="reading-progress">已读{{readingProgress}}%</view>
</view>
</view>
<!-- 目录菜单 -->
<view class="menu-panel {{showMenu ? 'show' : ''}}">
<view class="menu-header">
<text>目录</text>
<text class="close" bindtap="toggleMenu">×</text>
</view>
<scroll-view class="chapter-list" scroll-y>
<view class="chapter-item {{currentChapter === index + 1 ? 'active' : ''}}"
wx:for="{{chapters}}"
wx:key="index"
bindtap="jumpToChapter"
data-chapter="{{index + 1}}">
第{{index + 1}}章
</view>
</scroll-view>
</view>
<!-- 设置面板 -->
<view class="settings-panel {{showSettings ? 'show' : ''}}">
<view class="settings-header">
<text>设置</text>
<text class="close" bindtap="toggleSettings">×</text>
</view>
<view class="settings-content">
<!-- 字体大小设置 -->
<view class="setting-item">
<text>字体大小</text>
<view class="font-size-control">
<view class="size-btn" bindtap="decreaseFontSize">A-</view>
<text class="current-size">{{fontSize}}px</text>
<view class="size-btn" bindtap="increaseFontSize">A+</view>
</view>
</view>
<!-- 主题设置 -->
<view class="setting-item">
<text>主题</text>
<view class="theme-list">
<view class="theme-item {{currentTheme === theme.id ? 'active' : ''}}"
wx:for="{{themes}}"
wx:key="id"
wx:for-item="theme"
bindtap="switchTheme"
data-theme="{{theme.id}}"
style="background: {{theme.bg}};">
<text style="color: {{theme.color}}">{{theme.name}}</text>
</view>
</view>
</view>
<!-- 亮度调节 -->
<view class="setting-item">
<text>屏幕亮度</text>
<slider class="brightness-slider"
min="0"
max="100"
value="{{brightness}}"
bind:change="adjustBrightness"
block-size="20"
activeColor="#1989fa"/>
</view>
</view>
</view>
<!-- 遮罩层 -->
<view class="mask {{showSettings || showMenu ? 'show' : ''}}"
bindtap="closeAllPanels"></view>
<!-- 文本选择菜单 -->
<view class="text-menu {{showTextMenu ? 'show' : ''}}"
style="top: {{textMenuTop}}px; left: {{textMenuLeft}}px">
<view class="menu-item" bindtap="addNote">添加笔记</view>
<view class="menu-item" bindtap="addBookmark">添加书签</view>
</view>
</view>

View File

@ -0,0 +1,402 @@
.reader {
height: 100vh;
background: #f4ecd8;
position: relative;
overflow: hidden;
}
/* 顶部工具栏 */
.toolbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 88rpx;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
padding: 0 30rpx;
transition: all 0.3s;
z-index: 100;
}
.toolbar.hidden {
transform: translateY(-100%);
}
.back, .menu {
color: #fff;
font-size: 40rpx;
padding: 20rpx;
}
.title {
flex: 1;
color: #fff;
font-size: 32rpx;
text-align: center;
}
/* 阅读区域 */
.content {
height: 100vh;
padding: 0rpx 0rpx 0rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
width: 100%;
max-width: 700rpx;
margin: 0 auto;
}
.chapter-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin: 20rpx 0;
text-align: center;
}
.page-content {
flex: 1;
line-height: 1.8;
text-align: justify;
overflow: hidden;
white-space: pre-wrap;
padding: 0;
width: 100%;
box-sizing: border-box;
}
.page-content text {
text-indent: 2em;
line-height: 1.8;
}
.page-number {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 20rpx 0;
}
/* 底部页码 */
.page-info {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 10rpx 0;
}
/* 底部评论区域 */
.bottom-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-top: 1rpx solid #eee;
padding: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.bookmark-btn {
color: #666;
font-size: 28rpx;
padding: 10rpx 20rpx;
}
.add-bookmark {
color: #666;
font-size: 28rpx;
padding: 10rpx 20rpx;
display: flex;
align-items: center;
}
.add-bookmark .close {
margin-left: 10rpx;
font-size: 24rpx;
}
/* 评论列表 */
.comment-list {
padding: 20rpx;
background: #fff;
border-top: 1rpx solid #eee;
}
.comment-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
font-size: 28rpx;
color: #333;
}
.comment-time {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
/* 底部工具栏 */
.bottom-toolbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.7);
transition: all 0.3s;
z-index: 100;
padding-bottom: env(safe-area-inset-bottom);
}
.toolbar-top {
height: 100rpx;
display: flex;
align-items: center;
justify-content: space-around;
}
.toolbar-bottom {
height: 60rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 40rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
}
.prev, .next, .settings {
color: #fff;
font-size: 28rpx;
padding: 20rpx 40rpx;
}
.progress {
color: #fff;
font-size: 28rpx;
}
.current-time, .reading-progress {
color: rgba(255, 255, 255, 0.6);
font-size: 24rpx;
}
/* 设置面板 */
.settings-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
transform: translateY(100%);
transition: all 0.3s;
z-index: 999;
border-radius: 20rpx 20rpx 0 0;
padding-bottom: env(safe-area-inset-bottom);
}
.settings-panel.show {
transform: translateY(0);
}
.settings-header {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
}
.settings-header .close {
font-size: 40rpx;
color: #999;
padding: 20rpx;
}
.settings-content {
padding: 30rpx;
}
.setting-item {
margin-bottom: 40rpx;
}
.setting-item text {
font-size: 28rpx;
color: #666;
margin-bottom: 20rpx;
display: block;
}
/* 字体大小控制 */
.font-size-control {
display: flex;
align-items: center;
justify-content: space-between;
background: #f7f7f7;
border-radius: 12rpx;
padding: 20rpx;
margin-top: 16rpx;
}
.size-btn {
width: 80rpx;
height: 80rpx;
border: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #333;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.current-size {
font-size: 28rpx;
color: #333;
}
/* 主题列表 */
.theme-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
margin-top: 16rpx;
}
.theme-item {
aspect-ratio: 1;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.theme-item.active {
transform: scale(1.05);
box-shadow: 0 4rpx 12rpx rgba(25, 137, 250, 0.2);
}
/* 亮度调节 */
.brightness-slider {
margin-top: 16rpx;
padding: 0 12rpx;
}
.brightness-slider slider {
margin: 0;
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: all 0.3s;
z-index: 998;
}
.mask.show {
opacity: 1;
visibility: visible;
}
/* 目录菜单 */
.menu-panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 80%;
background: #fff;
transform: translateX(100%);
transition: all 0.3s;
z-index: 999;
display: flex;
flex-direction: column;
}
.menu-panel.show {
transform: translateX(0);
}
.menu-header {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
}
.menu-header .close {
font-size: 40rpx;
color: #999;
padding: 20rpx;
}
.chapter-list {
flex: 1;
overflow-y: auto;
}
.chapter-item {
padding: 30rpx;
border-bottom: 1rpx solid #eee;
color: #333;
font-size: 28rpx;
}
.chapter-item.active {
color: #1989fa;
background: rgba(25, 137, 250, 0.1);
}
/* 文本选择菜单 */
.text-menu {
position: fixed;
background: rgba(0,0,0,0.8);
border-radius: 8rpx;
padding: 10rpx 0;
transform: translate(-50%, -100%);
opacity: 0;
visibility: hidden;
transition: all 0.2s;
z-index: 999;
}
.text-menu.show {
opacity: 1;
visibility: visible;
}
.menu-item {
color: #fff;
font-size: 28rpx;
padding: 16rpx 30rpx;
white-space: nowrap;
}
.menu-item:active {
background: rgba(255,255,255,0.1);
}
.reading-time {
font-size: 24rpx;
color: #fff;
margin-right: 20rpx;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -0,0 +1,27 @@
Tabbar 图标规范说明
1. 图标列表
- home.png & home-active.png // 首页图标
- bookshelf.png & bookshelf-active.png // 书架图标
- notes.png & notes-active.png // 笔记图标
- profile.png & profile-active.png // 我的图标
2. 图标规格要求
- 尺寸: 81px × 81px
- 格式: PNG
- 背景: 透明背景
- 普通态颜色: #999999
- 选中态颜色: #1989fa
3. 设计建议
- 线条粗细统一
- 图标风格保持一致
- 预留出边距,不要顶边
- 避免过于复杂的细节
4. 图标来源建议
- iconfont (https://www.iconfont.cn/)
- 设计师定制
- 其他开源图标库
注意:替换图标时需保持文件名不变,以确保 tabbar 正常显示。

View File

@ -0,0 +1,16 @@
// 创建 tabbar 图标目录
const iconFiles = [
'home.png', // 首页-未选中
'home-active.png', // 首页-选中
'book.png', // 书架-未选中
'book-active.png', // 书架-选中
'note.png', // 笔记-未选中
'note-active.png', // 笔记-选中
'user.png', // 我的-未选中
'user-active.png' // 我的-选中
];
// 由于微信小程序对图标有特定要求,建议:
// 1. 图标尺寸: 81px × 81px
// 2. 图标格式: PNG
// 3. 背景色: 透明

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@ -0,0 +1,236 @@
import request from '../../utils/request';
interface BookForm {
isbn: string;
title: string;
author: string;
publisher: string;
description: string;
bookUrl: string;
coverUrl: string;
category: string;
tags: string;
language: string;
publishDate: string;
}
Component({
data: {
form: {
isbn: '',
title: '',
author: '',
publisher: '',
description: '',
bookUrl: '',
coverUrl: '',
category: '',
tags: '',
language: '汉语',
publishDate: ''
} as BookForm,
categories: ['小说', '文学', '历史', '科技', '教育', '其他']
},
methods: {
// 输入框变化处理
onInput(e: any) {
const { field } = e.currentTarget.dataset;
this.setData({
[`form.${field}`]: e.detail.value
});
},
// 选择分类
onCategoryChange(e: any) {
this.setData({
'form.category': e.detail.value
});
},
// 选择日期
onDateChange(e: any) {
const date = e.detail.value; // 格式: "2024-12-16"
// 添加时间部分,转换为标准格式
const dateTime = `${date} 00:00:00`;
this.setData({
'form.publishDate': dateTime
});
},
// 上传封面
async uploadCover() {
try {
const res = await wx.chooseMedia({
count: 1,
mediaType: ['image'],
sizeType: ['compressed']
});
wx.showLoading({ title: '上传中...' });
// 上传文件到服务器
const uploadRes = await new Promise((resolve, reject) => {
wx.uploadFile({
url: 'http://localhost:8084/api/common/uploadFile',
filePath: res.tempFiles[0].tempFilePath,
name: 'file',
formData: {
bucketName: 'photo'
},
success: (res) => {
console.log('上传成功:', res);
resolve(res);
},
fail: (error) => {
console.error('上传失败:', error);
reject(error);
}
});
});
console.log('上传响应:', uploadRes);
if (uploadRes.statusCode !== 200) {
throw new Error(`服务器响应错误: ${uploadRes.statusCode}`);
}
const data = JSON.parse(uploadRes.data);
console.log('解析后的数据:', data);
if(data.code === 200) {
this.setData({
'form.coverUrl': data.data
});
wx.showToast({
title: '上传成功',
icon: 'success'
});
} else {
throw new Error(data.message || '上传失败');
}
} catch (error) {
console.error('上传封面失败:', error);
wx.showToast({
title: error instanceof Error ? error.message : '上传失败',
icon: 'none'
});
} finally {
wx.hideLoading();
}
},
// 上传电子书
async uploadBook() {
try {
const res = await wx.chooseMessageFile({
count: 1,
type: 'file',
extension: ['txt']
});
wx.showLoading({ title: '上传中...' });
// 上传文件到服务器
const uploadRes = await new Promise((resolve, reject) => {
wx.uploadFile({
url: 'http://localhost:8084/api/common/uploadFile',
filePath: res.tempFiles[0].path,
name: 'file',
formData: {
bucketName: 'txt'
},
success: (res) => {
console.log('上传成功:', res);
resolve(res);
},
fail: (error) => {
console.error('上传失败:', error);
reject(error);
}
});
});
console.log('上传响应:', uploadRes);
if (uploadRes.statusCode !== 200) {
throw new Error(`服务器响应错误: ${uploadRes.statusCode}`);
}
const data = JSON.parse(uploadRes.data);
console.log('解析后的数据:', data);
if(data.code === 200) {
this.setData({
'form.bookUrl': data.data
});
wx.showToast({
title: '上传成功',
icon: 'success'
});
} else {
throw new Error(data.message || '上传失败');
}
} catch (error) {
console.error('上传文件失败:', error);
wx.showToast({
title: error instanceof Error ? error.message : '上传失败',
icon: 'none'
});
} finally {
wx.hideLoading();
}
},
// 提交表单
async submitForm() {
try {
const { form } = this.data;
// 表单验证
if (!form.title || !form.author || !form.bookUrl) {
wx.showToast({
title: '请填写必要信息',
icon: 'none'
});
return;
}
// 确保日期格式正确
if (form.publishDate && !form.publishDate.includes(':')) {
form.publishDate = `${form.publishDate} 00:00:00`;
}
wx.showLoading({ title: '提交中...' });
const res = await request.post('/books/add', {
...form,
// 添加当前时间作为创建和更新时间
createdTime: new Date().toISOString().replace('T', ' ').split('.')[0],
updatedTime: new Date().toISOString().replace('T', ' ').split('.')[0]
});
if (res.code === 200) {
wx.showToast({
title: '添加成功',
icon: 'success'
});
// 返回书架页面
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
} catch (error) {
console.error('提交失败:', error);
wx.showToast({
title: '提交失败',
icon: 'none'
});
} finally {
wx.hideLoading();
}
}
}
});

View File

@ -0,0 +1,100 @@
<view class="add-book">
<form>
<view class="form-item">
<text class="label">ISBN</text>
<input class="input"
placeholder="请输入ISBN"
value="{{form.isbn}}"
data-field="isbn"
bindinput="onInput"/>
</view>
<view class="form-item">
<text class="label">书名*</text>
<input class="input"
placeholder="请输入书名"
value="{{form.title}}"
data-field="title"
bindinput="onInput"/>
</view>
<view class="form-item">
<text class="label">作者*</text>
<input class="input"
placeholder="请输入作者"
value="{{form.author}}"
data-field="author"
bindinput="onInput"/>
</view>
<view class="form-item">
<text class="label">出版社</text>
<input class="input"
placeholder="请输入出版社"
value="{{form.publisher}}"
data-field="publisher"
bindinput="onInput"/>
</view>
<view class="form-item">
<text class="label">简介</text>
<textarea class="textarea"
placeholder="请输入简介"
value="{{form.description}}"
data-field="description"
bindinput="onInput"/>
</view>
<view class="form-item">
<text class="label">分类</text>
<picker mode="selector"
range="{{categories}}"
bindchange="onCategoryChange">
<view class="picker">
{{form.category || '请选择分类'}}
</view>
</picker>
</view>
<view class="form-item">
<text class="label">标签</text>
<input class="input"
placeholder="请输入标签,多个用逗号分隔"
value="{{form.tags}}"
data-field="tags"
bindinput="onInput"/>
</view>
<view class="form-item">
<text class="label">出版日期</text>
<picker mode="date"
bindchange="onDateChange">
<view class="picker">
{{form.publishDate || '请选择日期'}}
</view>
</picker>
</view>
<view class="form-item">
<text class="label">封面*</text>
<view class="upload-box" bindtap="uploadCover">
<image wx:if="{{form.coverUrl}}"
src="{{form.coverUrl}}"
mode="aspectFill"
class="preview"/>
<view wx:else class="upload-btn">上传封面</view>
</view>
</view>
<view class="form-item">
<text class="label">电子书*</text>
<view class="upload-box" bindtap="uploadBook">
<view class="{{form.bookUrl ? 'file-name' : 'upload-btn'}}">
{{form.bookUrl ? '已选择文件' : '上传TXT文件'}}
</view>
</view>
</view>
<button class="submit-btn" bindtap="submitForm">提交</button>
</form>
</view>

View File

@ -0,0 +1,81 @@
.add-book {
padding: 30rpx;
background: #f7f8fa;
min-height: 100vh;
}
.form-item {
margin-bottom: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 12rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
}
.input {
width: 100%;
height: 80rpx;
border: 1rpx solid #eee;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
.textarea {
width: 100%;
height: 200rpx;
border: 1rpx solid #eee;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
}
.picker {
width: 100%;
height: 80rpx;
border: 1rpx solid #eee;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
line-height: 80rpx;
color: #666;
}
.upload-box {
width: 100%;
height: 200rpx;
border: 1rpx solid #eee;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
}
.upload-btn {
color: #666;
font-size: 28rpx;
}
.preview {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.file-name {
color: #1989fa;
font-size: 28rpx;
}
.submit-btn {
margin-top: 60rpx;
background: #1989fa;
color: #fff;
border-radius: 8rpx;
}

View File

@ -0,0 +1,25 @@
Component({
methods: {
// 更新阅读进度
updateProgress(e: any) {
const { currentPage } = e.detail;
const { book } = this.data;
// 更新进度
wx.cloud.callFunction({
name: 'updateReadingProgress',
data: {
bookId: book.id,
currentPage,
readingTime: this.data.currentReadingTime
}
});
},
// 开始计时
startReading() {
this.startTime = Date.now();
// 开始计时逻辑
}
}
});

View File

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "书架",
"usingComponents": {}
}

View File

@ -0,0 +1,92 @@
import request from '../../utils/request';
Component({
data: {
books: [] as Book[],
categories: ['全部', '在读', '已读', '想读'],
currentCategory: '全部',
loading: false,
current: 1,
size: 10,
total: 0
},
lifetimes: {
attached() {
this.getBookList();
}
},
methods: {
// 获取书籍列表
async getBookList() {
if(this.data.loading) return;
this.setData({ loading: true });
try {
const res = await request.get<BookListResponse>('/books', {
current: this.data.current,
size: this.data.size,
category: this.data.currentCategory === '全部' ? '' : this.data.currentCategory
});
if(res.code === 200) {
console.log(res.data);
this.setData({
books: res.data.records,
total: res.data.total
});
} else {
wx.showToast({
title: '获取书籍列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取书籍列表失败:', error);
wx.showToast({
title: '获取书籍列表失败',
icon: 'none'
});
} finally {
this.setData({ loading: false });
}
},
// 切换分类
async switchCategory(e: any) {
const category = e.currentTarget.dataset.category;
this.setData({
currentCategory: category,
current: 1 // 切换分类时重置页码
});
await this.getBookList();
},
// 打开阅读器
openReader(e: any) {
const { id, bookUrl } = e.currentTarget.dataset;
console.log('准备跳转,参数:', e.currentTarget.dataset);
wx.navigateTo({
url: `/pages/bookshelf/reader/reader?id=${id}&bookUrl=${encodeURIComponent(bookUrl)}&title=${encodeURIComponent(this.data.books.find(b => b.id === id)?.title || '')}`,
success: () => {
console.log('跳转成功');
},
fail: (err) => {
console.error('跳转失败:', err);
}
});
},
// 跳转到添加书籍页
goToAddBook() {
wx.navigateTo({
url: '/pages/bookshelf/add'
});
}
}
});

View File

@ -0,0 +1,43 @@
<view class="bookshelf">
<!-- 分类选择 -->
<view class="categories">
<view class="category-item {{currentCategory === item ? 'active' : ''}}"
wx:for="{{categories}}"
wx:key="*this"
data-category="{{item}}"
bindtap="switchCategory">
{{item}}
</view>
</view>
<!-- 书籍列表 -->
<view class="book-list">
<view class="book-item"
wx:for="{{books}}"
wx:key="id"
bindtap="openReader"
data-id="{{item.id}}"
data-book-url="{{item.bookUrl}}">
<image class="book-cover" src="{{item.coverUrl}}" mode="aspectFill"/>
<view class="book-info">
<text class="book-title">{{item.title}}</text>
<text class="book-author">{{item.author}}</text>
<text class="book-desc">{{item.description}}</text>
<view class="book-meta">
<text class="book-category">{{item.category}}</text>
<!-- <text class="book-date">出版时间:{{item.publishDate}}</text> -->
</view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="loading" wx:if="{{loading}}">加载中...</view>
<view class="no-more" wx:if="{{books.length >= total && total > 0}}">没有更多了</view>
<view class="empty" wx:if="{{books.length === 0 && !loading}}">暂无书籍</view>
<!-- 添加按钮 -->
<view class="add-btn" bindtap="goToAddBook">
<text class="add-icon">+</text>
</view>
</view>

View File

@ -0,0 +1,133 @@
.bookshelf {
padding: 20rpx;
}
.categories {
display: flex;
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
}
.category-item {
padding: 10rpx 30rpx;
margin-right: 20rpx;
border-radius: 30rpx;
font-size: 28rpx;
color: #666;
}
.category-item.active {
background: #1989fa;
color: #fff;
}
.book-list {
margin-top: 30rpx;
}
.book-item {
display: flex;
padding: 20rpx;
margin-bottom: 20rpx;
background: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.book-cover {
width: 160rpx;
height: 220rpx;
border-radius: 8rpx;
}
.book-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
}
.book-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.book-author {
font-size: 28rpx;
color: #666;
margin: 10rpx 0;
}
.book-progress {
font-size: 24rpx;
color: #999;
}
.add-btn {
position: fixed;
right: 40rpx;
bottom: 40rpx;
width: 100rpx;
height: 100rpx;
background: #1989fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(25, 137, 250, 0.3);
}
.add-icon {
color: #fff;
font-size: 60rpx;
}
.book-type {
font-size: 24rpx;
color: #1989fa;
background: rgba(25, 137, 250, 0.1);
padding: 4rpx 12rpx;
border-radius: 4rpx;
margin-top: 8rpx;
}
.book-desc {
font-size: 24rpx;
color: #999;
margin: 8rpx 0;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.book-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8rpx;
}
.book-category {
font-size: 24rpx;
color: #1989fa;
background: rgba(25, 137, 250, 0.1);
padding: 4rpx 12rpx;
border-radius: 4rpx;
}
.book-date {
font-size: 24rpx;
color: #999;
}
/* 加载状态 */
.loading,
.no-more,
.empty {
text-align: center;
padding: 30rpx;
color: #999;
font-size: 28rpx;
}

View File

@ -0,0 +1,18 @@
import request from '../../utils/request';
Page({
data: {
bookId: '',
bookUrl: '',
title: ''
},
onLoad(options) {
const { id, bookUrl, title } = options;
this.setData({
bookId: id,
bookUrl: decodeURIComponent(bookUrl),
title: decodeURIComponent(title)
});
}
});

View File

@ -0,0 +1,5 @@
<reader-component
bookId="{{bookId}}"
bookUrl="{{bookUrl}}"
title="{{title}}"
/>

View File

@ -0,0 +1,27 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const request = require('../../../utils/request');
Page({
data: {
bookId: '',
bookUrl: '',
title: ''
},
onLoad(options) {
const { id, bookUrl, title } = options;
this.setData({
bookId: id,
bookUrl: decodeURIComponent(bookUrl),
title: decodeURIComponent(title)
});
}
});

View File

@ -0,0 +1,6 @@
{
"navigationBarTitleText": "阅读",
"usingComponents": {
"reader-component": "./reader"
}
}

View File

@ -0,0 +1,24 @@
import request from '../../../utils/request';
Page({
data: {
bookId: '',
bookUrl: '',
title: ''
},
onLoad(options) {
const { id, bookUrl, title } = options;
console.log('Reader页面接收到的参数:', {
id,
bookUrl: decodeURIComponent(bookUrl),
title: decodeURIComponent(title)
});
this.setData({
bookId: id,
bookUrl: decodeURIComponent(bookUrl),
title: decodeURIComponent(title)
});
}
});

View File

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@ -0,0 +1,66 @@
// pages/bookshelf/reader/read.ts
Page({
/**
*
*/
data: {
},
/**
* --
*/
onLoad() {
},
/**
* --
*/
onReady() {
},
/**
* --
*/
onShow() {
},
/**
* --
*/
onHide() {
},
/**
* --
*/
onUnload() {
},
/**
* --
*/
onPullDownRefresh() {
},
/**
*
*/
onReachBottom() {
},
/**
*
*/
onShareAppMessage() {
}
})

View File

@ -0,0 +1,2 @@
<!--pages/bookshelf/reader/read.wxml-->
<text>pages/bookshelf/reader/read.wxml</text>

View File

@ -0,0 +1 @@
/* pages/bookshelf/reader/read.wxss */

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,892 @@
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() {
console.log('Reader组件接收到的属性:', {
bookId: this.properties.bookId,
bookUrl: this.properties.bookUrl,
title: this.properties.title
});
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 });
},
// 保存和<E5AD98><E5928C>复设置
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
});
}
}
});

View File

@ -0,0 +1,120 @@
<view class="reader" style="background: {{themes[currentTheme].bg}}; color: {{themes[currentTheme].color}}">
<!-- 顶部工具栏 -->
<view class="toolbar {{showToolbar ? '' : 'hidden'}}">
<view class="back" bindtap="goBack">
<text class="back-icon">←</text>
</view>
<view class="title">{{bookTitle}}</view>
<view class="menu" bindtap="toggleMenu">
<text class="menu-icon">≡</text>
</view>
<view class="reading-time" wx:if="{{isReading}}">
已阅读 {{Math.floor(readingDuration / 60)}}分{{readingDuration % 60}}秒
</view>
</view>
<!-- 阅读区域 -->
<view class="content"
bindtap="hideTextMenu"
bindtouchstart="touchStart"
bindtouchend="touchEnd">
<view class="chapter-title">第{{currentChapter}}章</view>
<view class="page-content" style="font-size: {{fontSize}}rpx;">
<text user-select="true"
selection-start="{{selectionStart}}"
selection-end="{{selectionEnd}}"
bindlongpress="showTextMenu"
catch:tap="toggleToolbar">{{pages[currentPage]}}</text>
</view>
<view class="page-number">{{currentPage + 1}}/{{pages.length}}</view>
</view>
<!-- 底部工具栏 -->
<view class="bottom-toolbar {{showToolbar ? '' : 'hidden'}}">
<view class="toolbar-top">
<view class="prev" bindtap="prevPage">上一页</view>
<view class="progress">{{currentChapter}}/{{totalChapters}}</view>
<view class="settings" bindtap="toggleSettings">设置</view>
<view class="next" bindtap="nextPage">下一页</view>
</view>
<view class="toolbar-bottom">
<view class="current-time">{{currentTime}}</view>
<view class="reading-progress">已读{{readingProgress}}%</view>
</view>
</view>
<!-- 目录菜单 -->
<view class="menu-panel {{showMenu ? 'show' : ''}}">
<view class="menu-header">
<text>目录</text>
<text class="close" bindtap="toggleMenu">×</text>
</view>
<scroll-view class="chapter-list" scroll-y>
<view class="chapter-item {{currentChapter === index + 1 ? 'active' : ''}}"
wx:for="{{chapters}}"
wx:key="index"
bindtap="jumpToChapter"
data-chapter="{{index + 1}}">
第{{index + 1}}章
</view>
</scroll-view>
</view>
<!-- 设置面板 -->
<view class="settings-panel {{showSettings ? 'show' : ''}}">
<view class="settings-header">
<text>设置</text>
<text class="close" bindtap="toggleSettings">×</text>
</view>
<view class="settings-content">
<!-- 字体大小设置 -->
<view class="setting-item">
<text>字体大小</text>
<view class="font-size-control">
<view class="size-btn" bindtap="decreaseFontSize">A-</view>
<text class="current-size">{{fontSize}}px</text>
<view class="size-btn" bindtap="increaseFontSize">A+</view>
</view>
</view>
<!-- 主题设置 -->
<view class="setting-item">
<text>主题</text>
<view class="theme-list">
<view class="theme-item {{currentTheme === theme.id ? 'active' : ''}}"
wx:for="{{themes}}"
wx:key="id"
wx:for-item="theme"
bindtap="switchTheme"
data-theme="{{theme.id}}"
style="background: {{theme.bg}};">
<text style="color: {{theme.color}}">{{theme.name}}</text>
</view>
</view>
</view>
<!-- 亮度调节 -->
<view class="setting-item">
<text>屏幕亮度</text>
<slider class="brightness-slider"
min="0"
max="100"
value="{{brightness}}"
bind:change="adjustBrightness"
block-size="20"
activeColor="#1989fa"/>
</view>
</view>
</view>
<!-- 遮罩层 -->
<view class="mask {{showSettings || showMenu ? 'show' : ''}}"
bindtap="closeAllPanels"></view>
<!-- 文本选择菜单 -->
<view class="text-menu {{showTextMenu ? 'show' : ''}}"
style="top: {{textMenuTop}}px; left: {{textMenuLeft}}px">
<view class="menu-item" bindtap="addNote">添加笔记</view>
<view class="menu-item" bindtap="addBookmark">添加书签</view>
</view>
</view>

View File

@ -0,0 +1,402 @@
.reader {
height: 100vh;
background: #f4ecd8;
position: relative;
overflow: hidden;
}
/* 顶部工具栏 */
.toolbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 88rpx;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
padding: 0 30rpx;
transition: all 0.3s;
z-index: 100;
}
.toolbar.hidden {
transform: translateY(-100%);
}
.back, .menu {
color: #fff;
font-size: 40rpx;
padding: 20rpx;
}
.title {
flex: 1;
color: #fff;
font-size: 32rpx;
text-align: center;
}
/* 阅读区域 */
.content {
height: 100vh;
padding: 0rpx 0rpx 0rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
width: 100%;
max-width: 700rpx;
margin: 0 auto;
}
.chapter-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin: 20rpx 0;
text-align: center;
}
.page-content {
flex: 1;
line-height: 1.8;
text-align: justify;
overflow: hidden;
white-space: pre-wrap;
padding: 0;
width: 100%;
box-sizing: border-box;
}
.page-content text {
text-indent: 2em;
line-height: 1.8;
}
.page-number {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 20rpx 0;
}
/* 底部页码 */
.page-info {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 10rpx 0;
}
/* 底部评论区域 */
.bottom-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-top: 1rpx solid #eee;
padding: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.bookmark-btn {
color: #666;
font-size: 28rpx;
padding: 10rpx 20rpx;
}
.add-bookmark {
color: #666;
font-size: 28rpx;
padding: 10rpx 20rpx;
display: flex;
align-items: center;
}
.add-bookmark .close {
margin-left: 10rpx;
font-size: 24rpx;
}
/* 评论列表 */
.comment-list {
padding: 20rpx;
background: #fff;
border-top: 1rpx solid #eee;
}
.comment-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
font-size: 28rpx;
color: #333;
}
.comment-time {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
/* 底部工具栏 */
.bottom-toolbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.7);
transition: all 0.3s;
z-index: 100;
padding-bottom: env(safe-area-inset-bottom);
}
.toolbar-top {
height: 100rpx;
display: flex;
align-items: center;
justify-content: space-around;
}
.toolbar-bottom {
height: 60rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 40rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
}
.prev, .next, .settings {
color: #fff;
font-size: 28rpx;
padding: 20rpx 40rpx;
}
.progress {
color: #fff;
font-size: 28rpx;
}
.current-time, .reading-progress {
color: rgba(255, 255, 255, 0.6);
font-size: 24rpx;
}
/* 设置面板 */
.settings-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
transform: translateY(100%);
transition: all 0.3s;
z-index: 999;
border-radius: 20rpx 20rpx 0 0;
padding-bottom: env(safe-area-inset-bottom);
}
.settings-panel.show {
transform: translateY(0);
}
.settings-header {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
}
.settings-header .close {
font-size: 40rpx;
color: #999;
padding: 20rpx;
}
.settings-content {
padding: 30rpx;
}
.setting-item {
margin-bottom: 40rpx;
}
.setting-item text {
font-size: 28rpx;
color: #666;
margin-bottom: 20rpx;
display: block;
}
/* 字体大小控制 */
.font-size-control {
display: flex;
align-items: center;
justify-content: space-between;
background: #f7f7f7;
border-radius: 12rpx;
padding: 20rpx;
margin-top: 16rpx;
}
.size-btn {
width: 80rpx;
height: 80rpx;
border: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #333;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.current-size {
font-size: 28rpx;
color: #333;
}
/* 主题列表 */
.theme-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
margin-top: 16rpx;
}
.theme-item {
aspect-ratio: 1;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.theme-item.active {
transform: scale(1.05);
box-shadow: 0 4rpx 12rpx rgba(25, 137, 250, 0.2);
}
/* 亮度调节 */
.brightness-slider {
margin-top: 16rpx;
padding: 0 12rpx;
}
.brightness-slider slider {
margin: 0;
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: all 0.3s;
z-index: 998;
}
.mask.show {
opacity: 1;
visibility: visible;
}
/* 目录菜单 */
.menu-panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 80%;
background: #fff;
transform: translateX(100%);
transition: all 0.3s;
z-index: 999;
display: flex;
flex-direction: column;
}
.menu-panel.show {
transform: translateX(0);
}
.menu-header {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
}
.menu-header .close {
font-size: 40rpx;
color: #999;
padding: 20rpx;
}
.chapter-list {
flex: 1;
overflow-y: auto;
}
.chapter-item {
padding: 30rpx;
border-bottom: 1rpx solid #eee;
color: #333;
font-size: 28rpx;
}
.chapter-item.active {
color: #1989fa;
background: rgba(25, 137, 250, 0.1);
}
/* 文本选择菜单 */
.text-menu {
position: fixed;
background: rgba(0,0,0,0.8);
border-radius: 8rpx;
padding: 10rpx 0;
transform: translate(-50%, -100%);
opacity: 0;
visibility: hidden;
transition: all 0.2s;
z-index: 999;
}
.text-menu.show {
opacity: 1;
visibility: visible;
}
.menu-item {
color: #fff;
font-size: 28rpx;
padding: 16rpx 30rpx;
white-space: nowrap;
}
.menu-item:active {
background: rgba(255,255,255,0.1);
}
.reading-time {
font-size: 24rpx;
color: #fff;
margin-right: 20rpx;
}

View File

@ -0,0 +1,4 @@
{
"usingComponents": {
}
}

View File

@ -0,0 +1,152 @@
// index.ts
// 获取应用实例
const app = getApp<IAppOption>()
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
Component({
data: {
userInfo: {
avatarUrl: '/images/default-avatar.png',
nickName: '体验卡已过期'
},
recentBooks: [
{
id: 1,
title: 'Java编程思想',
author: 'Bruce Eckel',
coverUrl: 'https://img3.doubanio.com/view/subject/l/public/s27243455.jpg',
bookUrl: '',
description: '本书赢得了全球程序员的广泛赞誉'
},
{
id: 2,
title: 'C Primer Plus',
author: 'Stephen Prata',
coverUrl: 'https://img2.doubanio.com/view/subject/l/public/s29196249.jpg',
bookUrl: '',
description: 'C语言程序设计经典教程'
},
{
id: 3,
title: '狂帆',
author: '血红',
coverUrl: 'https://img1.doubanio.com/view/subject/l/public/s29934566.jpg',
bookUrl: '',
description: '一本精彩的玄幻小说'
}
] as Book[],
recommendBooks: [
{
id: 4,
title: '龙族IV奥丁之渊',
author: '江南',
coverUrl: 'https://img2.doubanio.com/view/subject/l/public/s29850307.jpg',
bookUrl: '',
description: '青春如同奔流的江水,一去不回来不及道别',
rating: 89.8,
status: '大家都在读'
},
{
id: 5,
title: '白鹿原',
author: '陈忠实',
coverUrl: 'https://img1.doubanio.com/view/subject/l/public/s28111905.jpg',
bookUrl: '',
description: '一部渭河平原五十年变迁的雄奇史诗',
rating: 91.9,
status: '神作'
},
{
id: 6,
title: 'Linux是怎样工作的',
author: '[日]武内觉',
coverUrl: 'https://img3.doubanio.com/view/subject/l/public/s33654311.jpg',
bookUrl: '',
description: '从命令行、Shell脚本到内核原理',
rating: 80.0,
status: '技术经典'
}
] as Book[],
currentTab: 'recommend',
loading: false
},
lifetimes: {
attached() {
// 使用模拟数据暂时注释掉实际的API调用
// this.getUserInfo();
// this.getRecentBooks();
// this.getRecommendBooks();
}
},
methods: {
// 获取用户信息
async getUserInfo() {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
this.setData({ userInfo });
}
},
// 获取最近阅读的书籍
async getRecentBooks() {
try {
const res = await request.get<BookListResponse>('/books/recent');
if (res.code === 200) {
this.setData({
recentBooks: res.data.records
});
}
} catch (error) {
console.error('获取最近阅读失败:', error);
}
},
// 获取推荐书籍
async getRecommendBooks() {
try {
this.setData({ loading: true });
const res = await request.get<BookListResponse>('/books/recommend');
if (res.code === 200) {
this.setData({
recommendBooks: res.data.records
});
}
} catch (error) {
console.error('获取推荐书籍失败:', error);
} finally {
this.setData({ loading: false });
}
},
// 切换标签
switchTab(e: any) {
const tab = e.currentTarget.dataset.tab;
this.setData({ currentTab: tab });
// TODO: 根据不同标签加载不同内容
},
// 打开书籍
openBook(e: any) {
const { id, url } = e.currentTarget.dataset;
wx.navigateTo({
url: `/pages/bookshelf/reader?id=${id}&bookUrl=${encodeURIComponent(url)}`
});
},
// 跳转到书架
goToBookshelf() {
wx.switchTab({
url: '/pages/bookshelf/list'
});
},
// 跳转到设置
goToSettings() {
wx.navigateTo({
url: '/pages/profile/settings'
});
}
}
})

View File

@ -0,0 +1,80 @@
<!--index.wxml-->
<scroll-view class="scrollarea" scroll-y type="list">
<view class="container">
<!-- 顶部标题栏 -->
<view class="header">
<text class="title">阅读管理</text>
<view class="settings" bindtap="goToSettings">设置</view>
</view>
<!-- 搜索框 -->
<view class="search-box">
<icon type="search" size="16" color="#999"/>
<input class="search-input" placeholder="搜索" placeholder-class="search-placeholder"/>
</view>
<!-- 用户信息栏 -->
<view class="user-bar">
<text class="status-text">体验卡已过期</text>
</view>
<!-- 书架预览 -->
<scroll-view class="book-list" scroll-x enable-flex>
<view class="book-item"
wx:for="{{recentBooks}}"
wx:key="id"
bindtap="openBook"
data-id="{{item.id}}"
data-url="{{item.bookUrl}}">
<image class="book-cover" src="{{item.coverUrl}}" mode="aspectFill"/>
</view>
<view class="book-more" bindtap="goToBookshelf">
<text>书架</text>
<text class="arrow">></text>
</view>
</scroll-view>
<!-- 导航标签 -->
<scroll-view class="nav-tabs" scroll-x enable-flex>
<view class="tab {{currentTab === 'recommend' ? 'active' : ''}}"
data-tab="recommend"
bindtap="switchTab">推荐</view>
<view class="tab {{currentTab === 'category' ? 'active' : ''}}"
data-tab="category"
bindtap="switchTab">分类</view>
<view class="tab {{currentTab === 'rank' ? 'active' : ''}}"
data-tab="rank"
bindtap="switchTab">排行</view>
<view class="tab {{currentTab === 'novel' ? 'active' : ''}}"
data-tab="novel"
bindtap="switchTab">精品小说</view>
<view class="tab {{currentTab === 'literature' ? 'active' : ''}}"
data-tab="literature"
bindtap="switchTab">文学</view>
</scroll-view>
<!-- 内容区域 -->
<view class="content">
<!-- 推荐列表 -->
<view class="book-list" wx:if="{{currentTab === 'recommend'}}">
<view class="book-item"
wx:for="{{recommendBooks}}"
wx:key="id"
bindtap="openBook"
data-id="{{item.id}}"
data-url="{{item.bookUrl}}">
<image class="book-cover" src="{{item.coverUrl}}" mode="aspectFill"/>
<view class="book-info">
<text class="book-title">{{item.title}}</text>
<view class="book-meta">
<text class="book-author">{{item.author}}</text>
<text class="book-rating">推荐值 {{item.rating}}%</text>
</view>
<text class="book-desc">{{item.description}}</text>
<text class="book-status">{{item.status}}</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>

View File

@ -0,0 +1,351 @@
/**index.wxss**/
page {
height: 100vh;
display: flex;
flex-direction: column;
}
.scrollarea {
flex: 1;
overflow-y: hidden;
}
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
color: #aaa;
width: 80%;
}
.userinfo-avatar {
overflow: hidden;
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.usermotto {
margin-top: 200px;
}
.avatar-wrapper {
padding: 0;
width: 56px !important;
border-radius: 8px;
margin-top: 40px;
margin-bottom: 40px;
}
.avatar {
display: block;
width: 56px;
height: 56px;
}
.nickname-wrapper {
display: flex;
width: 100%;
padding: 16px;
box-sizing: border-box;
border-top: .5px solid rgba(0, 0, 0, 0.1);
border-bottom: .5px solid rgba(0, 0, 0, 0.1);
color: black;
}
.nickname-label {
width: 105px;
}
.nickname-input {
flex: 1;
}
.container {
min-height: 100vh;
background: #f7f8fa;
}
/* 顶部标题栏 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background: #fff;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.settings {
font-size: 28rpx;
color: #666;
}
/* 搜索框 */
.search-box {
margin: 20rpx 30rpx;
display: flex;
align-items: center;
background: #f2f3f5;
padding: 16rpx 24rpx;
border-radius: 32rpx;
}
.search-input {
flex: 1;
margin-left: 16rpx;
font-size: 28rpx;
}
.search-placeholder {
color: #999;
}
/* 用户信息栏 */
.user-bar {
padding: 20rpx 30rpx;
background: #fff;
}
.status-text {
font-size: 32rpx;
color: #333;
}
/* 书架预览 */
.book-list {
padding: 20rpx 30rpx;
white-space: nowrap;
background: #fff;
margin-top: 20rpx;
}
.book-item {
display: inline-block;
margin-right: 20rpx;
}
.book-cover {
width: 180rpx;
height: 240rpx;
border-radius: 8rpx;
box-shadow: 0 4rpx 8rpx rgba(0,0,0,0.1);
}
.book-more {
display: inline-flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 180rpx;
height: 240rpx;
background: #f7f8fa;
border-radius: 8rpx;
color: #666;
font-size: 28rpx;
}
.arrow {
margin-top: 10rpx;
color: #999;
}
/* 导航标签 */
.nav-tabs {
display: flex;
white-space: nowrap;
background: #fff;
padding: 0 20rpx;
margin-top: 20rpx;
}
.tab {
display: inline-block;
padding: 20rpx 30rpx;
font-size: 30rpx;
color: #666;
position: relative;
}
.tab.active {
color: #333;
font-weight: bold;
}
.tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 6rpx;
background: #333;
border-radius: 3rpx;
}
/* 内容区域 */
.content .book-item {
display: flex;
padding: 20rpx;
background: #fff;
margin-bottom: 20rpx;
border-radius: 12rpx;
}
.content .book-cover {
width: 160rpx;
height: 220rpx;
margin-right: 20rpx;
}
.book-info {
flex: 1;
}
.book-title {
font-size: 32rpx;
color: #333;
margin-bottom: 10rpx;
}
.book-meta {
display: flex;
justify-content: space-between;
margin-bottom: 10rpx;
}
.book-author {
font-size: 26rpx;
color: #666;
}
.book-rating {
font-size: 26rpx;
color: #ff9500;
}
.book-desc {
font-size: 26rpx;
color: #999;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
margin-bottom: 10rpx;
}
.book-status {
font-size: 24rpx;
color: #1989fa;
}
.today-stats {
display: flex;
justify-content: space-around;
padding: 30rpx;
background: #fff;
border-radius: 12rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 40rpx;
font-weight: bold;
color: #1989fa;
}
.stat-label {
font-size: 24rpx;
color: #666;
margin-top: 10rpx;
}
.section {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
}
.more {
font-size: 24rpx;
color: #1989fa;
}
.note-list {
margin-top: 20rpx;
}
.note-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
}
.note-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.note-content {
font-size: 24rpx;
color: #666;
margin: 10rpx 0;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.note-time {
font-size: 24rpx;
color: #999;
}
.reading-goal {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.goal-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.goal-text {
font-size: 24rpx;
color: #666;
margin-top: 10rpx;
display: block;
text-align: center;
}

View File

@ -0,0 +1,137 @@
import request from '../../utils/request';
Component({
data: {
username: '',
password: '',
isRegister: false // 控制是登录还是注册模式
},
methods: {
// 切换登录/注册模式
switchMode() {
this.setData({
isRegister: !this.data.isRegister
});
},
// 输入手机号
onPhoneInput(e: any) {
this.setData({
username: e.detail.value
});
},
// 输入密码
onPasswordInput(e: any) {
this.setData({
password: e.detail.value
});
},
// 登录
async handleLogin() {
console.log('开始登录:', this.data);
const { username, password } = this.data;
if (!username || !password) {
wx.showToast({
title: '请输入账号和密码',
icon: 'none'
});
return;
}
try {
console.log('发送登录请求...');
const res = await request.post('/user/login', {
activeTab: "account",
username,
password
});
console.log('登录响应:', res);
if (res.code === 200) {
console.log('登录成功,保存信息...');
// 保存用户信息和token
wx.setStorageSync('userInfo', res.data);
wx.setStorageSync('token', res.data);
console.log('准备跳转...');
// 显示成功提示并跳转
wx.showToast({
title: '登录成功',
icon: 'success',
mask: true,
duration: 1500,
success: () => {
setTimeout(() => {
wx.switchTab({
url: '/pages/index/index',
success: () => {
console.log('跳转成功');
},
fail: (error) => {
console.error('跳转失败:', error);
}
});
}, 1500);
}
});
} else {
console.log('登录失败:', res.message);
wx.showToast({
title: res.message || '登录失败',
icon: 'none'
});
}
} catch (error) {
console.error('登录出错:', error);
wx.showToast({
title: '登录失败',
icon: 'none'
});
}
},
// 注册
async handleRegister() {
const { username, password } = this.data;
if (!username || !password) {
wx.showToast({
title: '请输入手机号和密码',
icon: 'none'
});
return;
}
try {
const res = await request.post('/user/register', {
username,
password
});
if (res.code === 0) {
wx.showToast({
title: '注册成功',
icon: 'success'
});
this.setData({
isRegister: false
});
} else {
wx.showToast({
title: res.message || '注册失败',
icon: 'none'
});
}
} catch (error) {
console.error('注册失败:', error);
wx.showToast({
title: '注册失败',
icon: 'none'
});
}
}
}
});

View File

@ -0,0 +1,30 @@
<view class="login-container">
<view class="logo">
<image src="/images/logo.png" mode="aspectFit"/>
</view>
<view class="form">
<view class="form-item">
<input type="number"
placeholder="请输入账号"
value="{{username}}"
bindinput="onPhoneInput"/>
</view>
<view class="form-item">
<input type="password"
placeholder="请输入密码"
value="{{password}}"
bindinput="onPasswordInput"/>
</view>
<button class="submit-btn"
bindtap="{{isRegister ? 'handleRegister' : 'handleLogin'}}">
{{isRegister ? '注册' : '登录'}}
</button>
<view class="switch-mode" bindtap="switchMode">
{{isRegister ? '已有账号?去登录' : '没有账号?去注册'}}
</view>
</view>
</view>

View File

@ -0,0 +1,43 @@
.login-container {
height: 100vh;
padding: 40rpx;
background: #fff;
}
.logo {
margin: 80rpx auto;
text-align: center;
}
.logo image {
width: 200rpx;
height: 200rpx;
}
.form {
margin-top: 60rpx;
}
.form-item {
margin-bottom: 40rpx;
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
}
.form-item input {
font-size: 32rpx;
}
.submit-btn {
margin-top: 60rpx;
background: #1989fa;
color: #fff;
border-radius: 8rpx;
}
.switch-mode {
margin-top: 40rpx;
text-align: center;
color: #1989fa;
font-size: 28rpx;
}

View File

@ -0,0 +1,4 @@
{
"usingComponents": {
}
}

View File

@ -0,0 +1,21 @@
// logs.ts
// const util = require('../../utils/util.js')
import { formatTime } from '../../utils/util'
Component({
data: {
logs: [],
},
lifetimes: {
attached() {
this.setData({
logs: (wx.getStorageSync('logs') || []).map((log: string) => {
return {
date: formatTime(new Date(log)),
timeStamp: log
}
}),
})
}
},
})

View File

@ -0,0 +1,6 @@
<!--logs.wxml-->
<scroll-view class="scrollarea" scroll-y type="list">
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
<view class="log-item">{{index + 1}}. {{log.date}}</view>
</block>
</scroll-view>

View File

@ -0,0 +1,16 @@
page {
height: 100vh;
display: flex;
flex-direction: column;
}
.scrollarea {
flex: 1;
overflow-y: hidden;
}
.log-item {
margin-top: 20rpx;
text-align: center;
}
.log-item:last-child {
padding-bottom: env(safe-area-inset-bottom);
}

View File

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "笔记",
"usingComponents": {}
}

View File

@ -0,0 +1,26 @@
Component({
data: {
notes: []
},
methods: {
// 获取笔记列表
getNoteList() {
// TODO: 从服务器获取笔记列表
},
// 跳转到笔记详情
goToNoteDetail(e: any) {
const noteId = e.currentTarget.dataset.id;
wx.navigateTo({
url: `/pages/notes/detail?id=${noteId}`
});
},
// 创建新笔记
createNote() {
wx.navigateTo({
url: '/pages/notes/edit'
});
}
}
});

View File

@ -0,0 +1,22 @@
<view class="notes">
<!-- 笔记列表 -->
<view class="note-list">
<view class="note-item"
wx:for="{{notes}}"
wx:key="id"
bindtap="goToNoteDetail"
data-id="{{item.id}}">
<view class="note-title">{{item.title}}</view>
<view class="note-content">{{item.content}}</view>
<view class="note-meta">
<text class="note-book">《{{item.bookTitle}}》</text>
<text class="note-time">{{item.createTime}}</text>
</view>
</view>
</view>
<!-- 添加按钮 -->
<view class="add-btn" bindtap="createNote">
<text class="add-icon">+</text>
</view>
</view>

View File

@ -0,0 +1,59 @@
.notes {
padding: 20rpx;
}
.note-list {
margin-top: 20rpx;
}
.note-item {
background: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
border-radius: 12rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.note-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
}
.note-content {
font-size: 28rpx;
color: #666;
line-height: 1.6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.note-meta {
margin-top: 16rpx;
font-size: 24rpx;
color: #999;
display: flex;
justify-content: space-between;
}
.add-btn {
position: fixed;
right: 40rpx;
bottom: 40rpx;
width: 100rpx;
height: 100rpx;
background: #1989fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(25, 137, 250, 0.3);
}
.add-icon {
color: #fff;
font-size: 60rpx;
}

View File

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "笔记",
"usingComponents": {}
}

View File

@ -0,0 +1,140 @@
Page({
data: {
activeTab: 'notes',
notes: [
{
bookTitle: '大爱仙尊',
chapter: 1,
selectedText: '"方源,乖乖地交出春秋蝉,我给你个痛快!"',
content: '反派的第一句台词,霸气十足。',
time: '2024-03-17 15:30'
},
{
bookTitle: '大爱仙尊',
chapter: 1,
selectedText: '"方老魔,你不要妄图反抗了,今日我们正道各大派联手起来,就是要踏破你的魔窟。这里早已经布下天罗地网,这次你必定身首异处!"',
content: '正派群起而攻之,场面宏大。',
time: '2024-03-17 16:45'
},
{
bookTitle: '大爱仙尊',
chapter: 2,
selectedText: '一直静如雕塑的方源,慢慢转身。群雄顿时一阵骚动,齐齐后退一大步。',
content: '反派气场强大,一个转身就震慑群雄。',
time: '2024-03-17 17:00'
}
],
bookmarks: [
{
bookTitle: '大爱仙尊',
chapter: 1,
content: '围攻他的正道群雄,不是堂堂一派之长者尊贵,就是名动四方之少年英豪。此时军军包围着方源,有的在咆哮,有的在冷笑,有的双眼眯起闪着警惕的光,有的捂着伤口恨恨地望着。',
page: 0,
time: '2024-03-17 15:20'
},
{
bookTitle: '大爱仙尊',
chapter: 3,
content: '他们没有动手,都忌惮着方源的临死反扑。就这样紧张地对峙了三个时辰,夕阳西下,落日的余晖将山边的晚霞点燃,一时间绚烂如火。',
page: 2,
time: '2024-03-17 17:00'
},
{
bookTitle: '大爱仙尊',
chapter: 5,
content: '方源站在悬崖边上,背后是万丈深渊。他的黑袍在风中猎猎作响,长发飞扬,眼神中透露出一丝决绝。',
page: 1,
time: '2024-03-17 17:30'
}
]
},
onLoad(options) {
// 如果从书签跳转过来,自动切换到书签标签
if (options.tab === 'bookmarks') {
this.setData({ activeTab: 'bookmarks' });
}
},
// 切换标签
switchTab(e: any) {
const tab = e.currentTarget.dataset.tab;
this.setData({ activeTab: tab });
},
// 编辑笔记
editNote(e: any) {
const note = e.currentTarget.dataset.note;
wx.showModal({
title: '编辑笔记',
editable: true,
content: note.content,
success: (res) => {
if (res.confirm && res.content) {
const notes = this.data.notes.map(item => {
if (item.time === note.time) {
return { ...item, content: res.content };
}
return item;
});
this.setData({ notes });
wx.showToast({ title: '编辑成功', icon: 'success' });
}
}
});
},
// 删除笔记
deleteNote(e: any) {
const note = e.currentTarget.dataset.note;
wx.showModal({
title: '删除笔记',
content: '确定要删除这条笔记吗?',
success: (res) => {
if (res.confirm) {
const notes = this.data.notes.filter(item => item.time !== note.time);
this.setData({ notes });
wx.showToast({ title: '删除成功', icon: 'success' });
}
}
});
},
// 跳转到书签位置
jumpToBookmark(e: any) {
const bookmark = e.currentTarget.dataset.bookmark;
wx.navigateTo({
url: `/pages/reader/reader?bookTitle=${bookmark.bookTitle}&chapter=${bookmark.chapter}&page=${bookmark.page}`
});
},
// 删除书签
deleteBookmark(e: any) {
const bookmark = e.currentTarget.dataset.bookmark;
wx.showModal({
title: '删除书签',
content: '确定要删除这个书签吗?',
success: (res) => {
if (res.confirm) {
const bookmarks = this.data.bookmarks.filter(item => item.time !== bookmark.time);
this.setData({ bookmarks });
wx.showToast({ title: '删除成功', icon: 'success' });
}
}
});
},
// 跳转到阅读器
navigateToReader() {
wx.navigateTo({
url: '/pages/reader/reader'
});
},
onShow() {
// 从本地存储获取最新数据
const notes = wx.getStorageSync('all_notes') || this.data.notes;
const bookmarks = wx.getStorageSync('all_bookmarks') || this.data.bookmarks;
this.setData({ notes, bookmarks });
}
});

View File

@ -0,0 +1,65 @@
<view class="notes-page">
<!-- 顶部导航 -->
<view class="nav-bar">
<view class="nav-item {{activeTab === 'notes' ? 'active' : ''}}"
bindtap="switchTab" data-tab="notes">笔记</view>
<view class="nav-item {{activeTab === 'bookmarks' ? 'active' : ''}}"
bindtap="switchTab" data-tab="bookmarks">书签</view>
</view>
<!-- 笔记列表 -->
<view class="notes-list" wx:if="{{activeTab === 'notes'}}">
<block wx:if="{{notes.length > 0}}">
<view class="note-item" wx:for="{{notes}}" wx:key="time">
<view class="note-header">
<text class="book-title">{{item.bookTitle}}</text>
<text class="chapter">第{{item.chapter}}章</text>
</view>
<view class="note-content">
<text class="selected-text">{{item.selectedText}}</text>
<text class="note-text">{{item.content}}</text>
</view>
<view class="note-footer">
<text class="time">{{item.time}}</text>
<view class="actions">
<text class="action" bindtap="editNote" data-note="{{item}}">编辑</text>
<text class="action" bindtap="deleteNote" data-note="{{item}}">删除</text>
</view>
</view>
</view>
</block>
<view class="empty-state" wx:else>
<text>暂无笔记</text>
<text class="tip">阅读时长按文字添加笔记</text>
</view>
</view>
<!-- 书签列表 -->
<view class="bookmarks-list" wx:if="{{activeTab === 'bookmarks'}}">
<block wx:if="{{bookmarks.length > 0}}">
<view class="bookmark-item" wx:for="{{bookmarks}}" wx:key="time">
<view class="bookmark-header">
<text class="book-title">{{item.bookTitle}}</text>
<text class="chapter">第{{item.chapter}}章</text>
</view>
<view class="bookmark-content">{{item.content}}</view>
<view class="bookmark-footer">
<text class="time">{{item.time}}</text>
<view class="actions">
<text class="action" bindtap="jumpToBookmark" data-bookmark="{{item}}">跳转</text>
<text class="action" bindtap="deleteBookmark" data-bookmark="{{item}}">删除</text>
</view>
</view>
</view>
</block>
<view class="empty-state" wx:else>
<text>暂无书签</text>
<text class="tip">阅读时点击右上角添加书签</text>
</view>
</view>
<!-- 添加按钮 -->
<view class="add-btn" bindtap="navigateToReader">
<text class="plus">+</text>
</view>
</view>

View File

@ -0,0 +1,168 @@
.notes-page {
min-height: 100vh;
background: #f8f8f8;
}
/* 顶部导航 */
.nav-bar {
display: flex;
background: #fff;
padding: 20rpx 40rpx;
position: sticky;
top: 0;
z-index: 10;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.nav-item {
flex: 1;
text-align: center;
font-size: 28rpx;
color: #666;
padding: 20rpx 0;
position: relative;
}
.nav-item.active {
color: #1989fa;
font-weight: 500;
}
.nav-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background: #1989fa;
border-radius: 2rpx;
}
/* 笔记列表 */
.notes-list, .bookmarks-list {
padding: 20rpx;
}
.note-item, .bookmark-item {
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.note-header, .bookmark-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.book-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.chapter {
font-size: 24rpx;
color: #999;
}
.note-content {
margin: 16rpx 0;
}
.selected-text {
font-size: 26rpx;
color: #666;
background: #f5f5f5;
padding: 16rpx;
border-radius: 8rpx;
display: block;
margin-bottom: 12rpx;
}
.note-text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
.bookmark-content {
font-size: 28rpx;
color: #333;
line-height: 1.6;
margin: 16rpx 0;
}
.note-footer, .bookmark-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx solid #eee;
}
.time {
font-size: 24rpx;
color: #999;
}
.actions {
display: flex;
gap: 20rpx;
}
.action {
font-size: 24rpx;
color: #1989fa;
padding: 8rpx 16rpx;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
color: #999;
}
.empty-state text {
font-size: 28rpx;
margin-bottom: 20rpx;
}
.empty-state .tip {
font-size: 24rpx;
color: #ccc;
}
/* 添加按钮 */
.add-btn {
position: fixed;
right: 40rpx;
bottom: calc(40rpx + env(safe-area-inset-bottom));
width: 100rpx;
height: 100rpx;
background: #1989fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(25, 137, 250, 0.4);
transition: all 0.3s;
}
.add-btn:active {
transform: scale(0.95);
}
.add-btn .plus {
color: #fff;
font-size: 48rpx;
font-weight: 300;
}

View File

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "我的",
"usingComponents": {}
}

View File

@ -0,0 +1,21 @@
Component({
data: {
userInfo: {},
readingStats: {
totalBooks: 0,
totalPages: 0,
totalTime: 0
}
},
methods: {
// 获取用户信息
getUserInfo() {
// TODO: 获取用户信息
},
// 获取阅读统计
getReadingStats() {
// TODO: 获取阅读统计数据
}
}
});

View File

@ -0,0 +1,39 @@
<view class="profile">
<!-- 用户信息 -->
<view class="user-info">
<image class="avatar" src="{{userInfo.avatarUrl}}" mode="aspectFill"/>
<text class="nickname">{{userInfo.nickName}}</text>
</view>
<!-- 阅读统计 -->
<view class="stats">
<view class="stats-item">
<text class="stats-num">{{readingStats.totalBooks}}</text>
<text class="stats-label">读过的书</text>
</view>
<view class="stats-item">
<text class="stats-num">{{readingStats.totalPages}}</text>
<text class="stats-label">阅读页数</text>
</view>
<view class="stats-item">
<text class="stats-num">{{readingStats.totalTime}}</text>
<text class="stats-label">阅读时长</text>
</view>
</view>
<!-- 功能列表 -->
<view class="menu-list">
<view class="menu-item">
<text class="menu-label">阅读目标</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item">
<text class="menu-label">阅读报告</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item">
<text class="menu-label">设置</text>
<text class="menu-arrow">></text>
</view>
</view>
</view>

View File

@ -0,0 +1,70 @@
.profile {
min-height: 100vh;
background: #f8f8f8;
}
.user-info {
background: #fff;
padding: 40rpx;
display: flex;
align-items: center;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
}
.nickname {
margin-left: 30rpx;
font-size: 36rpx;
font-weight: bold;
}
.stats {
margin: 30rpx 0;
padding: 30rpx;
background: #fff;
display: flex;
justify-content: space-around;
}
.stats-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stats-num {
font-size: 40rpx;
font-weight: bold;
color: #1989fa;
}
.stats-label {
margin-top: 10rpx;
font-size: 24rpx;
color: #666;
}
.menu-list {
background: #fff;
}
.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
}
.menu-label {
font-size: 30rpx;
color: #333;
}
.menu-arrow {
color: #999;
}

View File

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@ -0,0 +1,66 @@
Page({
data: {
userInfo: {} as WechatMiniprogram.UserInfo,
todayReadingTime: 0,
totalBooks: 0,
totalNotes: 0,
totalBookmarks: 0,
totalReadingDays: 0
},
onLoad() {
this.loadUserInfo();
this.loadStatistics();
},
// 加载用户信息
loadUserInfo() {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
this.setData({ userInfo });
}
},
// 加载统计数据
loadStatistics() {
// 获取今日阅读时间
const today = new Date().toDateString();
const statistics = wx.getStorageSync('reading_statistics') || {};
const todayReadingTime = Math.floor((statistics[today] || 0) / 60); // 转换为分钟
// 获取笔记和书签数量
const notes = wx.getStorageSync('all_notes') || [];
const bookmarks = wx.getStorageSync('all_bookmarks') || [];
// 获取阅读过的书籍
const books = wx.getStorageSync('reading_history') || [];
// 获取总阅读天数
const readingDays = Object.keys(statistics).length;
this.setData({
todayReadingTime,
totalBooks: books.length,
totalNotes: notes.length,
totalBookmarks: bookmarks.length,
totalReadingDays: readingDays
});
},
// 页面跳转
navigateToNotes() {
wx.navigateTo({ url: '/pages/notes/notes' });
},
navigateToBookmarks() {
wx.navigateTo({ url: '/pages/notes/notes?tab=bookmarks' });
},
navigateToHistory() {
wx.navigateTo({ url: '/pages/history/history' });
},
navigateToSettings() {
wx.navigateTo({ url: '/pages/settings/settings' });
}
});

View File

@ -0,0 +1,60 @@
<view class="profile-page">
<!-- 用户信息 -->
<view class="user-info">
<image class="avatar" src="{{userInfo.avatarUrl || '/images/default-avatar.png'}}"></image>
<view class="info">
<text class="nickname">{{userInfo.nickName || '未登录'}}</text>
<text class="reading-time">今日阅读 {{todayReadingTime}}分钟</text>
</view>
</view>
<!-- 阅读统计 -->
<view class="stats-section">
<view class="stats-header">
<text>阅读统计</text>
<text class="more">查看更多</text>
</view>
<view class="stats-grid">
<view class="stats-item">
<text class="number">{{totalBooks}}</text>
<text class="label">阅读书籍</text>
</view>
<view class="stats-item">
<text class="number">{{totalNotes}}</text>
<text class="label">笔记数量</text>
</view>
<view class="stats-item">
<text class="number">{{totalBookmarks}}</text>
<text class="label">书签数量</text>
</view>
<view class="stats-item">
<text class="number">{{totalReadingDays}}</text>
<text class="label">阅读天数</text>
</view>
</view>
</view>
<!-- 功能列表 -->
<view class="feature-list">
<view class="feature-item" bindtap="navigateToNotes">
<text class="icon">📝</text>
<text>我的笔记</text>
<text class="arrow">></text>
</view>
<view class="feature-item" bindtap="navigateToBookmarks">
<text class="icon">🔖</text>
<text>我的书签</text>
<text class="arrow">></text>
</view>
<view class="feature-item" bindtap="navigateToHistory">
<text class="icon">📚</text>
<text>阅读历史</text>
<text class="arrow">></text>
</view>
<view class="feature-item" bindtap="navigateToSettings">
<text class="icon">⚙️</text>
<text>设置</text>
<text class="arrow">></text>
</view>
</view>
</view>

View File

@ -0,0 +1,118 @@
.profile-page {
min-height: 100vh;
background: #f8f8f8;
padding-bottom: env(safe-area-inset-bottom);
}
/* 用户信息 */
.user-info {
background: #fff;
padding: 40rpx;
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 30rpx;
}
.info {
flex: 1;
}
.nickname {
font-size: 32rpx;
font-weight: 500;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.reading-time {
font-size: 24rpx;
color: #999;
}
/* 阅读统计 */
.stats-section {
background: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.stats-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.stats-header text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.more {
font-size: 24rpx;
color: #999;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
}
.stats-item {
text-align: center;
}
.number {
font-size: 36rpx;
color: #333;
font-weight: 500;
display: block;
margin-bottom: 8rpx;
}
.label {
font-size: 24rpx;
color: #999;
}
/* 功能列表 */
.feature-list {
background: #fff;
}
.feature-item {
display: flex;
align-items: center;
padding: 30rpx 40rpx;
border-bottom: 1rpx solid #eee;
}
.feature-item:last-child {
border-bottom: none;
}
.icon {
font-size: 36rpx;
margin-right: 20rpx;
}
.feature-item text:nth-child(2) {
flex: 1;
font-size: 28rpx;
color: #333;
}
.arrow {
font-size: 24rpx;
color: #999;
}

View File

@ -0,0 +1,25 @@
这是一个示例TXT文档
阅读管理小程序使用说明:
1. 书籍管理
- 添加新书
- 更新阅读进度
- 书籍分类管理
2. 笔记功能
- 创建阅读笔记
- 笔记分类
- 笔记搜索
3. 统计功能
- 阅读时长统计
- 阅读页数统计
- 月度/年度报告
4. 使用建议
- 及时更新阅读进度
- 做好笔记整理
- 设定合理的阅读目标
祝您阅读愉快!

View File

@ -0,0 +1 @@
hahha

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 24 24" width="24" height="24">
<!-- SVG 路径内容 -->
</svg>

After

Width:  |  Height:  |  Size: 84 B

24
miniprogram/types/book.d.ts vendored Normal file
View File

@ -0,0 +1,24 @@
interface Book {
id: number;
isbn: string;
title: string;
author: string;
publisher: string;
description: string;
bookUrl: string;
coverUrl: string;
category: string;
tags: string;
language: string;
publishDate: string;
createdTime: string;
updatedTime: string;
}
interface BookListResponse {
records: Book[];
total: number;
size: number;
current: number;
pages: number;
}

View File

@ -0,0 +1,139 @@
interface RequestHeader {
'Content-Type': string;
'Authorization'?: string;
[key: string]: string | undefined;
}
interface RequestOptions extends WechatMiniprogram.RequestOption {
loading?: boolean;
}
interface ResponseData<T = any> {
code: number;
message: string;
data: T;
time?: string;
}
const baseURL = 'http://localhost:8084/api';
const request = {
async request<T>(options: RequestOptions): Promise<ResponseData<T>> {
const token = wx.getStorageSync('token');
console.log(token);
// 定义header类型
const header: RequestHeader = {
'Content-Type': 'application/json',
...options.header,
};
// 统一添加token
if (token) {
console.log('Token值:', token);
header['Authorization'] = `Bearer ${token}`;
console.log('完整Authorization:', header['Authorization']);
}
if (options.loading) {
wx.showLoading({
title: '加载中...',
mask: true
});
}
try {
console.log('发送请求的header:', header);
const res = await new Promise<WechatMiniprogram.RequestSuccessCallbackResult>((resolve, reject) => {
wx.request({
url: baseURL + options.url,
method: options.method,
data: options.data,
header,
success: (res) => {
console.log('请求响应:', res);
// 先检查状态码
if (res.statusCode === 401) {
// 清除本地token
wx.removeStorageSync('token');
wx.removeStorageSync('userInfo');
// 跳转到登录页
wx.redirectTo({
url: '/pages/login/index'
});
reject(new Error('登录已过期,请重新登录'));
return;
}
resolve(res);
},
fail: reject
});
});
const data = res.data as ResponseData<T>;
// 检查业务状态码
if (data.code !== 200) {
throw new Error(data.message || '请求失败');
}
return data;
} catch (error) {
console.error('请求错误:', error);
// 统一错误提示
wx.showToast({
title: error instanceof Error ? error.message : '请求失败',
icon: 'none'
});
throw error;
} finally {
if (options.loading) {
wx.hideLoading();
}
}
},
// GET请求
get<T>(url: string, data?: object, options: Partial<RequestOptions> = {}): Promise<ResponseData<T>> {
return this.request<T>({
method: 'GET',
url,
data,
...options
});
},
// POST请求
post<T>(url: string, data?: object, options: Partial<RequestOptions> = {}): Promise<ResponseData<T>> {
return this.request<T>({
method: 'POST',
url,
data,
...options
});
},
// PUT请求
put<T>(url: string, data?: object, options: Partial<RequestOptions> = {}): Promise<ResponseData<T>> {
return this.request<T>({
method: 'PUT',
url,
data,
...options
});
},
// DELETE请求
delete<T>(url: string, data?: object, options: Partial<RequestOptions> = {}): Promise<ResponseData<T>> {
return this.request<T>({
method: 'DELETE',
url,
data,
...options
});
}
};
export default request;

19
miniprogram/utils/util.ts Normal file
View File

@ -0,0 +1,19 @@
export const formatTime = (date: Date) => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return (
[year, month, day].map(formatNumber).join('/') +
' ' +
[hour, minute, second].map(formatNumber).join(':')
)
}
const formatNumber = (n: number) => {
const s = n.toString()
return s[1] ? s : '0' + s
}

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "miniprogram-ts-quickstart",
"version": "1.0.0",
"description": "",
"scripts": {
},
"keywords": [],
"author": "",
"license": "",
"dependencies": {
},
"devDependencies": {
"miniprogram-api-typings": "^2.8.3-1"
}
}

56
project.config.json Normal file
View File

@ -0,0 +1,56 @@
{
"description": "项目配置文件",
"miniprogramRoot": "miniprogram/",
"cloudfunctionRoot": "cloudfunctions/",
"compileType": "miniprogram",
"setting": {
"useCompilerPlugins": [
"typescript"
],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"coverView": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmRelationList": [],
"ignoreUploadUnusedFiles": true,
"compileHotReLoad": false,
"skylineRenderEnable": true,
"urlCheck": true,
"es6": true,
"preloadBackgroundData": false,
"newFeature": true,
"nodeModules": false,
"autoAudits": false,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"lazyloadPlaceholderEnable": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true
},
"simulatorType": "wechat",
"simulatorPluginLibVersion": {},
"condition": {},
"srcMiniprogramRoot": "miniprogram/",
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
},
"packOptions": {
"ignore": [],
"include": []
},
"libVersion": "3.0.0",
"appid": "wx2aeaa9db5139384c",
"projectname": "reading-manager",
"cloudfunctionTemplateRoot": "cloudfunctionTemplate/"
}

View File

@ -0,0 +1,8 @@
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "miniprogram-1",
"setting": {
"compileHotReLoad": true,
"urlCheck": false
}
}

30
tsconfig.json Normal file
View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"strictNullChecks": true,
"noImplicitAny": true,
"module": "CommonJS",
"target": "ES2020",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
"strictPropertyInitialization": true,
"lib": ["ES2020"],
"typeRoots": [
"./typings"
]
},
"include": [
"./**/*.ts"
],
"exclude": [
"node_modules"
]
}

17
types/book.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
interface Book {
id: string;
title: string;
author: string;
cover: string;
isbn: string;
totalPages: number;
currentPage: number;
status: 'reading' | 'finished' | 'planning';
startDate?: Date;
finishDate?: Date;
category: string[];
notes: string[];
rating?: number;
createTime: Date;
updateTime: Date;
}

10
types/note.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
interface Note {
id: string;
bookId: string;
title: string;
content: string;
tags: string[];
page?: number;
createTime: Date;
updateTime: Date;
}

15
types/reading-stats.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
interface ReadingStats {
userId: string;
dailyReadingTime: number; // 单位:分钟
dailyPages: number;
monthlyStats: {
month: string; // YYYY-MM
totalTime: number;
totalPages: number;
finishedBooks: number;
}[];
yearlyGoal: {
books: number;
pages: number;
};
}

8
typings/index.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
/// <reference path="./types/index.d.ts" />
interface IAppOption {
globalData: {
userInfo?: WechatMiniprogram.UserInfo,
}
userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback,
}

1
typings/types/index.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference path="./wx/index.d.ts" />

163
typings/types/wx/index.d.ts vendored Normal file
View File

@ -0,0 +1,163 @@
/*! *****************************************************************************
Copyright (c) 2024 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
/// <reference path="./lib.wx.app.d.ts" />
/// <reference path="./lib.wx.page.d.ts" />
/// <reference path="./lib.wx.api.d.ts" />
/// <reference path="./lib.wx.cloud.d.ts" />
/// <reference path="./lib.wx.canvas.d.ts" />
/// <reference path="./lib.wx.component.d.ts" />
/// <reference path="./lib.wx.behavior.d.ts" />
/// <reference path="./lib.wx.event.d.ts" />
/// <reference path="./lib.wx.wasm.d.ts" />
declare namespace WechatMiniprogram {
type IAnyObject = Record<string, any>
type Optional<F> = F extends (arg: infer P) => infer R ? (arg?: P) => R : F
type OptionalInterface<T> = { [K in keyof T]: Optional<T[K]> }
interface AsyncMethodOptionLike {
success?: (...args: any[]) => void
}
type PromisifySuccessResult<
P,
T extends AsyncMethodOptionLike
> = P extends {
success: any
}
? void
: P extends { fail: any }
? void
: P extends { complete: any }
? void
: Promise<Parameters<Exclude<T['success'], undefined>>[0]>
// TODO: Extract real definition from `lib.dom.d.ts` to replace this
type IIRFilterNode = any
type WaveShaperNode = any
type ConstantSourceNode = any
type OscillatorNode = any
type GainNode = any
type BiquadFilterNode = any
type PeriodicWaveNode = any
type AudioNode = any
type ChannelSplitterNode = any
type ChannelMergerNode = any
type DelayNode = any
type DynamicsCompressorNode = any
type ScriptProcessorNode = any
type PannerNode = any
type AnalyserNode = any
type WebGLTexture = any
type WebGLRenderingContext = any
// TODO: fill worklet type
type WorkletFunction = (...args: any) => any
type AnimationObject = any
type SharedValue<T = any> = T
type DerivedValue<T = any> = T
}
declare let console: WechatMiniprogram.Console
declare let wx: WechatMiniprogram.Wx
/** 引入模块。返回模块通过 `module.exports` 或 `exports` 暴露的接口。 */
interface Require {
(
/** 需要引入模块文件相对于当前文件的相对路径,或 npm 模块名,或 npm 模块路径。不支持绝对路径 */
module: string,
/** 用于异步获取其他分包中的模块的引用结果,详见 [分包异步化]((subpackages/async)) */
callback?: (moduleExport: any) => void,
/** 异步获取分包失败时的回调,详见 [分包异步化]((subpackages/async)) */
errorCallback?: (err: any) => void
): any
/** 以 Promise 形式异步引入模块。返回模块通过 `module.exports` 或 `exports` 暴露的接口。 */
async(
/** 需要引入模块文件相对于当前文件的相对路径,或 npm 模块名,或 npm 模块路径。不支持绝对路径 */
module: string
): Promise<any>
}
declare const require: Require
/** 引入插件。返回插件通过 `main` 暴露的接口。 */
interface RequirePlugin {
(
/** 需要引入的插件的 alias */
module: string,
/** 用于异步获取其他分包中的插件的引用结果,详见 [分包异步化]((subpackages/async)) */
callback?: (pluginExport: any) => void
): any
/** 以 Promise 形式异步引入插件。返回插件通过 `main` 暴露的接口。 */
async(
/** 需要引入的插件的 alias */
module: string
): Promise<any>
}
declare const requirePlugin: RequirePlugin
/** 插件引入当前使用者小程序。返回使用者小程序通过 [插件配置中 `export` 暴露的接口](https://developers.weixin.qq.com/miniprogram/dev/framework/plugin/using.html#%E5%AF%BC%E5%87%BA%E5%88%B0%E6%8F%92%E4%BB%B6)
*
*
*
* `2.11.1` */
declare function requireMiniProgram(): any
/** 当前模块对象 */
declare let module: {
/** 模块向外暴露的对象,使用 `require` 引用该模块时可以获取 */
exports: any
}
/** `module.exports` 的引用 */
declare let exports: any
/** [clearInterval(number intervalID)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/clearInterval.html)
*
* setInterval */
declare function clearInterval(
/** 要取消的定时器的 ID */
intervalID: number
): void
/** [clearTimeout(number timeoutID)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/clearTimeout.html)
*
* setTimeout */
declare function clearTimeout(
/** 要取消的定时器的 ID */
timeoutID: number
): void
/** [number setInterval(function callback, number delay, any rest)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/setInterval.html)
*
* */
declare function setInterval(
/** 回调函数 */
callback: (...args: any[]) => any,
/** 执行回调函数之间的时间间隔,单位 ms。 */
delay?: number,
/** param1, param2, ..., paramN 等附加参数,它们会作为参数传递给回调函数。 */
rest?: any
): number
/** [number setTimeout(function callback, number delay, any rest)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/setTimeout.html)
*
* */
declare function setTimeout(
/** 回调函数 */
callback: (...args: any[]) => any,
/** 延迟的时间,函数的调用会在该延迟之后发生,单位 ms。 */
delay?: number,
/** param1, param2, ..., paramN 等附加参数,它们会作为参数传递给回调函数。 */
rest?: any
): number

33751
typings/types/wx/lib.wx.api.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

424
typings/types/wx/lib.wx.app.d.ts vendored Normal file
View File

@ -0,0 +1,424 @@
/*! *****************************************************************************
Copyright (c) 2024 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
declare namespace WechatMiniprogram.App {
type SceneValues =
| 1000
| 1001
| 1005
| 1006
| 1007
| 1008
| 1010
| 1011
| 1012
| 1013
| 1014
| 1017
| 1019
| 1020
| 1022
| 1023
| 1024
| 1025
| 1026
| 1027
| 1028
| 1029
| 1030
| 1031
| 1032
| 1034
| 1035
| 1036
| 1037
| 1038
| 1039
| 1042
| 1043
| 1044
| 1045
| 1046
| 1047
| 1048
| 1049
| 1052
| 1053
| 1054
| 1056
| 1057
| 1058
| 1059
| 1060
| 1064
| 1065
| 1067
| 1068
| 1069
| 1071
| 1072
| 1073
| 1074
| 1077
| 1078
| 1079
| 1081
| 1082
| 1084
| 1088
| 1089
| 1090
| 1091
| 1092
| 1095
| 1096
| 1097
| 1099
| 1100
| 1101
| 1102
| 1103
| 1104
| 1106
| 1107
| 1113
| 1114
| 1119
| 1120
| 1121
| 1124
| 1125
| 1126
| 1129
| 1131
| 1133
| 1135
| 1144
| 1145
| 1146
| 1148
| 1150
| 1151
| 1152
| 1153
| 1154
| 1155
| 1157
| 1158
| 1160
| 1167
| 1168
| 1169
| 1171
| 1173
| 1175
| 1176
| 1177
| 1178
| 1179
| 1181
| 1183
| 1184
| 1185
| 1186
| 1187
| 1189
| 1191
| 1192
| 1193
| 1194
| 1195
| 1196
| 1197
| 1198
| 1200
| 1201
| 1202
| 1203
| 1206
| 1207
| 1208
| 1212
| 1215
| 1216
| 1223
| 1228
| 1231
interface LaunchShowOption {
/** 打开小程序的路径 */
path: string
/** 打开小程序的query */
query: IAnyObject
/**
* - 1000
* - 1001使 2.2.4
* - 1005
* - 1006
* - 1007
* - 1008
* - 1010
* - 1011
* - 1012
* - 1013
* - 1014 1107
* - 1017
* - 1019 7.0.0
* - 1020 profile
* - 1022 6.6.1
* - 1023
* - 1024 profile
* - 1025
* - 1026
* - 1027使
* - 1028
* - 1029
* - 1030
* - 1031
* - 1032
* - 1034
* - 1035
* - 1036App
* - 1037
* - 1038
* - 1039
* - 1042
* - 1043
* - 1044 shareTicket [](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html)
* - 1045广
* - 1046广
* - 1047
* - 1048
* - 1049
* - 1052
* - 1053
* - 1054 6.7.4
* - 1056
* - 1057
* - 1058
* - 1059
* - 1060 1034
* - 1064 Wi-Fi
* - 1065URL scheme [](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html)
* - 1067广
* - 1068广
* - 1069 openSDK
* - 1071
* - 1072
* - 1073
* - 1074
* - 1077
* - 1078 Wi-Fi
* - 1079
* - 1081
* - 1082
* - 1084广
* - 1088
* - 1089使 2.2.4
* - 1090使
* - 1091
* - 1092
* - 1095广
* - 1096
* - 1097
* - 1099
* - 1100
* - 1101 -> ->
* - 1102 profile
* - 1103 2.2.4
* - 1104 2.2.4
* - 1106
* - 1107
* - 1113
* - 1114
* - 1119
* - 1120
* - 1121
* - 1124
* - 1125
* - 1126
* - 1129访 [](https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/sitemap.html)
* - 11318.0
* - 1133 [](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/)
* - 1135 profile
* - 1144 -
* - 1145 -
* - 1146
* - 1148-
* - 1150
* - 1151 -
* - 1152
* - 1153
* - 1154
* - 1155
* - 1157
* - 1158
* - 1160
* - 1167H5 [](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html)
* - 1168
* - 1169
* - 1171
* - 1173 [](https://developers.weixin.qq.com/miniprogram/dev/framework/material/support_material.html)
* - 1175
* - 1176
* - 1177
* - 1178
* - 1179#
* - 1181 PC
* - 1183PC - - -
* - 1184
* - 1185
* - 1186 -
* - 11878.0
* - 1189广
* - 1191
* - 1192 profile
* - 1193
* - 1194URL Link [](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-link.html)
* - 1195 tab
* - 1196
* - 1197
* - 1198
* - 1200广
* - 1201广
* - 1202
* - 1203
* - 1206
* - 1207
* - 1208
* - 1212
* - 1215广
* - 1216
* - 1223 Widget
* - 1228广
* - 1231
*/
scene: SceneValues
/** shareTicket详见 [获取更多转发信息]((转发#获取更多转发信息)) */
shareTicket: string
/** 当场景为由从另一个小程序或公众号或App打开时返回此字段 */
referrerInfo?: ReferrerInfo
/** 打开的文件信息数组只有从聊天素材场景打开scene为1173才会携带该参数 */
forwardMaterials: ForwardMaterials[]
/** /chatType /
*
*
* - 1: 微信联系人单聊;
* - 2: 企业微信联系人单聊;
* - 3: 普通微信群聊;
* - 4: 企业微信互通群聊; */
chatType?: 1 | 2 | 3 | 4
/** `2.20.0`
*
* API
*
*
* - 'default': ;
* - 'nativeFunctionalized': ;
* - 'browseOnly': ;
* - 'embedded': ; */
apiCategory:
| 'default'
| 'nativeFunctionalized'
| 'browseOnly'
| 'embedded'
}
interface PageNotFoundOption {
/** 不存在页面的路径 */
path: string
/** 打开不存在页面的 query */
query: IAnyObject
/** 是否本次启动的首个页面(例如从分享等入口进来,首个页面是开发者配置的分享页面) */
isEntryPage: boolean
}
interface Option {
/**
*
*
*/
onLaunch(options: LaunchShowOption): void
/**
*
*
*/
onShow(options: LaunchShowOption): void
/**
*
*
*/
onHide(): void
/**
*
* api
*/
onError(/** 错误信息,包含堆栈 */ error: string): void
/**
*
*
*
* ****
* 1. `onPageNotFound`
* 2. `onPageNotFound` `onPageNotFound`
*
* 1.9.90
*/
onPageNotFound(options: PageNotFoundOption): void
/**
* Promise 使 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 绑定监听。注意事项请参考 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html)。
* **** [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 一致
*/
onUnhandledRejection: OnUnhandledRejectionCallback
/**
* 使 wx.onThemeChange
*
* 2.11.0
*/
onThemeChange: OnThemeChangeCallback
}
type Instance<T extends IAnyObject> = Option & T
type Options<T extends IAnyObject> = Partial<Option> &
T &
ThisType<Instance<T>>
type TrivialInstance = Instance<IAnyObject>
interface Constructor {
<T extends IAnyObject>(options: Options<T>): void
}
interface GetAppOption {
/** `App` AppApp
*
* 2.2.4
*/
allowDefault?: boolean
}
interface GetApp {
<T extends IAnyObject = IAnyObject>(opts?: GetAppOption): Instance<T>
}
}
declare let App: WechatMiniprogram.App.Constructor
declare let getApp: WechatMiniprogram.App.GetApp

85
typings/types/wx/lib.wx.behavior.d.ts vendored Normal file
View File

@ -0,0 +1,85 @@
/*! *****************************************************************************
Copyright (c) 2024 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
declare namespace WechatMiniprogram.Behavior {
type BehaviorIdentifier<
TData extends DataOption = {},
TProperty extends PropertyOption = {},
TMethod extends MethodOption = {},
TBehavior extends BehaviorOption = []
> = string & {
[key in 'BehaviorType']?: {
data: Component.FilterUnknownType<TData> & Component.MixinData<TBehavior>
properties: Component.FilterUnknownType<TProperty> & Component.MixinProperties<TBehavior, true>
methods: Component.FilterUnknownType<TMethod> & Component.MixinMethods<TBehavior>
}
}
type Instance<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TBehavior extends BehaviorOption,
TCustomInstanceProperty extends IAnyObject = Record<string, never>
> = Component.Instance<TData, TProperty, TMethod, TBehavior, TCustomInstanceProperty>
type TrivialInstance = Instance<IAnyObject, IAnyObject, IAnyObject, Component.IEmptyArray>
type TrivialOption = Options<IAnyObject, IAnyObject, IAnyObject, Component.IEmptyArray>
type Options<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TBehavior extends BehaviorOption,
TCustomInstanceProperty extends IAnyObject = Record<string, never>
> = Partial<Data<TData>> &
Partial<Property<TProperty>> &
Partial<Method<TMethod>> &
Partial<Behavior<TBehavior>> &
Partial<OtherOption> &
Partial<Lifetimes> &
ThisType<Instance<TData, TProperty, TMethod, TBehavior, TCustomInstanceProperty>>
interface Constructor {
<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TBehavior extends BehaviorOption,
TCustomInstanceProperty extends IAnyObject = Record<string, never>
>(
options: Options<TData, TProperty, TMethod, TBehavior, TCustomInstanceProperty>
): BehaviorIdentifier<TData, TProperty, TMethod, TBehavior>
}
type DataOption = Component.DataOption
type PropertyOption = Component.PropertyOption
type MethodOption = Component.MethodOption
type BehaviorOption = Component.BehaviorOption
type Data<D extends DataOption> = Component.Data<D>
type Property<P extends PropertyOption> = Component.Property<P>
type Method<M extends MethodOption> = Component.Method<M>
type Behavior<B extends BehaviorOption> = Component.Behavior<B>
type DefinitionFilter = Component.DefinitionFilter
type Lifetimes = Component.Lifetimes
type OtherOption = Omit<Component.OtherOption, 'options'>
}
/** 注册一个 `behavior`,接受一个 `Object` 类型的参数。*/
declare let Behavior: WechatMiniprogram.Behavior.Constructor

2623
typings/types/wx/lib.wx.canvas.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

1016
typings/types/wx/lib.wx.cloud.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

771
typings/types/wx/lib.wx.component.d.ts vendored Normal file
View File

@ -0,0 +1,771 @@
/*! *****************************************************************************
Copyright (c) 2024 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
declare namespace WechatMiniprogram.Component {
type FilterUnknownType<T> = string extends keyof T ? {} : T
type Instance<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends Partial<MethodOption>,
TBehavior extends BehaviorOption,
TCustomInstanceProperty extends IAnyObject = {},
TIsPage extends boolean = false
> = InstanceProperties &
InstanceMethods<TData> &
TMethod &
MixinMethods<TBehavior> &
(TIsPage extends true ? Page.ILifetime : {}) &
Omit<TCustomInstanceProperty, 'properties' | 'methods' | 'data'> & {
/** 组件数据,**包括内部数据和属性值** */
data: FilterUnknownType<TData> & MixinData<TBehavior> &
MixinProperties<TBehavior> & PropertyOptionToData<FilterUnknownType<TProperty>>
/** 组件数据,**包括内部数据和属性值**(与 `data` 一致) */
properties: FilterUnknownType<TData> & MixinData<TBehavior> &
MixinProperties<TBehavior> & PropertyOptionToData<FilterUnknownType<TProperty>>
}
type IEmptyArray = []
type TrivialInstance = Instance<
IAnyObject,
IAnyObject,
IAnyObject,
IEmptyArray,
IAnyObject
>
type TrivialOption = Options<IAnyObject, IAnyObject, IAnyObject, IEmptyArray, IAnyObject>
type Options<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TBehavior extends BehaviorOption,
TCustomInstanceProperty extends IAnyObject = {},
TIsPage extends boolean = false
> = Partial<Data<TData>> &
Partial<Property<TProperty>> &
Partial<Method<TMethod, TIsPage>> &
Partial<Behavior<TBehavior>> &
Partial<OtherOption> &
Partial<Lifetimes> &
ThisType<
Instance<
TData,
TProperty,
TMethod,
TBehavior,
TCustomInstanceProperty,
TIsPage
>
>
interface Constructor {
<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TBehavior extends BehaviorOption,
TCustomInstanceProperty extends IAnyObject = {},
TIsPage extends boolean = false
>(
options: Options<
TData,
TProperty,
TMethod,
TBehavior,
TCustomInstanceProperty,
TIsPage
>
): string
}
type DataOption = Record<string, any>
type PropertyOption = Record<string, AllProperty>
type MethodOption = Record<string, Function>
type BehaviorOption = Behavior.BehaviorIdentifier[]
type ExtractBehaviorType<T> = T extends { BehaviorType?: infer B } ? B : never
type ExtractData<T> = T extends { data: infer D } ? D : never
type ExtractProperties<T, TIsBehavior extends boolean = false> = T extends { properties: infer P } ?
TIsBehavior extends true ? P : PropertyOptionToData<P extends PropertyOption ? P : {}> : never
type ExtractMethods<T> = T extends { methods: infer M } ? M : never
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never
type MixinData<T extends any[]> = UnionToIntersection<ExtractData<ExtractBehaviorType<T[number]>>>
type MixinProperties<T extends any[], TIsBehavior extends boolean = false> = UnionToIntersection<ExtractProperties<ExtractBehaviorType<T[number]>, TIsBehavior>>
type MixinMethods<T extends any[]> = UnionToIntersection<ExtractMethods<ExtractBehaviorType<T[number]>>>
interface Behavior<B extends BehaviorOption> {
/** 类似于mixins和traits的组件间代码复用机制参见 [behaviors](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html) */
behaviors?: B
}
interface Data<D extends DataOption> {
/** 组件的内部数据,和 `properties` 一同用于组件的模板渲染 */
data?: D
}
interface Property<P extends PropertyOption> {
/** 组件的对外属性,是属性名到属性设置的映射表 */
properties: P
}
interface Method<M extends MethodOption, TIsPage extends boolean = false> {
/** 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见 [组件间通信与事件](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html) */
methods: M & (TIsPage extends true ? Partial<Page.ILifetime> : {})
}
type PropertyType =
| StringConstructor
| NumberConstructor
| BooleanConstructor
| ArrayConstructor
| ObjectConstructor
| null
type ValueType<T extends PropertyType> = T extends null
? any
: T extends StringConstructor
? string
: T extends NumberConstructor
? number
: T extends BooleanConstructor
? boolean
: T extends ArrayConstructor
? any[]
: T extends ObjectConstructor
? IAnyObject
: never
type FullProperty<T extends PropertyType> = {
/** 属性类型 */
type: T
/** 属性初始值 */
value?: ValueType<T>
/** 属性值被更改时的响应函数 */
observer?:
| string
| ((
newVal: ValueType<T>,
oldVal: ValueType<T>,
changedPath: Array<string | number>
) => void)
/** 属性的类型(可以指定多个) */
optionalTypes?: ShortProperty[]
}
type AllFullProperty =
| FullProperty<StringConstructor>
| FullProperty<NumberConstructor>
| FullProperty<BooleanConstructor>
| FullProperty<ArrayConstructor>
| FullProperty<ObjectConstructor>
| FullProperty<null>
type ShortProperty =
| StringConstructor
| NumberConstructor
| BooleanConstructor
| ArrayConstructor
| ObjectConstructor
| null
type AllProperty = AllFullProperty | ShortProperty
type PropertyToData<T extends AllProperty> = T extends ShortProperty
? ValueType<T>
: FullPropertyToData<Exclude<T, ShortProperty>>
type ArrayOrObject = ArrayConstructor | ObjectConstructor
type FullPropertyToData<T extends AllFullProperty> = T['type'] extends ArrayOrObject ? unknown extends T['value'] ? ValueType<T['type']> : T['value'] : ValueType<T['type']>
type PropertyOptionToData<P extends PropertyOption> = {
[name in keyof P]: PropertyToData<P[name]>
}
interface Router {
switchTab: Wx['switchTab']
reLaunch: Wx['reLaunch']
redirectTo: Wx['redirectTo']
navigateTo: Wx['navigateTo']
navigateBack: Wx['navigateBack']
}
interface InstanceProperties {
/** 组件的文件路径 */
is: string
/** 节点id */
id: string
/** 节点dataset */
dataset: Record<string, string>
/** 上一次退出前 onSaveExitState 保存的数据 */
exitState: any
/** 相对于当前自定义组件的 Router 对象 */
router: Router
/** 相对于当前自定义组件所在页面的 Router 对象 */
pageRouter: Router
/** 渲染当前组件的渲染后端 */
renderer: 'webview' | 'skyline'
}
interface InstanceMethods<D extends DataOption> {
/** `setData`
* `this.data`
*
* ****
*
* 1. ** this.data this.setData **
* 1. JSON
* 1. 1024kB
* 1. data value `undefined`
*/
setData(
/**
*
* `key: value` `this.data` `key` `value`
*
* `key` `array[2].message``a.b.c.d` this.data
*/
data: Partial<D> & IAnyObject,
/** setData引起的界面更新渲染完毕后的回调函数最低基础库 `1.5.0` */
callback?: () => void
): void
/** 检查组件是否具有 `behavior` 检查时会递归检查被直接或间接引入的所有behavior */
hasBehavior(behavior: Behavior.BehaviorIdentifier): void
/** 触发事件,参见组件事件 */
triggerEvent<DetailType = any>(
name: string,
detail?: DetailType,
options?: TriggerEventOption
): void
/** 创建一个 SelectorQuery 对象,选择器选取范围为这个组件实例内 */
createSelectorQuery(): SelectorQuery
/** 创建一个 IntersectionObserver 对象,选择器选取范围为这个组件实例内 */
createIntersectionObserver(
options: CreateIntersectionObserverOption
): IntersectionObserver
/** 创建一个 MediaQueryObserver 对象 */
createMediaQueryObserver(): MediaQueryObserver
/** 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象(会被 `wx://component-export` 影响) */
selectComponent(selector: string): TrivialInstance
/** 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组 */
selectAllComponents(selector: string): TrivialInstance[]
/**
* `wx://component-export`
*
* [`2.8.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
selectOwnerComponent(): TrivialInstance
/** 获取这个关系所对应的所有关联节点,参见 组件间关系 */
getRelationNodes(relationKey: string): TrivialInstance[]
/**
* callback setData setData
*
* [`2.4.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
groupSetData(callback?: () => void): void
/**
* custom-tab-bar
*
* [`2.6.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
getTabBar(): TrivialInstance
/**
*
*
* [`2.7.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
getPageId(): string
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
animate(
selector: string,
keyFrames: KeyFrame[],
duration: number,
callback?: () => void
): void
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
animate(
selector: string,
keyFrames: ScrollTimelineKeyframe[],
duration: number,
scrollTimeline: ScrollTimelineOption
): void
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
clearAnimation(selector: string, callback: () => void): void
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
clearAnimation(
selector: string,
options?: ClearAnimationOptions,
callback?: () => void
): void
/**
* [wx.navigateTo]((wx.navigateTo))
*
* [`2.7.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
getOpenerEventChannel(): EventChannel
/**
* worklet [worklet ](https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/worklet.html)
*
* [`2.29.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
applyAnimatedStyle(
selector: string,
updater: () => Record<string, string>,
userConfig?: { immediate: boolean, flush: 'sync' | 'async' },
callback?: (res: { styleId: number }) => void
): void
/**
* worklet
*
* [`2.30.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
clearAnimatedStyle(
selector: string,
styleIds: number[],
callback?: () => void
): void
/**
* []((custom-component/update-perf-stat))
*
*
* [`2.12.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
setUpdatePerformanceListener<WithDataPath extends boolean = false>(
options: SetUpdatePerformanceListenerOption<WithDataPath>,
callback?: UpdatePerformanceListener<WithDataPath>
): void
/**
* `touch` passive [enablePassiveEvent]((configuration/app#enablePassiveEvent))
*
* [`2.25.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
getPassiveEvent(callback: (config: PassiveConfig) => void): void
/**
* `touch` passive [enablePassiveEvent]((configuration/app#enablePassiveEvent))
*
* [`2.25.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
setPassiveEvent(config: PassiveConfig): void
}
interface ComponentOptions {
/**
* [slot支持](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件wxml的slot)
*/
multipleSlots?: boolean
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件样式隔离)
*/
addGlobalClass?: boolean
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件样式隔离)
*/
styleIsolation?:
| 'isolated'
| 'apply-shared'
| 'shared'
| 'page-isolated'
| 'page-apply-shared'
| 'page-shared'
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/pure-data.html) 是一些不用于界面渲染的 data 字段,可以用于提升页面更新性能。从小程序基础库版本 2.8.2 开始支持。
*/
pureDataPattern?: RegExp
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E8%99%9A%E6%8B%9F%E5%8C%96%E7%BB%84%E4%BB%B6%E8%8A%82%E7%82%B9) 使自定义组件内部的第一层节点由自定义组件本身完全决定。从小程序基础库版本 [`2.11.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) 开始支持 */
virtualHost?: boolean
}
interface TriggerEventOption {
/**
*
* `false`
*/
bubbles?: boolean
/** 穿false
*
* `false`
*/
composed?: boolean
/**
*
* `false`
*/
capturePhase?: boolean
}
interface RelationOption {
/** 目标组件的相对关系 */
type: 'parent' | 'child' | 'ancestor' | 'descendant'
/** 关系生命周期函数当关系被建立在页面节点树中时触发触发时机在组件attached生命周期之后 */
linked?(target: TrivialInstance): void
/** 关系生命周期函数当关系在页面节点树中发生改变时触发触发时机在组件moved生命周期之后 */
linkChanged?(target: TrivialInstance): void
/** 关系生命周期函数当关系脱离页面节点树时触发触发时机在组件detached生命周期之后 */
unlinked?(target: TrivialInstance): void
/** 如果这一项被设置则它表示关联的目标节点所应具有的behavior所有拥有这一behavior的组件节点都会被关联 */
target?: string
}
interface PageLifetimes {
/**
*
* /
*/
show(): void
/**
*
* / `navigateTo` `tab`
*/
hide(): void
/**
*
*
*/
resize(size: Page.IResizeOption): void
/**
*
*
*/
routeDone(): void
}
type DefinitionFilter = <T extends TrivialOption>(
/** 使用该 behavior 的 component/behavior 的定义对象 */
defFields: T,
/** 该 behavior 所使用的 behavior 的 definitionFilter 函数列表 */
definitionFilterArr?: DefinitionFilter[]
) => void
interface Lifetimes {
/** `created``attached``ready``moved``detached` `lifetimes` `lifetimes`
*
* `2.2.3` */
lifetimes: Partial<{
/**
* `setData`
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
created(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
attached(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
ready(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
moved(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
detached(): void
/**
*
*
* [`2.4.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
error(err: Error): void
}>
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
created(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
attached(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
ready(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
moved(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
detached(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`2.4.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
error(err: Error): void
}
interface OtherOption {
/**
* properties data [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/observer.html)
*
* [`2.6.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
observers: Record<string, (...args: any[]) => any>
/** 组件间关系定义,参见 [组件间关系](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html) */
relations: {
[componentName: string]: RelationOption
}
/** 组件接受的外部样式类,参见 [外部样式类](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html) */
externalClasses?: string[]
/** 组件所在页面的生命周期声明对象,参见 [组件生命周期](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html)
*
* [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
pageLifetimes?: Partial<PageLifetimes>
/** 一些选项(文档中介绍相关特性时会涉及具体的选项设置,这里暂不列举) */
options: ComponentOptions
/** 定义段过滤器,用于自定义组件扩展,参见 [自定义组件扩展](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/extend.html)
*
* [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
definitionFilter?: DefinitionFilter
/**
* 使 `behavior: wx://component-export` selectComponent [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html)
* [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
export: () => IAnyObject
}
interface KeyFrame {
/** 关键帧的偏移,范围[0-1] */
offset?: number
/** 动画缓动函数 */
ease?: string
/** 基点位置,即 CSS transform-origin */
transformOrigin?: string
/** 背景颜色,即 CSS background-color */
backgroundColor?: string
/** 底边位置,即 CSS bottom */
bottom?: number | string
/** 高度,即 CSS height */
height?: number | string
/** 左边位置,即 CSS left */
left?: number | string
/** 宽度,即 CSS width */
width?: number | string
/** 不透明度,即 CSS opacity */
opacity?: number | string
/** 右边位置,即 CSS right */
right?: number | string
/** 顶边位置,即 CSS top */
top?: number | string
/** 变换矩阵,即 CSS transform matrix */
matrix?: number[]
/** 三维变换矩阵,即 CSS transform matrix3d */
matrix3d?: number[]
/** 旋转,即 CSS transform rotate */
rotate?: number
/** 三维旋转,即 CSS transform rotate3d */
rotate3d?: number[]
/** X 方向旋转,即 CSS transform rotateX */
rotateX?: number
/** Y 方向旋转,即 CSS transform rotateY */
rotateY?: number
/** Z 方向旋转,即 CSS transform rotateZ */
rotateZ?: number
/** 缩放,即 CSS transform scale */
scale?: number[]
/** 三维缩放,即 CSS transform scale3d */
scale3d?: number[]
/** X 方向缩放,即 CSS transform scaleX */
scaleX?: number
/** Y 方向缩放,即 CSS transform scaleY */
scaleY?: number
/** Z 方向缩放,即 CSS transform scaleZ */
scaleZ?: number
/** 倾斜,即 CSS transform skew */
skew?: number[]
/** X 方向倾斜,即 CSS transform skewX */
skewX?: number
/** Y 方向倾斜,即 CSS transform skewY */
skewY?: number
/** 位移,即 CSS transform translate */
translate?: Array<number | string>
/** 三维位移,即 CSS transform translate3d */
translate3d?: Array<number | string>
/** X 方向位移,即 CSS transform translateX */
translateX?: number | string
/** Y 方向位移,即 CSS transform translateY */
translateY?: number | string
/** Z 方向位移,即 CSS transform translateZ */
translateZ?: number | string
}
interface ClearAnimationOptions {
/** 基点位置,即 CSS transform-origin */
transformOrigin?: boolean
/** 背景颜色,即 CSS background-color */
backgroundColor?: boolean
/** 底边位置,即 CSS bottom */
bottom?: boolean
/** 高度,即 CSS height */
height?: boolean
/** 左边位置,即 CSS left */
left?: boolean
/** 宽度,即 CSS width */
width?: boolean
/** 不透明度,即 CSS opacity */
opacity?: boolean
/** 右边位置,即 CSS right */
right?: boolean
/** 顶边位置,即 CSS top */
top?: boolean
/** 变换矩阵,即 CSS transform matrix */
matrix?: boolean
/** 三维变换矩阵,即 CSS transform matrix3d */
matrix3d?: boolean
/** 旋转,即 CSS transform rotate */
rotate?: boolean
/** 三维旋转,即 CSS transform rotate3d */
rotate3d?: boolean
/** X 方向旋转,即 CSS transform rotateX */
rotateX?: boolean
/** Y 方向旋转,即 CSS transform rotateY */
rotateY?: boolean
/** Z 方向旋转,即 CSS transform rotateZ */
rotateZ?: boolean
/** 缩放,即 CSS transform scale */
scale?: boolean
/** 三维缩放,即 CSS transform scale3d */
scale3d?: boolean
/** X 方向缩放,即 CSS transform scaleX */
scaleX?: boolean
/** Y 方向缩放,即 CSS transform scaleY */
scaleY?: boolean
/** Z 方向缩放,即 CSS transform scaleZ */
scaleZ?: boolean
/** 倾斜,即 CSS transform skew */
skew?: boolean
/** X 方向倾斜,即 CSS transform skewX */
skewX?: boolean
/** Y 方向倾斜,即 CSS transform skewY */
skewY?: boolean
/** 位移,即 CSS transform translate */
translate?: boolean
/** 三维位移,即 CSS transform translate3d */
translate3d?: boolean
/** X 方向位移,即 CSS transform translateX */
translateX?: boolean
/** Y 方向位移,即 CSS transform translateY */
translateY?: boolean
/** Z 方向位移,即 CSS transform translateZ */
translateZ?: boolean
}
interface ScrollTimelineKeyframe {
composite?: 'replace' | 'add' | 'accumulate' | 'auto'
easing?: string
offset?: number | null
[property: string]: string | number | null | undefined
}
interface ScrollTimelineOption {
/** 指定滚动元素的选择器(只支持 scroll-view该元素滚动时会驱动动画的进度 */
scrollSource: string
/** 指定滚动的方向。有效值为 horizontal 或 vertical */
orientation?: string
/** 指定开始驱动动画进度的滚动偏移量,单位 px */
startScrollOffset: number
/** 指定停止驱动动画进度的滚动偏移量,单位 px */
endScrollOffset: number
/** 起始和结束的滚动范围映射的时间长度,该时间可用于与关键帧动画里的时间 (duration) 相匹配,单位 ms */
timeRange: number
}
interface SetUpdatePerformanceListenerOption<WithDataPath> {
/** 是否返回变更的 data 字段信息 */
withDataPaths?: WithDataPath
}
interface UpdatePerformanceListener<WithDataPath> {
(res: UpdatePerformance<WithDataPath>): void
}
interface UpdatePerformance<WithDataPath> {
/** 此次更新过程的 ID */
updateProcessId: number
/** 对于子更新,返回它所属的更新过程 ID */
parentUpdateProcessId?: number
/** 是否是被合并更新,如果是,则 updateProcessId 表示被合并到的更新过程 ID */
isMergedUpdate: boolean
/** 此次更新的 data 字段信息,只有 withDataPaths 设为 true 时才会返回 */
dataPaths: WithDataPath extends true ? string[] : undefined
/** 此次更新进入等待队列时的时间戳 */
pendingStartTimestamp: number
/** 更新运算开始时的时间戳 */
updateStartTimestamp: number
/** 更新运算结束时的时间戳 */
updateEndTimestamp: number
}
type PassiveConfig =
| {
/** 是否设置 touchmove 事件为 passive默认为 `false` */
touchmove?: boolean
/** 是否设置 touchstart 事件为 passive默认为 `false` */
touchstart?: boolean
/** 是否设置 wheel 事件为 passive默认为 `false` */
wheel?: boolean
}
| boolean
}
/** ComponentComponent
*
* * 使 `this.data` 使 `setData`
* * `this` 访
* * data `dataXyz` WXML `data-xyz=""` dataset
* * 使 data
* * `2.0.9` data
* * `bug` : type Object Array `this.setData` observer observer `newVal` `oldVal` `changedPath`
*/
declare let Component: WechatMiniprogram.Component.Constructor

1436
typings/types/wx/lib.wx.event.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

299
typings/types/wx/lib.wx.page.d.ts vendored Normal file
View File

@ -0,0 +1,299 @@
/*! *****************************************************************************
Copyright (c) 2024 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
declare namespace WechatMiniprogram.Page {
type Instance<
TData extends DataOption,
TCustom extends CustomOption
> = OptionalInterface<ILifetime> &
InstanceProperties &
InstanceMethods<TData> &
Data<TData> &
TCustom
type Options<
TData extends DataOption,
TCustom extends CustomOption
> = (TCustom &
Partial<Data<TData>> &
Partial<ILifetime> & {
options?: Component.ComponentOptions
}) &
ThisType<Instance<TData, TCustom>>
type TrivialInstance = Instance<IAnyObject, IAnyObject>
interface Constructor {
<TData extends DataOption, TCustom extends CustomOption>(
options: Options<TData, TCustom>
): void
}
interface ILifetime {
/**
*
* onLoad
*/
onLoad(
/** 打开当前页面路径中的参数 */
query: Record<string, string | undefined>
): void | Promise<void>
/**
*
* /
*/
onShow(): void | Promise<void>
/**
*
*
*
* API `wx.setNavigationBarTitle``onReady`
*/
onReady(): void | Promise<void>
/**
*
* / `navigateTo` `tab`
*/
onHide(): void | Promise<void>
/**
*
* `redirectTo``navigateBack`
*/
onUnload(): void | Promise<void>
/**
*
* wx.navigateTo wx.navigateBack
*/
onRouteDone(): void | Promise<void>
/**
*
*
* - `app.json``window``enablePullDownRefresh`
* - `wx.startPullDownRefresh`
* - `wx.stopPullDownRefresh`
*/
onPullDownRefresh(): void | Promise<void>
/**
*
*
* - `app.json``window``onReachBottomDistance`
* -
*/
onReachBottom(): void | Promise<void>
/**
*
* `<button>` `open-type="share"`
*
* ****
*
* return Object
*/
onShareAppMessage(
/** 分享发起来源参数 */
options: IShareAppMessageOption
):
| ICustomShareContent
| IAsyncCustomShareContent
| Promise<ICustomShareContent>
| void
| Promise<void>
/**
*
*
* Beta Android [ (Beta)](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html)
*
* 2.11.3
*/
onShareTimeline(): ICustomTimelineContent | void
/**
*
*
*/
onPageScroll(
/** 页面滚动参数 */
options: IPageScrollOption
): void | Promise<void>
/** 当前是 tab 页时,点击 tab 时触发,最低基础库: `1.9.0` */
onTabItemTap(
/** tab 点击参数 */
options: ITabItemTapOption
): void | Promise<void>
/** 窗口尺寸改变时触发,最低基础库:`2.4.0` */
onResize(
/** 窗口尺寸参数 */
options: IResizeOption
): void | Promise<void>
/**
*
* 2.10.3 7.0.15 iOS
*/
onAddToFavorites(options: IAddToFavoritesOption): IAddToFavoritesContent
/** 每当小程序可能被销毁之前会被调用,可以进行退出状态的保存。最低基础库: `2.7.4` */
onSaveExitState(): ISaveExitState
}
interface InstanceProperties {
/** 页面的文件路径 */
is: string
/** 到当前页面的路径 */
route: string
/** 打开当前页面路径中的参数 */
options: Record<string, string | undefined>
/** 上一次退出前 onSaveExitState 保存的数据 */
exitState: any
/** 相对于当前页面的 Router 对象 */
router: Component.Router
/** 相对于当前页面的 Router 对象 */
pageRouter: Component.Router
/** 渲染当前页面的渲染后端 */
renderer: 'webview' | 'skyline'
}
type DataOption = Record<string, any>
type CustomOption = Record<string, any>
type InstanceMethods<D extends DataOption> = Component.InstanceMethods<D>
interface Data<D extends DataOption> {
/**
*
* `data` 使****
*
* `data` `JSON``data``JSON`
*
* `WXML`
*/
data: D
}
interface ICustomShareContent {
/** 转发标题。默认值:当前小程序名称 */
title?: string
/** 转发路径,必须是以 / 开头的完整路径。默认值:当前页面 path */
path?: string
/** 自定义图片路径可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4最低基础库 `1.5.0`。默认值:使用默认截图 */
imageUrl?: string
}
interface IAsyncCustomShareContent extends ICustomShareContent {
promise: Promise<ICustomShareContent>
}
interface ICustomTimelineContent {
/** 自定义标题,即朋友圈列表页上显示的标题。默认值:当前小程序名称 */
title?: string
/** 自定义页面路径中携带的参数,如 `path?a=1&b=2` 的 “?” 后面部分 默认值:当前页面路径携带的参数 */
query?: string
/** 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持 PNG 及 JPG。显示图片长宽比是 1:1。默认值默认使用小程序 Logo*/
imageUrl?: string
}
interface IPageScrollOption {
/** 页面在垂直方向已滚动的距离单位px */
scrollTop: number
}
interface IShareAppMessageOption {
/**
*
*
* - `button`
* - `menu`
*
* `1.2.4`
*/
from: 'button' | 'menu'
/** `from` `button` `target` `button` `undefined`
*
* `1.2.4` */
target: any
/** `<web-view>``<web-view>`url
*
* `1.6.4`
*/
webViewUrl?: string
}
interface ITabItemTapOption {
/** 被点击tabItem的序号从0开始最低基础库 `1.9.0` */
index: string
/** 被点击tabItem的页面路径最低基础库 `1.9.0` */
pagePath: string
/** 被点击tabItem的按钮文字最低基础库 `1.9.0` */
text: string
}
interface IResizeOption {
size: {
/** 变化后的窗口宽度,单位 px */
windowWidth: number
/** 变化后的窗口高度,单位 px */
windowHeight: number
}
}
interface IAddToFavoritesOption {
/** 页面中包含web-view组件时返回当前web-view的url */
webviewUrl?: string
}
interface IAddToFavoritesContent {
/** 自定义标题,默认值:页面标题或账号名称 */
title?: string
/** 自定义图片,显示图片长宽比为 11默认值页面截图 */
imageUrl?: string
/** 自定义query字段默认值当前页面的query */
query?: string
}
interface ISaveExitState {
/** 需要保存的数据(只能是 JSON 兼容的数据) */
data: any
/** 超时时刻,在这个时刻后,保存的数据保证一定被丢弃,默认为 (当前时刻 + 1 天) */
expireTimeStamp?: number
}
interface GetCurrentPages {
(): Array<Instance<IAnyObject, IAnyObject>>
}
}
/**
* `Object`
*/
declare let Page: WechatMiniprogram.Page.Constructor
/**
*
* __注意__
* - __不要尝试修改页面栈__
* - `App.onLaunch` `getCurrentPages()` `page`
*/
declare let getCurrentPages: WechatMiniprogram.Page.GetCurrentPages

409
typings/types/wx/lib.wx.phys3D.d.ts vendored Normal file
View File

@ -0,0 +1,409 @@
/*! *****************************************************************************
Copyright (c) 2024 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
declare namespace phys3D {
// pvd调试配置
export interface PhysicsDebugConfig {
isNetwork: boolean // 采用网络的方式
ip?: string // 如果isNetwork为true调试信息会通过tcp转发的方式转发到打开了pvd调试软件的电脑需要注意的是防火墙要对pvd打开
port?: 5425 // pvd默认接口
timeout?: 1000 // 默认耗时
path?: string // 如果isNetwork为false调试信息会通过写本地文件的方式落地文件名建议为xxx.pxd2导入pvd调试即可
}
export enum QueryTriggerInteraction {
UseGlobal = 0,
Ignore = 1,
Collide = 2
}
export class PhysSystem {
constructor(config?: PhysicsDebugConfig)
gravity: RawVec3f
bounceThreshold: number
defaultMaxAngularSpeed: number
defaultSolverIterations: number
defaultSolverVelocityIterations: number
sleepThreshold: number
defaultContactOffset: number
destroyScene: () => void
createScene: () => number
Simulate: (step: number) => void
SyncFromTransforms: (() => void) | undefined // added in 2021.06
SetCollisionMask: (mask: ArrayBuffer) => void
Raycast: (
origin: RawVec3f,
unitDir: RawVec3f,
distance: number,
hit: RaycastHit,
layerMask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
) => boolean
RaycastAll: (
origin: RawVec3f,
unitDir: RawVec3f,
distance: number,
layerMask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
) => RaycastHit[]
CapsuleCast(
p1: RawVec3f,
p2: RawVec3f,
radius: number,
direction: RawVec3f,
hit: RaycastHit,
maxDistance: number,
layerMask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
): void
CapsuleCastAll: (
p1: RawVec3f,
p2: RawVec3f,
radius: number,
direction: RawVec3f,
maxDistance: number,
layerMask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
) => RaycastHit[]
BoxCast(
center: RawVec3f,
halfExt: RawVec3f,
direction: RawVec3f,
hit: RaycastHit,
orientation: RawQuaternion,
maxDistance: number,
layerMask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
): void
BoxCastAll: (
center: RawVec3f,
halfExt: RawVec3f,
direction: RawVec3f,
orientation: RawQuaternion,
maxDistance: number,
layerMask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
) => RaycastHit[]
OverlapBox: (
center: RawVec3f,
halfExt: RawVec3f,
orientation: RawQuaternion,
layermask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
) => Collider[]
OverlapCapsule: (
p1: RawVec3f,
p2: RawVec3f,
radius: number,
layermask?: number,
queryTriggerInteraction?: QueryTriggerInteraction
) => Collider[]
}
export class Rigidbody {
constructor(system: PhysSystem)
enabled?: boolean // since 2021.06
position: RawVec3f
rotation: RawQuaternion
AttachToEntity: (pollObj: any, id: number) => void
Remove(): void
Detach(): void
IsAttached(): boolean
}
export enum CollisionDetectionMode {
Discrete = 0,
Continuous = 1,
ContinuousDynamic = 2,
ContinuousSpeculative = 3
}
export enum RigidbodyConstraints {
None = 0,
FreezePositionX = 1 << 0,
FreezePositionY = 1 << 1,
FreezePositionZ = 1 << 2,
FreezeRotationX = 1 << 3,
FreezeRotationY = 1 << 4,
FreezeRotationZ = 1 << 5,
FreezePosition = FreezePositionX | FreezePositionY | FreezePositionZ,
FreezeRotation = FreezeRotationX | FreezeRotationY | FreezeRotationZ,
FreezeAll = FreezePosition | FreezeRotation
}
export enum ForceMode {
kForceModeForce = 0,
kForceModeImpulse = 1 << 0,
kForceModeVelocityChange = 1 << 1,
kForceModeAcceleration = 1 << 2
}
export enum CombineMode {
eAverage = 0,
eMin,
eMultiply,
eMax
}
export enum CookingFlag {
None = 0,
CookForFasterSimulation = 1 << 0,
EnableMeshCleaning = 1 << 1,
WeldColocatedVertices = 1 << 2
}
export enum CollisionFlags {
None = 0,
Sides = 1 << 0,
Above = 1 << 1,
Below = 1 << 2
}
export class RawVec3f {
constructor()
constructor(x: number, y: number, z: number)
x: number
y: number
z: number
}
export class RawQuaternion {
constructor()
constructor(x: number, y: number, z: number, w: number)
x: number
y: number
z: number
w: number
}
export class Collider {
attachedRigidbody: Rigidbody
bounds: Bounds
name: string
contactOffset: number
enabled: boolean
isTrigger: boolean
scale: RawVec3f
material?: Material
sharedMateiral?: Material
ClosestPoint: (raw: RawVec3f) => RawVec3f
ClosestPointOnBounds: (raw: RawVec3f) => RawVec3f
onCollisionEnter?: (collision: Collision) => void
onCollisionExit?: (collision: Collision) => void
onCollisionStay?: (collision: Collision) => void
onTriggerEnter?: (collision: Collision) => void
onTriggerExit?: (collision: Collision) => void
onTriggerStay?: (collision: Collision) => void
dettachRigidbody?: () => void
userData?: unknown
layer: number
}
export class BoxCollider extends Collider {
constructor(system: PhysSystem, center: RawVec3f, size: RawVec3f)
center: RawVec3f
size: RawVec3f
}
export class SphereCollider extends Collider {
constructor(system: PhysSystem, center: RawVec3f, radius: number)
center: RawVec3f
radius: number
}
export class CapsuleCollider extends Collider {
constructor(
system: PhysSystem,
center: RawVec3f,
height: number,
radius: number
)
center: RawVec3f
height: number
radius: number
}
export class MeshCollider extends Collider {
constructor(
system: PhysSystem,
convex: boolean,
cookingOptions: number,
sharedMesh: PhysMesh
)
cookingOptions: number
sharedMesh: PhysMesh | null
convex: boolean
}
export class CharacterController extends Collider {
constructor(system: PhysSystem)
position: RawVec3f
center: RawVec3f
collisionFlags: CollisionFlags
detectCollisions: boolean
enableOverlapRecovery: boolean
height: number
isGrounded: boolean
minMoveDistance: number
radius: number
skinWidth: number
slopeLimit: number
stepOffset: number
velocity: RawVec3f
Move: (movement: RawVec3f) => CollisionFlags
SimpleMove: (speed: RawVec3f) => boolean
AttachToEntity: (pollObj: any, id: number) => void
OnControllerColliderHit?: (hit: ControllerColliderHit) => void
}
export interface ContactPoint {
normal: RawVec3f
this_collider: Collider
other_collider: Collider
point: RawVec3f
separation: number
}
export interface Collision {
collider: Collider
contacts: ContactPoint[]
impulse: RawVec3f
relative_velocity: RawVec3f
}
export interface ControllerColliderHit {
collider: Collider
controller: CharacterController
moveDirection: RawVec3f
normal: RawVec3f
moveLength: number
point: RawVec3f
}
export class Bounds {
constructor(center: RawVec3f, size: RawVec3f)
center: RawVec3f
extents: RawVec3f
max: RawVec3f
min: RawVec3f
size: RawVec3f
ClosestPoint: (point: RawVec3f) => RawVec3f
Contains: (point: RawVec3f) => boolean
Expand: (amount: number) => void
Intersects: (bounds: Bounds) => boolean
SetMinMax: (min: RawVec3f, max: RawVec3f) => void
SqrDistance: (point: RawVec3f) => number
}
export class Material {
constructor(system: PhysSystem)
dynamicFriction: number
staticFriction: number
bounciness: number
frictionCombine: CombineMode
bounceCombine: CombineMode
id: number
}
export class DynamicRigidbody extends Rigidbody {
mass: number
angularDamping: number
angularVelocity: RawVec3f
centerOfMass: RawVec3f
collisionDetectionMode: CollisionDetectionMode
constraints: number
detectCollisions: boolean
linearDamping: number
freezeRotation: boolean
inertiaTensor: number
// inertiaTensorRotation
// interpolation
isKinematic: boolean
maxAngularVelocity: number
maxDepenetrationVelocity: number
sleepThreshold: number
solverIterations: number
solverVelocityIterations: number
useGravity: boolean
velocity: RawVec3f
userData?: unknown
GetWorldCenterOfMass: () => RawVec3f
AddForce: (force: RawVec3f, mode: ForceMode) => void
AddTorque: (torque: RawVec3f, mode: ForceMode) => void
IsSleeping: () => boolean
Sleep: () => void
WakeUp: () => void
AddExplosionForce: (
explosionForce: number,
explosionPosition: RawVec3f,
explosionRadius: number,
upwardsModifier: number,
mode: ForceMode
) => void
AddForceAtPosition: (
force: RawVec3f,
position: RawVec3f,
mode: ForceMode
) => void
AddRelativeForce: (force: RawVec3f, mode: ForceMode) => void
AddRelativeTorque: (torque: RawVec3f, mode: ForceMode) => void
ClosestPointOnBounds: (position: RawVec3f) => RawVec3f
GetPointVelocity: (worldPoint: RawVec3f) => RawVec3f
GetRelativePointVelocity: (relativePoint: RawVec3f) => RawVec3f
MovePosition: (position: RawVec3f) => void
MoveRotation: (rotation: RawQuaternion) => void
ResetCenterOfMass: () => void
ResetInertiaTensor: () => void
SetDensity: (density: number) => void
// SweepTest: () => void;
// SweepTestAll: () => void;
}
export class PhysMesh {
constructor(system: PhysSystem)
// set vertices
SetVertices: (buffer: Float32Array, count: number) => void
// set indices
SetTriangles: (
buffer: Uint16Array | Uint32Array,
count: number,
useUint16: boolean
) => void
}
export class RaycastHit {
constructor()
collider: Collider
distance: number
normal: RawVec3f
point: RawVec3f
rigidbody: Rigidbody
}
}

152
typings/types/wx/lib.wx.wasm.d.ts vendored Normal file
View File

@ -0,0 +1,152 @@
/*! *****************************************************************************
Copyright (c) 2024 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
/** [WXWebAssembly](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/wasm.html)
*
* WXWebAssembly */
declare namespace WXWebAssembly {
type BufferSource = ArrayBufferView | ArrayBuffer
type CompileError = Error
const CompileError: {
prototype: CompileError
new (message?: string): CompileError
(message?: string): CompileError
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance) */
interface Instance {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports) */
readonly exports: Exports
}
const Instance: {
prototype: Instance
new (module: Module, importObject?: Imports): Instance
}
type LinkError = Error
const LinkError: {
prototype: LinkError
new (message?: string): LinkError
(message?: string): LinkError
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) */
interface Memory {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/buffer) */
readonly buffer: ArrayBuffer
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/grow) */
grow(delta: number): number
}
const Memory: {
prototype: Memory
new (descriptor: MemoryDescriptor): Memory
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module) */
interface Module {}
const Module: {
prototype: Module
new (bytes: BufferSource): Module
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/customSections) */
customSections(moduleObject: Module, sectionName: string): ArrayBuffer[]
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/exports) */
exports(moduleObject: Module): ModuleExportDescriptor[]
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/imports) */
imports(moduleObject: Module): ModuleImportDescriptor[]
}
interface RuntimeError extends Error {}
const RuntimeError: {
prototype: RuntimeError
new (message?: string): RuntimeError
(message?: string): RuntimeError
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table) */
interface Table {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/length) */
readonly length: number
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get) */
get(index: number): any
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow) */
grow(delta: number, value?: any): number
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/set) */
set(index: number, value?: any): void
}
const Table: {
prototype: Table
new (descriptor: TableDescriptor, value?: any): Table
}
interface MemoryDescriptor {
initial: number
maximum?: number
shared?: boolean
}
interface ModuleExportDescriptor {
kind: ImportExportKind
name: string
}
interface ModuleImportDescriptor {
kind: ImportExportKind
module: string
name: string
}
interface TableDescriptor {
element: TableKind
initial: number
maximum?: number
}
type ImportExportKind = 'function' | 'global' | 'memory' | 'table'
type TableKind = 'anyfunc' | 'externref'
type ValueType =
| 'anyfunc'
| 'externref'
| 'f32'
| 'f64'
| 'i32'
| 'i64'
| 'v128'
// eslint-disable-next-line @typescript-eslint/ban-types
type ExportValue = Function | Memory | Table
type Exports = Record<string, ExportValue>
type ImportValue = ExportValue | number
type Imports = Record<string, ModuleImports>
type ModuleImports = Record<string, ImportValue>
/** [WXWebAssembly](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/wasm.html) */
function instantiate(
path: string,
importObject?: Imports
): Promise<Instance>
}

16375
typings/types/wx/lib.wx.xr-frame.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff