diff --git a/app.json b/app.json index cc97cac..d37606f 100644 --- a/app.json +++ b/app.json @@ -8,7 +8,6 @@ "pages/search/search", "pages/recite/recite", "pages/review/review", - "pages/pendingQuestion/pendingQuestion", "pages/keyWord/keyWord", "pages/question/question", "pages/managePoems/managePoems" diff --git a/cloudfunctions/answerquestion/config.json b/cloudfunctions/answerquestion/config.json deleted file mode 100644 index 0bf75a8..0000000 --- a/cloudfunctions/answerquestion/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "permissions": { - "openapi": [] - } -} \ No newline at end of file diff --git a/cloudfunctions/answerquestion/index.js b/cloudfunctions/answerquestion/index.js deleted file mode 100644 index 9c6a694..0000000 --- a/cloudfunctions/answerquestion/index.js +++ /dev/null @@ -1,133 +0,0 @@ -// cloudfunctions/answerquestion/index.js -const cloud = require('wx-server-sdk'); - -cloud.init({env: cloud.DYNAMIC_CURRENT_ENV}); - -const db = cloud.database(); -const _ = db.command; - -exports.main = async function(event, context) { - console.log('=== answerquestion 云函数开始执行 ==='); - console.log('收到参数:', JSON.stringify(event, null, 2)); - - try { - // 参数验证 - if (!event.questionId) { - throw new Error('缺少问题ID(questionId)'); - } - - if (!event.answers || !Array.isArray(event.answers) || event.answers.length === 0) { - throw new Error('答案内容不能为空'); - } - - const { questionId, answers, keywords = [], question, poemId = '', title = '' } = event; - - console.log('开始处理问题:', questionId); - - // 1. 验证问题是否存在 - let questionDoc; - try { - questionDoc = await db.collection('Question').doc(questionId).get(); - console.log('查询到的问题:', questionDoc.data); - - if (!questionDoc.data) { - throw new Error(`问题不存在,ID: ${questionId}`); - } - - // 检查问题是否已被回答 - if (questionDoc.data.status === 1) { - throw new Error('该问题已被回答,无法重复提交'); - } - - } catch (dbError) { - console.error('查询问题失败:', dbError); - throw new Error(`查询问题失败: ${dbError.message}`); - } - - // 2. 创建答案记录 - console.log('开始创建答案记录...'); - const answerData = { - question: question || questionDoc.data.question, - answers: answers, - keywords: Array.isArray(keywords) ? keywords : [], - poemId: poemId, - title: title, - status: 1, - createTime: db.serverDate() - }; - - console.log('答案数据:', answerData); - - let answerResult; - try { - answerResult = await db.collection('Answer').add({ - data: answerData - }); - console.log('答案创建成功,ID:', answerResult._id); - } catch (addError) { - console.error('创建答案失败:', addError); - throw new Error(`创建答案记录失败: ${addError.message}`); - } - - // 3. 更新问题状态 - console.log('开始更新问题状态...'); - const updateData = { - status: 1, - answerId: answerResult._id, - answerTime: db.serverDate(), - updateTime: db.serverDate() - }; - - console.log('更新数据:', updateData); - - let updateResult; - try { - updateResult = await db.collection('Question') - .doc(questionId) - .update({ - data: updateData - }); - console.log('问题状态更新成功:', updateResult); - } catch (updateError) { - console.error('更新问题状态失败:', updateError); - - // 如果更新问题失败,尝试删除已创建的答案记录 - try { - await db.collection('Answer').doc(answerResult._id).remove(); - console.log('已回滚删除答案记录'); - } catch (rollbackError) { - console.error('回滚失败:', rollbackError); - } - - throw new Error(`更新问题状态失败: ${updateError.message}`); - } - - // 返回成功结果 - const successResult = { - success: true, - message: '答案提交成功', - answerId: answerResult._id, - data: { - answer: answerResult, - question: updateResult - } - }; - - console.log('云函数执行成功,返回:', successResult); - return successResult; - - } catch (error) { - console.error('=== 云函数执行失败 ==='); - console.error('错误信息:', error.message); - console.error('错误堆栈:', error.stack); - - const errorResult = { - success: false, - message: error.message || '答案提交失败', - error: error.stack - }; - - console.log('返回错误结果:', errorResult); - return errorResult; - } -}; \ No newline at end of file diff --git a/cloudfunctions/answerquestion/package.json b/cloudfunctions/answerquestion/package.json deleted file mode 100644 index 40649fd..0000000 --- a/cloudfunctions/answerquestion/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "answerquestion", - "version": "1.0.0", - "description": "管理员回答问题", - "main": "index.js", - "dependencies": { - "wx-server-sdk": "~2.6.3" - } -} \ No newline at end of file diff --git a/cloudfunctions/getAnsweredQuestions/config.json b/cloudfunctions/getAnsweredQuestions/config.json deleted file mode 100644 index 5ecc33e..0000000 --- a/cloudfunctions/getAnsweredQuestions/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "permissions": { - "openapi": [ - ] - } -} \ No newline at end of file diff --git a/cloudfunctions/getAnsweredQuestions/index.js b/cloudfunctions/getAnsweredQuestions/index.js deleted file mode 100644 index 28c1817..0000000 --- a/cloudfunctions/getAnsweredQuestions/index.js +++ /dev/null @@ -1,96 +0,0 @@ -// 云函数 getAnsweredQuestions -const cloud = require('wx-server-sdk') -cloud.init() - -const db = cloud.database() - -exports.main = async (event, context) => { - const wxContext = cloud.getWXContext() - const openid = wxContext.OPENID - - try { - // 查询该用户提出的所有问题 - const questionsResult = await db.collection('Question') - .where({ - _openid: openid - }) - .orderBy('createTime', 'desc') // 按提问时间倒序排列 - .get() - - // 获取问题列表 - const questions = questionsResult.data - - // 对于每个问题,尝试获取对应的回答(如果有) - const questionsWithAnswers = await Promise.all(questions.map(async (question) => { - try { - // 查询对应问题的回答(通过问题内容匹配) - const answerResult = await db.collection('Answer') - .where({ - question: question.question, // 通过问题内容精确匹配 - _openid: openid // 确保是该用户的问题的回答 - }) - .get() - - // 获取回答内容(如果有) - const answerData = answerResult.data[0] || null - - // 正确获取answers数组内容 - let answerContent = null; - if (answerData) { - // 检查answers是否存在且为数组 - if (Array.isArray(answerData.answers) && answerData.answers.length > 0) { - // 使用数组中的第一个元素作为答案 - answerContent = answerData.answers[0]; - console.log(`找到问题答案: ${question.question.substring(0, 20)}...`); - } else if (answerData.answer) { - // 兼容可能直接存储在answer字段的情况 - answerContent = answerData.answer; - console.log(`从answer字段获取答案`); - } - } - - return { - ...question, - answer: answerContent, // 返回正确获取的答案内容 - answerData: answerData // 返回完整的回答数据 - } - } catch (error) { - console.error(`获取问题回答失败:`, error) - return { - ...question, - answer: null, - answerData: null - } - } - })) - - // 按状态排序:已回答(status=1)的排在前面,待处理(status=0)的排在后面,然后按创建时间倒序 - const sortedQuestions = questionsWithAnswers.sort((a, b) => { - // 将状态标准化为数字:已回答=1,待处理=0 - const statusA = a.status === 1 ? 1 : 0; - const statusB = b.status === 1 ? 1 : 0; - - // 先按状态排序:已回答的排在前面 - if (statusA !== statusB) { - return statusB - statusA; - } - - // 状态相同时按创建时间倒序 - return (b.createTime || 0) - (a.createTime || 0); - }); - - return { - code: 200, - data: sortedQuestions, // 返回所有问题,包括已回答和待处理的 - total: sortedQuestions.length, - message: '获取成功' - } - } catch (error) { - console.error('获取用户问题列表失败:', error) - return { - code: 500, - data: null, - message: '获取失败:' + error.message - } - } -} \ No newline at end of file diff --git a/cloudfunctions/getAnsweredQuestions/package.json b/cloudfunctions/getAnsweredQuestions/package.json deleted file mode 100644 index bbbb35c..0000000 --- a/cloudfunctions/getAnsweredQuestions/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "getAnsweredQuestions", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "wx-server-sdk": "~3.0.1" - } -} \ No newline at end of file diff --git a/cloudfunctions/getQuestionDetail/config.json b/cloudfunctions/getQuestionDetail/config.json deleted file mode 100644 index 5ecc33e..0000000 --- a/cloudfunctions/getQuestionDetail/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "permissions": { - "openapi": [ - ] - } -} \ No newline at end of file diff --git a/cloudfunctions/getQuestionDetail/index.js b/cloudfunctions/getQuestionDetail/index.js deleted file mode 100644 index 89ca3e1..0000000 --- a/cloudfunctions/getQuestionDetail/index.js +++ /dev/null @@ -1,43 +0,0 @@ -// 云函数 getQuestionDetail -const cloud = require('wx-server-sdk') -cloud.init() - -const db = cloud.database() - -exports.main = async (event, context) => { - const { questionId } = event - - try { - // 从问题表获取原始问题 - const questionResult = await db.collection('Question') - .doc(questionId) - .get() - - // 从回答表获取回答 - const answerResult = await db.collection('answer') - .where({ - _id: questionId // 根据设计,answer表的_id对应question表的_id - }) - .get() - - // 提取回答内容(如果有) - const answerData = answerResult.data[0] || null - const answerContent = answerData ? answerData.answer : null - - return { - code: 200, - data: { - question: questionResult.data, - answer: answerContent - }, - message: '获取成功' - } - } catch (error) { - console.error('获取问题详情失败:', error) - return { - code: 500, - data: null, - message: '获取失败:' + error.message - } - } -} \ No newline at end of file diff --git a/cloudfunctions/getQuestionDetail/package.json b/cloudfunctions/getQuestionDetail/package.json deleted file mode 100644 index 707fe46..0000000 --- a/cloudfunctions/getQuestionDetail/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "getQuestionDetail", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "wx-server-sdk": "~3.0.1" - } -} \ No newline at end of file diff --git a/cloudfunctions/initDatabase/config.json b/cloudfunctions/initDatabase/config.json deleted file mode 100644 index 6ecbd09..0000000 --- a/cloudfunctions/initDatabase/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "openapi": [], - "db": { - "cloudbase_env": "cloud1-0g2sr1117862afae", - "auth_mode": "admin" - } - } -} \ No newline at end of file diff --git a/cloudfunctions/initDatabase/index.js b/cloudfunctions/initDatabase/index.js deleted file mode 100644 index 99e0df7..0000000 --- a/cloudfunctions/initDatabase/index.js +++ /dev/null @@ -1,106 +0,0 @@ -// cloudfunctions/initDatabase/index.js -const cloud = require('wx-server-sdk') - -// 初始化云环境 -cloud.init({ - env: cloud.DYNAMIC_CURRENT_ENV -}) - -// 获取数据库引用 -const db = cloud.database() - -// 集合名称 - 使用正确的首字母大写格式 -const COLLECTIONS = { - REVIEW: 'Review', - POETIES: 'poeties', - USER: 'user' -} - -exports.main = async (event, context) => { - try { - console.log('开始初始化数据库...') - - // 检查并创建review集合 - await createCollectionIfNotExists(COLLECTIONS.REVIEW) - - // 检查并创建poeties集合 - await createCollectionIfNotExists(COLLECTIONS.POETIES) - - // 检查并创建user集合 - await createCollectionIfNotExists(COLLECTIONS.USER) - - // 为review集合创建索引(可选,但有助于提高查询性能) - await createIndexesForReviewCollection() - - return { - success: true, - message: '数据库初始化成功', - collections: Object.values(COLLECTIONS) - } - } catch (error) { - console.error('数据库初始化失败:', error) - return { - success: false, - message: '数据库初始化失败', - error: error.message, - errorCode: error.code - } - } -} - -// 检查集合是否存在,如果不存在则创建 -async function createCollectionIfNotExists(collectionName) { - try { - console.log(`检查集合 ${collectionName} 是否存在...`) - - // 尝试获取集合信息 - await db.collection(collectionName).count() - console.log(`集合 ${collectionName} 已存在`) - - return true - } catch (error) { - // 如果集合不存在,会抛出错误 - if (error.code === -502004 || error.code === -502025 || error.code === -502005) { - console.log(`集合 ${collectionName} 不存在(错误码: ${error.code}),正在创建...`) - try { - // 创建集合 - await db.createCollection(collectionName) - console.log(`集合 ${collectionName} 创建成功`) - return true - } catch (createError) { - console.error(`创建集合 ${collectionName} 失败:`, createError) - throw createError - } - } else { - // 其他错误直接抛出 - throw error - } - } -} - -// 为review集合创建索引 -async function createIndexesForReviewCollection() { - try { - console.log('为review集合创建索引...') - - // 为openid创建索引,提高查询性能 - await db.collection(COLLECTIONS.REVIEW).createIndex({ - openid: 1 - }) - - // 为poemId创建索引 - await db.collection(COLLECTIONS.REVIEW).createIndex({ - poemId: 1 - }) - - // 为reciteDateTime创建索引,用于按时间排序 - await db.collection(COLLECTIONS.REVIEW).createIndex({ - reciteDateTime: -1 - }) - - console.log('review集合索引创建成功') - } catch (error) { - console.error('创建索引失败:', error) - // 索引创建失败不应该阻止整个初始化过程 - } -} \ No newline at end of file diff --git a/cloudfunctions/initDatabase/package.json b/cloudfunctions/initDatabase/package.json deleted file mode 100644 index e143eb3..0000000 --- a/cloudfunctions/initDatabase/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "init-database", - "version": "1.0.0", - "description": "初始化数据库集合和索引", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "wx-server-sdk": "~3.0.1" - } -} \ No newline at end of file diff --git a/cloudfunctions/pendingquestion/config.json b/cloudfunctions/pendingquestion/config.json deleted file mode 100644 index c9e9daf..0000000 --- a/cloudfunctions/pendingquestion/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "openapi": [], - "db": { - "query": true, - "read": true - } - } -} \ No newline at end of file diff --git a/cloudfunctions/pendingquestion/index.js b/cloudfunctions/pendingquestion/index.js deleted file mode 100644 index d8823df..0000000 --- a/cloudfunctions/pendingquestion/index.js +++ /dev/null @@ -1,70 +0,0 @@ -const cloud = require('wx-server-sdk') -cloud.init({ - env: cloud.DYNAMIC_CURRENT_ENV -}) - -exports.main = async (event, context) => { - try { - // 添加日志记录,便于调试 - console.log('pendingquestion云函数被调用,event:', event) - - const db = cloud.database() - const _ = db.command - - // 获取查询参数 - const { pageSize = 20, pageNum = 1 } = event - - // 计算起始位置 - const skip = (pageNum - 1) * pageSize - - // 查询待处理问题 - const result = await db.collection('Question') - .where({ - status: 0 // 0表示待处理状态 - }) - .orderBy('createTime', 'asc') - .skip(skip) - .limit(pageSize) - .get() - - console.log('查询结果:', result) - - // 获取总数用于分页 - const countResult = await db.collection('Question') - .where({ - status: 0 - }) - .count() - - console.log('总数结果:', countResult) - - // 构建并返回标准格式的响应 - const response = { - success: true, - data: result.data, - total: countResult.total, - pageNum: pageNum, - pageSize: pageSize, - hasMore: skip + result.data.length < countResult.total, - message: '查询成功' - } - - console.log('返回响应:', response) - return response - - } catch (error) { - console.error('获取待处理问题失败:', error) - - // 构建错误响应 - const errorResponse = { - success: false, - message: '获取待处理问题失败:' + error.message, - data: [], - total: 0, - error: error.message - } - - console.log('返回错误响应:', errorResponse) - return errorResponse - } -} \ No newline at end of file diff --git a/cloudfunctions/pendingquestion/package.json b/cloudfunctions/pendingquestion/package.json deleted file mode 100644 index bf718b6..0000000 --- a/cloudfunctions/pendingquestion/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "pendingquestion", - "version": "1.0.0", - "description": "获取待处理问题列表", - "main": "index.js", - "dependencies": { - "wx-server-sdk": "~2.6.3" - } -} \ No newline at end of file diff --git a/cloudfunctions/reviewManagement/index.js b/cloudfunctions/reviewManagement/index.js index ef91788..16299b9 100644 --- a/cloudfunctions/reviewManagement/index.js +++ b/cloudfunctions/reviewManagement/index.js @@ -5,10 +5,19 @@ cloud.init({ }) const db = cloud.database() +const _ = db.command const reviewCollection = db.collection('Review') exports.main = async (event, context) => { - const { action, reviewData, recordId, openid, poemId, page = 1, pageSize = 20 } = event + const { + action, + reviewData, + recordId, + openid, + poemId, + page = 1, + pageSize = 20 + } = event try { switch (action) { @@ -24,6 +33,10 @@ exports.main = async (event, context) => { return await deleteReviewRecord(recordId) case 'getReviewStats': return await getReviewStats(openid) + case 'checkExistingRecord': // 检查是否存在记录 + return await checkExistingRecord(poemId, openid) + case 'updateReviewRecord': // 更新已有记录 + return await updateReviewRecord(recordId, reviewData) default: return { success: false, @@ -77,6 +90,137 @@ exports.main = async (event, context) => { } } +// 检查是否已存在该用户该诗歌的记录 +async function checkExistingRecord(poemId, openid) { + if (!poemId || !openid) { + return { + success: false, + message: '古诗ID和用户ID不能为空' + } + } + + try { + console.log('检查是否存在记录,poemId:', poemId, 'openid:', openid); + + const result = await reviewCollection + .where({ + poemId: poemId, + openid: openid + }) + .orderBy('reciteDateTime', 'desc') + .limit(1) + .get() + + console.log('查询结果:', result); + + if (result.data && result.data.length > 0) { + const existingRecord = result.data[0]; + console.log('找到已存在记录,ID:', existingRecord._id); + + return { + success: true, + exists: true, + recordId: existingRecord._id, + recordData: existingRecord + } + } else { + console.log('未找到已存在记录'); + return { + success: true, + exists: false, + recordId: null + } + } + } catch (error) { + console.error('检查记录失败:', error); + throw error; + } +} + +// 更新已有背诵记录 +async function updateReviewRecord(recordId, reviewData) { + if (!recordId) { + return { + success: false, + message: '记录ID不能为空' + } + } + + // 验证必要字段 + if (!reviewData || !reviewData.poemId || !reviewData.openid) { + return { + success: false, + message: '缺少必要字段' + } + } + + // 详细日志记录输入数据 + console.log('更新记录 - 接收到的原始reviewData数据结构:', Object.keys(reviewData).join(', ')) + console.log('更新记录 - 接收到的reviewData是否包含content字段:', reviewData.hasOwnProperty('content')) + console.log('更新记录 - 接收到的reviewData是否包含score字段:', reviewData.hasOwnProperty('score')) + + // 先从传入的数据中删除content和score字段,确保不被保存 + if (reviewData.hasOwnProperty('content')) { + console.warn('更新记录 - 发现并删除content字段:', reviewData.content) + delete reviewData.content; + } + if (reviewData.hasOwnProperty('score')) { + console.warn('更新记录 - 发现并删除score字段:', reviewData.score) + delete reviewData.score; + } + + // 详细日志记录清理后的数据 + console.log('更新记录 - 清理后reviewData的数据结构:', Object.keys(reviewData).join(', ')) + + // 准备更新数据,只包含需要更新的字段 + const updateData = {}; + updateData.poemName = reviewData.poemName || ''; // 古诗名称 + updateData.accuracy = Number(reviewData.accuracy) || 0; // 正确率,确保是数字类型 + updateData.duration = Number(reviewData.duration) || 0; // 背诵时长(秒),确保是数字类型 + updateData.reciteDateTime = reviewData.reciteDateTime || new Date().toISOString(); // 背诵时间 + updateData.updateTime = db.serverDate(); // 更新时间 + + // 再次明确确认updateData中没有content和score字段 + if (updateData.hasOwnProperty('content')) { + console.error('更新记录 - 警告: updateData中仍存在content字段,将被强制删除!') + delete updateData.content; + } + if (updateData.hasOwnProperty('score')) { + console.error('更新记录 - 警告: updateData中仍存在score字段,将被强制删除!') + delete updateData.score; + } + + // 详细日志记录最终更新的数据 + console.log('更新记录 - 最终准备更新的数据结构:', Object.keys(updateData).join(', ')) + console.log('更新记录 - 最终准备更新的数据:', JSON.stringify(updateData)) + + try { + // 执行更新操作 + const result = await reviewCollection.doc(recordId).update({ + data: updateData + }) + + console.log('背诵记录更新成功,ID:', recordId, '更新结果:', result) + + // 返回结果 + return { + success: true, + _id: recordId, + message: '背诵记录更新成功', + updatedFields: Object.keys(updateData), // 返回实际更新的字段列表,便于调试 + stats: result.stats // 返回更新统计信息 + } + } catch (error) { + if (error.errCode === -502005) { + return { + success: false, + message: '背诵记录不存在或已被删除' + } + } + throw error + } +} + // 添加背诵记录 async function addReviewRecord(reviewData) { // 验证必要字段 diff --git a/pages/guiding/guiding.js b/pages/guiding/guiding.js index d37b847..02d1e3e 100644 --- a/pages/guiding/guiding.js +++ b/pages/guiding/guiding.js @@ -52,9 +52,9 @@ Page({ url: `/pages/index/index?openid=${cachedUserInfo.openid}` }); } else { - console.log('缓存中为管理员,跳转到pendingQuestion页面'); + console.log('缓存中为管理员,跳转到managePoem页面'); wx.reLaunch({ - url: `/pages/pendingQuestion/pendingQuestion?openid=${cachedUserInfo.openid}` + url: `/pages/managePoems/managePoems?openid=${cachedUserInfo.openid}` }); } return; // 跳转后可以return,避免后续代码执行 @@ -95,9 +95,9 @@ Page({ url: `/pages/index/index?openid=${userInfo.openid}` }); } else { - console.log('管理员,跳转到pendingQuestion页面'); + console.log('管理员,跳转到managePoems页面'); wx.reLaunch({ - url: `/pages/pendingQuestion/pendingQuestion?openid=${userInfo.openid}` + url: `/pages/managePoems/managePoems?openid=${userInfo.openid}` }); } diff --git a/pages/index/index.js b/pages/index/index.js index be99d10..5a5faa5 100644 --- a/pages/index/index.js +++ b/pages/index/index.js @@ -26,7 +26,8 @@ Page({ }); } }, -// 加载用户数据和诗词数据 + + // 加载用户数据和诗词数据 async loadUserDataAndPoems() { try { this.setData({ isLoading: true }); @@ -40,25 +41,28 @@ Page({ if (userResult.success) { const userInfo = userResult.data.userInfo; this.setData({ - userInfo: userInfo, - reviewedPoems: userInfo.reviewedPoems || {} + userInfo: userInfo }); - - console.log('用户复习记录:', this.data.reviewedPoems); } if (poemsResult.success) { const allPoems = poemsResult.data.poems; - // 筛选其他诗词 - const { reviewedPoems, otherPoems } = this.classifyPoems(allPoems); + + // 获取用户的复习记录 + const reviewResult = await this.getUserReviewRecords(this.data.openid); + const reviewedPoemIds = reviewResult.success ? reviewResult.data.reviews.map(item => item.poemId) : []; + + // 分类诗词:把需要复习的古诗放到末尾 + const { reviewedPoems, otherPoems, poems } = this.classifyPoems(allPoems, reviewedPoemIds); this.setData({ - poems: allPoems, + poems: poems, reviewedPoems: reviewedPoems, otherPoems: otherPoems }); - + console.log('复习诗词数量:', reviewedPoems.length); + console.log('其他诗词数量:', otherPoems.length); } } catch (error) { @@ -71,6 +75,26 @@ Page({ this.setData({ isLoading: false }); } }, + + // 从Review表获取用户的复习记录 + async getUserReviewRecords(openid) { + try { + const result = await wx.cloud.callFunction({ + name: 'reviewManagement', + data: { + action: 'getUserReviews', + openid: openid, + page: 1, + pageSize: 100 // 获取所有复习记录 + } + }); + return result.result; + } catch (error) { + console.error('获取复习记录失败:', error); + return { success: false, message: '获取复习记录失败' }; + } + }, + // 获取用户信息 async getUserInfo(openid) { try { @@ -87,6 +111,7 @@ Page({ return { success: false, message: '获取用户信息失败' }; } }, + // 获取所有诗词 async getAllPoems() { try { @@ -102,28 +127,38 @@ Page({ return { success: false, message: '获取诗词失败' }; } }, - // 分类诗词:今天需要复习的 vs 其他诗词 - classifyPoems(allPoems) { - const reviewedPoems = this.data.reviewedPoems; + + // 分类诗词:把需要复习的古诗放到末尾 + classifyPoems(allPoems, reviewedPoemIds) { + const reviewedPoems = []; const otherPoems = []; allPoems.forEach(poem => { - const poemReviewData = reviewedPoems[poem._id]; - if (poemReviewData==null){ + if (reviewedPoemIds.includes(poem._id)) { + // 需要复习的诗词 + reviewedPoems.push(poem); + } else { + // 其他诗词 otherPoems.push(poem); } - }); - return { reviewedPoems, otherPoems }; + // 把需要复习的诗词放到其他诗词的末尾 + const finalPoems = [...otherPoems, ...reviewedPoems]; + + return { + reviewedPoems: reviewedPoems, + otherPoems: otherPoems, + poems: finalPoems // 最终的诗词列表,复习的放在末尾 + }; }, + // 进入学习页面 goToStudy(e) { const poemId = e.currentTarget.dataset.id; - - wx.navigateTo({ - url: `/pages/study/study?id=${poemId}` - }); + wx.navigateTo({ + url: `/pages/study/study?id=${poemId}` + }); }, // 进入背诵页面 @@ -138,16 +173,14 @@ Page({ } }, - review(){ - const userInfo= this.data.userInfo; - console.log('跳转到复习页面,诗歌ID:', userInfo); + const userInfo = this.data.userInfo; + console.log('跳转到复习页面,用户信息:', userInfo); wx.navigateTo({ - url: `/pages/review/review?userInfo=${userInfo}` + url: `/pages/review/review?userInfo=${encodeURIComponent(JSON.stringify(userInfo))}` }) }, - question(){ wx.navigateTo({ url: '/pages/question/question' @@ -162,15 +195,19 @@ Page({ onShow() { // 每次显示页面时检查数据 - if (this.data.poems.length === 0 && !this.data.isLoading) { - this.loadPoemsFromDatabase(); + if (this.data.openid && this.data.poems.length === 0 && !this.data.isLoading) { + this.loadUserDataAndPoems(); } }, onPullDownRefresh() { - this.loadPoemsFromDatabase().finally(() => { + if (this.data.openid) { + this.loadUserDataAndPoems().finally(() => { + wx.stopPullDownRefresh(); + }); + } else { wx.stopPullDownRefresh(); - }); + } }, onShareAppMessage() { @@ -179,7 +216,4 @@ Page({ path: '/pages/index/index' }; } -}) - - - \ No newline at end of file +}) \ No newline at end of file diff --git a/pages/keyWord/keyWord.js b/pages/keyWord/keyWord.js index aea5c78..f624a1a 100644 --- a/pages/keyWord/keyWord.js +++ b/pages/keyWord/keyWord.js @@ -1,327 +1,20 @@ // pages/keyWord/keyWord.js Page({ - data: { - questionId: '', - question: '', - answer: '', - // 关键词相关数据 - allKeywords: [], // 所有关键词数组 - currentLevel: 0, // 当前关键词层级 - selectedKeywords: [], // 已选择的关键词路径 - availableKeywords: [], // 当前层级可用的关键词 - newKeyword: '', // 新建关键词输入 - isCreatingNew: false // 是否正在创建新关键词 - }, - - onLoad(options) { - if (options) { - this.setData({ - questionId: options.id || '', - question: options.question ? decodeURIComponent(options.question) : '' - }); - // 加载已有的关键词数据 - this.loadExistingKeywords(); - } - }, - - // 加载已有关键词数据 - async loadExistingKeywords() { - try { - const db = wx.cloud.database(); - // 从数据库获取所有已有的关键词数据 - const result = await db.collection('Answer').get(); - - const allKeywords = []; - result.data.forEach(item => { - if (item.keywords && Array.isArray(item.keywords)) { - // 将每个答案的关键词路径添加到总列表中 - allKeywords.push(item.keywords); - } - }); - - this.setData({ allKeywords }); - this.updateAvailableKeywords(); - - } catch (error) { - console.error('加载关键词失败:', error); - // 如果answers集合不存在,就初始化为空数组 - this.setData({ allKeywords: [] }); - } - }, - - // 更新当前可用的关键词 - updateAvailableKeywords() { - const { selectedKeywords, allKeywords, currentLevel } = this.data; - - // 获取当前层级的所有可能关键词 - const available = new Set(); - - allKeywords.forEach(keywordPath => { - if (keywordPath.length > currentLevel) { - // 检查路径是否匹配已选择的关键词 - let isMatch = true; - for (let i = 0; i < selectedKeywords.length; i++) { - if (keywordPath[i] !== selectedKeywords[i]) { - isMatch = false; - break; - } - } - - if (isMatch && keywordPath[currentLevel]) { - available.add(keywordPath[currentLevel]); - } - } - }); - - this.setData({ - availableKeywords: Array.from(available) - }); - }, - - // 选择关键词 - selectKeyword(e) { - const keyword = e.currentTarget.dataset.keyword; - const { selectedKeywords, currentLevel } = this.data; - - // 更新已选择的关键词路径 - const newSelectedKeywords = [...selectedKeywords]; - if (newSelectedKeywords.length > currentLevel) { - newSelectedKeywords[currentLevel] = keyword; - } else { - newSelectedKeywords.push(keyword); - } - - this.setData({ - selectedKeywords: newSelectedKeywords, - currentLevel: currentLevel + 1, - isCreatingNew: false, - newKeyword: '' - }); - - // 更新可用关键词 - this.updateAvailableKeywords(); - }, - - // 新建关键词 - toggleCreateNew() { - this.setData({ - isCreatingNew: !this.data.isCreatingNew, - newKeyword: '' - }); - }, - - // 输入新建关键词 - onNewKeywordInput(e) { - this.setData({ - newKeyword: e.detail.value - }); - }, - // 确认新建关键词 - confirmNewKeyword() { - const { newKeyword, selectedKeywords, currentLevel } = this.data; - - if (!newKeyword.trim()) { - wx.showToast({ - title: '请输入关键词', - icon: 'none' - }); - return; - } - - // 更新已选择的关键词路径 - const newSelectedKeywords = [...selectedKeywords]; - if (newSelectedKeywords.length > currentLevel) { - newSelectedKeywords[currentLevel] = newKeyword.trim(); - } else { - newSelectedKeywords.push(newKeyword.trim()); - } - - this.setData({ - selectedKeywords: newSelectedKeywords, - currentLevel: currentLevel + 1, - isCreatingNew: false, - newKeyword: '' - }); - - // 更新可用关键词(新建的关键词会出现在下一级选择中) - this.updateAvailableKeywords(); - }, - - // 返回上一级 - goBack() { - const { currentLevel } = this.data; - if (currentLevel > 0) { - this.setData({ - currentLevel: currentLevel - 1, - isCreatingNew: false, - newKeyword: '' - }); - this.updateAvailableKeywords(); - } - }, + /** + * 页面的初始数据 + */ + data: { - // 输入回答 - onAnswerInput(e) { - this.setData({ - answer: e.detail.value - }); }, - // 获取当前关键词路径显示 - getCurrentKeywordPath() { - const { selectedKeywords, currentLevel } = this.data; - const currentPath = selectedKeywords.slice(0, currentLevel); - - if (currentPath.length === 0) { - return '请选择关键词(从第一级开始)'; - } - - return currentPath.join(' > '); - }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { - // 重置关键词 - resetKeywords() { - this.setData({ - selectedKeywords: [], - currentLevel: 0, - isCreatingNew: false, - newKeyword: '' - }); - this.updateAvailableKeywords(); - - wx.showToast({ - title: '关键词已重置', - icon: 'success' - }); }, - // 提交答案和关键词 -async submitAnswer() { - const { questionId, question, selectedKeywords, answer } = this.data; - - // 验证输入 - if (selectedKeywords.length === 0) { - wx.showToast({ - title: '请至少选择一个关键词', - icon: 'none' - }); - return; - } - - if (!answer.trim()) { - wx.showToast({ - title: '请输入答案', - icon: 'none' - }); - return; - } - - // 显示加载中 - wx.showLoading({ - title: '提交中...', - mask: true - }); - - try { - const db = wx.cloud.database(); - - // 1. 从Question集合获取问题详情,包括用户的_openid - let userOpenid = ''; - let questionDocId = questionId; - - if (questionId) { - try { - const questionDetail = await db.collection('Question').doc(questionId).get(); - userOpenid = questionDetail.data._openid || ''; - console.log('获取到用户openid:', userOpenid); - } catch (detailError) { - console.error('获取问题详情失败:', detailError); - // 如果通过ID查询失败,尝试通过问题内容查询 - const queryResult = await db.collection('Question').where({ - question: question - }).get(); - - if (queryResult.data.length > 0) { - const firstQuestion = queryResult.data[0]; - userOpenid = firstQuestion._openid || ''; - questionDocId = firstQuestion._id; - console.log('通过问题内容获取到用户openid:', userOpenid); - } - } - } - - // 2. 保存答案到Answer集合,按照数据库要求的格式 - const saveData = { - createTime: db.serverDate(), // 使用服务器时间 - keywords: selectedKeywords, // 关键词数组 - question: question, // 问题内容 - questionId: questionDocId, // 保存问题ID,便于后续查询 - answers: [answer.trim()], // 答案使用answers数组 - status: 1, // 标记为已回答状态 - userId: userOpenid // 保存提问用户的openid,但使用userId而非_openid,避免系统保留字段冲突 - }; - - await db.collection('Answer').add({ - data: saveData - }); - - console.log('答案保存成功:', saveData); - - // 3. 更新Question集合中对应问题的状态为已回答(status: 1) - if (questionDocId) { - try { - await db.collection('Question').doc(questionDocId).update({ - data: { - status: 1 - } - }); - console.log('问题状态更新成功,问题ID:', questionDocId); - } catch (updateError) { - console.error('更新问题状态失败:', updateError); - // 尝试通过问题内容更新 - const updateResult = await db.collection('Question').where({ - question: question - }).update({ - data: { - status: 1 - } - }); - console.log('通过问题内容更新状态结果:', updateResult); - } - } - - wx.hideLoading(); - wx.showToast({ - title: '提交成功', - icon: 'success', - duration: 2000, - success: () => { - setTimeout(() => { - wx.navigateBack(); - }, 1500); - } - }); - - } catch (error) { - console.error('提交失败:', error); - wx.hideLoading(); - - let errorMsg = '提交失败,请重试'; - if (error.errCode === -502005) { - errorMsg = '数据库集合不存在,请检查配置'; - } else if (error.errCode === -501007) { - errorMsg = '参数错误,请检查输入'; - } - - wx.showToast({ - title: errorMsg, - icon: 'none', - duration: 2000 - }); - } -}, - /** * 生命周期函数--监听页面初次渲染完成 */ diff --git a/pages/keyWord/keyWord.json b/pages/keyWord/keyWord.json deleted file mode 100644 index 8835af0..0000000 --- a/pages/keyWord/keyWord.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "usingComponents": {} -} \ No newline at end of file diff --git a/pages/keyWord/keyWord.wxml b/pages/keyWord/keyWord.wxml index 4929bdd..b9cd149 100644 --- a/pages/keyWord/keyWord.wxml +++ b/pages/keyWord/keyWord.wxml @@ -1,83 +1,2 @@ - - 关键词回答 - - - - - - 问题: - - {{question}} - - - - - - 关键词管理: - - - - 当前路径: - {{getCurrentKeywordPath()}} - - - - - 第{{currentLevel + 1}}级关键词 - - - - - - {{item}} - - - - - - - - + - {{isCreatingNew ? '取消新建' : '新建关键词'}} - - - - - - - - - - - - - - - - - - - 答案: - - + + + + + + + + 识别中... {{recognitionProgress}}% + + + + + + @@ -40,25 +60,10 @@ - - - - - - - - {{accuracyRate >= 90 ? '优秀!背诵得非常准确' : accuracyRate >= 70 ? '良好!继续保持' : accuracyRate >= 50 ? '一般,还需要练习' : '需要多加练习'}} - - - - - - \ No newline at end of file diff --git a/pages/recite/recite.wxss b/pages/recite/recite.wxss index 53e3f56..7ec1085 100644 --- a/pages/recite/recite.wxss +++ b/pages/recite/recite.wxss @@ -140,25 +140,28 @@ text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); } -/* 进度条样式 */ -.progress-bar { - margin-bottom: 30rpx; +/* 进度条容器 */ +.progress-container { + margin: 40rpx 0; + padding: 0 30rpx; } +/* 进度条背景 */ .progress-bg { - width: 100%; - height: 30rpx; - background: #f0f0f0; - border-radius: 15rpx; + flex: 1; + height: 24rpx; + background-color: #f0f0f0; + border-radius: 12rpx; overflow: hidden; - box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.1); + position: relative; } +/* 进度条填充 */ .progress-fill { height: 100%; - border-radius: 15rpx; - transition: width 0.6s ease; - box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2); + border-radius: 12rpx; + transition: width 0.5s ease-in-out, background 0.5s ease; + min-width: 0%; } /* 评价文字 */ @@ -209,6 +212,49 @@ padding: 20rpx 0; } +/* 语音识别进度条样式 */ +.recognition-progress { + margin: 40rpx; + background: #fff; + padding: 30rpx; + border-radius: 15rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08); +} + +.recognition-progress .progress-bar { + width: 100%; + height: 20rpx; + background: #eee; + border-radius: 10rpx; + overflow: hidden; + margin-bottom: 20rpx; +} + +.recognition-progress .progress-inner { + height: 100%; + background: linear-gradient(90deg, #4CAF50, #45a049); + border-radius: 10rpx; + transition: width 0.3s ease; +} + +.recognition-progress .progress-text { + font-size: 28rpx; + text-align: center; + margin-top: 20rpx; + color: #333; + display: block; +} + +/* 录音按钮状态 */ +.record-btn.recording { + background-color: #FF4757; +} + +.record-btn.processing { + background-color: #FFA502; + opacity: 0.7; +} + /* 响应式设计 */ @media screen and (min-width: 768px) { .recite-container { diff --git a/pages/review/review.js b/pages/review/review.js index 565f5eb..cad62ea 100644 --- a/pages/review/review.js +++ b/pages/review/review.js @@ -65,14 +65,18 @@ Page({ */ data: { // 遗忘曲线数据 - 显示未来7天的复习计划 - 复习计划数据: [], + 复习计划数据: [], // 需要复习的古诗列表 poemsToReview: [], // Canvas尺寸 canvasWidth: 0, canvasHeight: 300, // 今日需要复习的总数 - todayReviewCount: 0 + todayReviewCount: 0, + // 用户是否有背诵记录 + hasRecitationRecords: false, + // 加载状态 + isLoading: true }, /** @@ -91,21 +95,31 @@ Page({ */ async loadPoemsToReview() { try { - wx.showLoading({ title: '加载复习计划...' }) + this.setData({ isLoading: true }); // 从数据库加载真实背诵记录 let reciteRecords = await this.loadReciteRecordsFromDatabase(); - // 如果没有记录或记录为空,使用模拟数据 - let recordsToUse = reciteRecords && reciteRecords.length > 0 ? reciteRecords : this.getMockReciteRecords(); - - // 增强:为每条记录获取完整的诗歌信息,特别是作者信息 - if (reciteRecords && reciteRecords.length > 0) { - recordsToUse = await this.enrichRecordsWithPoemDetails(reciteRecords); + console.log('获取到的背诵记录数量:', reciteRecords ? reciteRecords.length : 0); + + // 检查是否有背诵记录 + if (!reciteRecords || reciteRecords.length === 0) { + console.log('用户暂无背诵记录'); + this.setData({ + poemsToReview: [], + 复习计划数据: [], + todayReviewCount: 0, + hasRecitationRecords: false, + isLoading: false + }); + return []; } + // 为每条记录获取完整的诗歌信息,特别是作者信息 + reciteRecords = await this.enrichRecordsWithPoemDetails(reciteRecords); + // 使用SM2算法计算复习计划 - const poemsWithReviewPlan = this.calculateReviewPlan(recordsToUse); + const poemsWithReviewPlan = this.calculateReviewPlan(reciteRecords); // 生成未来7天的复习计划数据(用于图表显示) const reviewPlanData = this.generateReviewPlanData(poemsWithReviewPlan); @@ -116,27 +130,23 @@ Page({ this.setData({ poemsToReview: poemsWithReviewPlan, 复习计划数据: reviewPlanData, - todayReviewCount + todayReviewCount, + hasRecitationRecords: true, + isLoading: false }); - wx.hideLoading() return poemsWithReviewPlan; } catch (error) { - wx.hideLoading() console.error('加载复习数据失败:', error); - // 出错时使用模拟数据 - const mockRecords = this.getMockReciteRecords(); - const poemsWithReviewPlan = this.calculateReviewPlan(mockRecords); - const reviewPlanData = this.generateReviewPlanData(poemsWithReviewPlan); - const todayReviewCount = poemsWithReviewPlan.filter(p => p.needsReviewToday).length; - this.setData({ - poemsToReview: poemsWithReviewPlan, - 复习计划数据: reviewPlanData, - todayReviewCount + poemsToReview: [], + 复习计划数据: [], + todayReviewCount: 0, + hasRecitationRecords: false, + isLoading: false }); - return poemsWithReviewPlan; + return []; } }, @@ -198,59 +208,7 @@ Page({ }, /** - * 获取模拟的背诵记录数据 - */ - getMockReciteRecords() { - // 仅在数据库加载失败时使用模拟数据 - const now = Date.now(); - const dayMs = 24 * 60 * 60 * 1000; - - return [ - { - poemId: 'poet_001', - poemTitle: '静夜思', - poemAuthor: '李白', - lastReciteTime: now - dayMs, // 1天前 - accuracy: 85, - reciteCount: 2, - ef: 2.3, - nextReviewInterval: 1 - }, - { - poemId: 'poet_002', - poemTitle: '春晓', - poemAuthor: '孟浩然', - lastReciteTime: now - 3 * dayMs, // 3天前 - accuracy: 60, - reciteCount: 1, - ef: 2.0, - nextReviewInterval: 1 - }, - { - poemId: 'poet_003', - poemTitle: '望庐山瀑布', - poemAuthor: '李白', - lastReciteTime: now - 0.5 * dayMs, // 0.5天前 - accuracy: 95, - reciteCount: 3, - ef: 2.7, - nextReviewInterval: 6 - }, - { - poemId: 'poet_004', - poemTitle: '登鹳雀楼', - poemAuthor: '王之涣', - lastReciteTime: now - 7 * dayMs, // 7天前 - accuracy: 70, - reciteCount: 2, - ef: 2.1, - nextReviewInterval: 3 - } - ] - }, - - /** - * 从数据库加载真实的背诵记录 + * 从数据库加载真实的背诵记录 - 修改为每首诗只返回最新记录 */ async loadReciteRecordsFromDatabase() { try { @@ -263,19 +221,39 @@ Page({ throw new Error('获取用户信息失败') } - // 查询用户的背诵记录 + console.log('当前用户openid:', result.openid); + + // 查询用户的背诵记录,按诗歌ID分组,获取每组的最新记录 const db = wx.cloud.database() - const records = await db.collection('Review') + const allRecords = await db.collection('Review') .where({ openid: result.openid }) .orderBy('reciteDateTime', 'desc') .get() - console.log('从数据库获取的背诵记录:', records.data); + console.log('从数据库获取的所有背诵记录:', allRecords.data); + + // 按poemId分组,只保留每个poemId的最新记录 + const latestRecordsMap = new Map(); + + allRecords.data.forEach(record => { + const poemId = record.poemId; + // 如果还没有这个poemId的记录,或者当前记录时间更晚,则更新 + if (!latestRecordsMap.has(poemId) || + new Date(record.reciteDateTime) > new Date(latestRecordsMap.get(poemId).reciteDateTime)) { + latestRecordsMap.set(poemId, record); + } + }); + + // 将Map转换为数组 + const uniqueRecords = Array.from(latestRecordsMap.values()); + + console.log('去重后的最新记录数量:', uniqueRecords.length); + console.log('去重后的记录:', uniqueRecords); // 转换数据库记录格式,确保字段名一致 - const formattedRecords = records.data.map(record => { + const formattedRecords = uniqueRecords.map(record => { return { poemId: record.poemId, poemTitle: record.poemName || '未知标题', @@ -291,58 +269,18 @@ Page({ return formattedRecords; } catch (error) { console.error('加载背诵记录失败:', error); - // 如果加载失败,返回空数组,让上层函数处理 + // 如果加载失败,返回空数组 return []; } }, - /** - * 手动初始化数据库 - */ - async initDatabaseManually() { - try { - wx.showLoading({ title: '初始化数据库...' }) - - const result = await wx.cloud.callFunction({ - name: 'initDatabase' - }) - - wx.hideLoading() - - if (result.result && result.result.success) { - wx.showToast({ - title: '数据库初始化成功', - icon: 'success' - }) - // 重新加载数据 - this.loadPoemsToReview() - } else { - wx.showToast({ - title: result.result?.message || '初始化失败', - icon: 'none' - }) - } - } catch (error) { - wx.hideLoading() - wx.showToast({ - title: '初始化失败: ' + error.message, - icon: 'none' - }) - } - }, - - /** - * 基于SM2算法计算复习计划和优先级 - */ calculateReviewPlan(records) { const now = Date.now(); - const sm2 = new SM2(); - // 去重处理,确保每首诗只出现一次 + // 去重处理,确保每首诗只出现一次(这里已经是去重后的数据,但再加一层保障) const uniqueRecords = []; const poemIdSet = new Set(); - // 优先保留最新的记录(根据传入的顺序,假设前面的记录更新) records.forEach(record => { const poemId = record.poemId || record._id; if (poemId && !poemIdSet.has(poemId)) { @@ -351,20 +289,20 @@ Page({ } }); - console.log('去重后的记录数量:', uniqueRecords.length); + console.log('最终去重后的记录数量:', uniqueRecords.length); // 计算每首诗的复习状态和下次复习时间 const poemsWithReviewStatus = uniqueRecords.map(record => { - // 标准化字段名,确保兼容性 + // 标准化字段名 const poem = { id: record.poemId || record._id, title: record.poemTitle || record.poemName || '未知标题', author: record.poemAuthor || '未知作者', - lastReciteTime: record.lastReciteTime || (record.reciteDateTime ? new Date(record.reciteDateTime).getTime() : now - 24 * 60 * 60 * 1000), - accuracy: record.accuracy || record.matchRate || 0, + lastReciteTime: record.lastReciteTime, + accuracy: record.accuracy || 0, reciteCount: record.reciteCount || 0, - ef: record.ef || 2.5, // 易度因子 - nextReviewInterval: record.nextReviewInterval || 1 // 下次复习间隔 + ef: record.ef || 2.5, + nextReviewInterval: record.nextReviewInterval || 1 }; // 计算距离上次背诵的时间(天) @@ -378,32 +316,39 @@ Page({ else if (poem.accuracy >= 40) quality = 2; else if (poem.accuracy >= 20) quality = 1; - // 使用SM2计算下次复习间隔 + // 使用SM2计算下次复习间隔(传入当前状态) + const sm2 = new SM2(); sm2.ef = poem.ef; sm2.reps = poem.reciteCount; sm2.lastInterval = poem.nextReviewInterval; const reviewResult = sm2.calculateNextReview(quality); - // 判断是否需要今天复习 - const needsReviewToday = daysSinceLastRecite >= poem.nextReviewInterval - 0.1; // 允许0.1天的误差 - - // 计算记忆保留率(基于艾宾浩斯公式) - const hoursSinceLastReview = daysSinceLastRecite * 24; - const retentionRate = sm2.calculateRetentionRate(hoursSinceLastReview, poem.ef * 10); + // 计算距离下次复习还有多少天 + const daysUntilNextReview = Math.max(0, Math.ceil(reviewResult.nextInterval - daysSinceLastRecite)); + // 判断是否需要今天复习 - 统一使用一个变量声明 + const needsReviewToday = daysUntilNextReview === 0; + + console.log(`《${poem.title}》: + 距离上次: ${daysSinceLastRecite.toFixed(1)}天 + SM2建议间隔: ${reviewResult.nextInterval}天 + 距离下次复习: ${daysUntilNextReview}天 + 需要今天复习: ${needsReviewToday}`); + return { ...poem, - daysSinceLastRecite: Math.round(daysSinceLastRecite * 10) / 10, // 保留一位小数 + daysSinceLastRecite: Math.round(daysSinceLastRecite * 10) / 10, nextReviewInterval: reviewResult.nextInterval, newEF: reviewResult.newEF, needsReviewToday, - retentionRate, - reviewUrgency: needsReviewToday ? (100 - retentionRate) : 0 + daysUntilNextReview, // 这个字段用于显示 + retentionRate: sm2.calculateRetentionRate(daysSinceLastRecite * 24, poem.ef * 10), + reviewUrgency: needsReviewToday ? (100 - (sm2.calculateRetentionRate(daysSinceLastRecite * 24, poem.ef * 10) || 0)) : daysUntilNextReview }; }); - // 按复习紧急程度排序(今天需要复习的排前面,按记忆保留率从低到高) + // 按复习紧急程度排序 poemsWithReviewStatus.sort((a, b) => { if (a.needsReviewToday && !b.needsReviewToday) return -1; if (!a.needsReviewToday && b.needsReviewToday) return 1; @@ -412,17 +357,18 @@ Page({ return poemsWithReviewStatus; }, - + /** * 生成未来7天的复习计划数据(用于图表显示) */ generateReviewPlanData(poems) { const planData = []; const now = new Date(); + const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); // 初始化未来7天的数据 for (let i = 0; i < 7; i++) { - const date = new Date(now); + const date = new Date(todayStart); date.setDate(date.getDate() + i); planData.push({ day: i === 0 ? '今天' : i === 1 ? '明天' : `${i}天后`, @@ -433,27 +379,18 @@ Page({ // 统计每天需要复习的古诗数量 poems.forEach(poem => { - // 优先使用needsReviewToday标志来判断是否今天需要复习 - if (poem.needsReviewToday) { - // 如果标记为今天需要复习,直接计入今天的数量 - planData[0].count++; - return; // 跳过后续计算 - } - - // 对于不是今天需要复习的,正常计算下次复习日期 - const lastReviewDate = new Date(poem.lastReciteTime); - const nextReviewDate = new Date(lastReviewDate); - nextReviewDate.setDate(nextReviewDate.getDate() + poem.nextReviewInterval); + // 直接使用计算好的 daysUntilNextReview + const daysUntilNextReview = poem.daysUntilNextReview; - // 计算下次复习日期距离今天的天数(使用宽松计算,不四舍五入) - const daysUntilNextReview = Math.floor((nextReviewDate - now) / (24 * 60 * 60 * 1000)); + console.log(`《${poem.title}》: ${daysUntilNextReview}天后复习`); - // 更新对应天数的复习数量 + // 分配到对应的天数 if (daysUntilNextReview >= 0 && daysUntilNextReview < 7) { planData[daysUntilNextReview].count++; } }); + console.log('复习计划数据:', planData); return planData; }, @@ -578,157 +515,169 @@ Page({ }, /** - * 绘制复习计划图表 - * 显示未来7天每天需要复习的古诗数量 - */ - drawReviewPlanChart() { - const ctx = wx.createCanvasContext('reviewPlanChart'); - const { canvasWidth, canvasHeight } = this.data; - const data = this.data.复习计划数据; - - // 确保有数据 - if (!data || data.length === 0) { - // 绘制无数据状态 - ctx.setFillStyle('#ffffff'); - ctx.fillRect(0, 0, canvasWidth, canvasHeight); - ctx.setFillStyle('#999999'); - ctx.setFontSize(14); - ctx.setTextAlign('center'); - ctx.fillText('暂无复习计划数据', canvasWidth / 2, canvasHeight / 2); - ctx.draw(); - return; - } - - // 设置画布背景 + * 绘制复习计划图表 + * 显示未来7天每天需要复习的古诗数量 + */ +drawReviewPlanChart() { + const ctx = wx.createCanvasContext('reviewPlanChart'); + const { canvasWidth, canvasHeight } = this.data; + const data = this.data.复习计划数据; + + // 确保有数据 + if (!data || data.length === 0 || !this.data.hasRecitationRecords) { + // 绘制无数据状态 ctx.setFillStyle('#ffffff'); ctx.fillRect(0, 0, canvasWidth, canvasHeight); - - // 计算坐标转换参数 - const padding = 40; - const plotWidth = canvasWidth - 2 * padding; - const plotHeight = canvasHeight - 2 * padding; - const maxCount = Math.max(...data.map(item => item.count), 1); // 至少为1,避免除以0 - - // 绘制坐标轴 - ctx.setStrokeStyle('#e0e0e0'); - ctx.setLineWidth(1); - - // 垂直网格线 - for (let i = 0; i <= 6; i++) { - const x = padding + (plotWidth / 6) * i; - ctx.beginPath(); - ctx.moveTo(x, padding); - ctx.lineTo(x, canvasHeight - padding); - ctx.stroke(); - } - - // 水平网格线 - const maxYAxisValue = Math.ceil(maxCount * 1.2); // 留出20%的顶部空间 - const yAxisSteps = Math.min(5, maxYAxisValue); // 最多5条水平线 - for (let i = 0; i <= yAxisSteps; i++) { - const y = padding + (plotHeight / yAxisSteps) * i; - ctx.beginPath(); - ctx.moveTo(padding, y); - ctx.lineTo(canvasWidth - padding, y); - ctx.stroke(); - } - - // 绘制坐标轴标签 - ctx.setFillStyle('#666666'); - ctx.setFontSize(12); + ctx.setFillStyle('#999999'); + ctx.setFontSize(14); ctx.setTextAlign('center'); + ctx.fillText('暂无复习计划数据', canvasWidth / 2, canvasHeight / 2); + ctx.draw(); + return; + } + + // 设置画布背景 + ctx.setFillStyle('#ffffff'); + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + // 计算坐标转换参数 + const padding = 40; + const plotWidth = canvasWidth - 2 * padding; + const plotHeight = canvasHeight - 2 * padding; + + // 计算Y轴最大值(至少为1,避免除以0) + const maxCount = Math.max(...data.map(item => item.count), 1); + const maxYAxisValue = Math.max(maxCount, 5); // 确保Y轴至少有5的刻度,避免只有1个数据点时图表太扁 + + // 绘制坐标轴 + ctx.setStrokeStyle('#e0e0e0'); + ctx.setLineWidth(1); + + // 垂直网格线 - 修正:当只有1个数据点时也要正确绘制 + const dataPoints = data.length; + const xStep = dataPoints > 1 ? plotWidth / (dataPoints - 1) : plotWidth; + + for (let i = 0; i < dataPoints; i++) { + const x = padding + (dataPoints > 1 ? (plotWidth / (dataPoints - 1)) * i : 0); + ctx.beginPath(); + ctx.moveTo(x, padding); + ctx.lineTo(x, canvasHeight - padding); + ctx.stroke(); + } + + // 水平网格线 + const yAxisSteps = Math.min(5, maxYAxisValue); + for (let i = 0; i <= yAxisSteps; i++) { + const y = padding + (plotHeight / yAxisSteps) * i; + ctx.beginPath(); + ctx.moveTo(padding, y); + ctx.lineTo(canvasWidth - padding, y); + ctx.stroke(); + } + + // 绘制坐标轴标签 + ctx.setFillStyle('#666666'); + ctx.setFontSize(12); + ctx.setTextAlign('center'); + + // X轴标签(显示日期)- 修正:根据实际数据点数量计算位置 + data.forEach((item, index) => { + const x = padding + (dataPoints > 1 ? (plotWidth / (dataPoints - 1)) * index : plotWidth / 2); - // X轴标签(显示日期) - data.forEach((item, index) => { - const x = padding + (plotWidth / 6) * index; - - // 绘制日期 - ctx.fillText(item.dateStr, x, canvasHeight - padding + 15); - - // 绘制天数 - ctx.fillText(item.day, x, canvasHeight - padding + 30); - }); + // 绘制日期 + ctx.fillText(item.dateStr, x, canvasHeight - padding + 15); - // Y轴标签 - ctx.setTextAlign('right'); - for (let i = 0; i <= yAxisSteps; i++) { - const count = maxYAxisValue * (1 - i / yAxisSteps); - const y = padding + (plotHeight / yAxisSteps) * i; - ctx.fillText(Math.round(count), padding - 10, y + 5); - } + // 绘制天数 + ctx.fillText(item.day, x, canvasHeight - padding + 30); + }); + + // Y轴标签 + ctx.setTextAlign('right'); + for (let i = 0; i <= yAxisSteps; i++) { + const count = Math.round(maxYAxisValue * (1 - i / yAxisSteps)); + const y = padding + (plotHeight / yAxisSteps) * i; + ctx.fillText(count.toString(), padding - 10, y + 5); + } + + // 绘制数据点和连线 + ctx.setStrokeStyle('#4CAF50'); + ctx.setLineWidth(2); + ctx.beginPath(); + + // 先绘制所有点 + const points = data.map((item, index) => { + const x = padding + (dataPoints > 1 ? (plotWidth / (dataPoints - 1)) * index : plotWidth / 2); + const valueRatio = item.count / maxYAxisValue; + const y = canvasHeight - padding - (valueRatio * plotHeight); - // 绘制曲线图 - if (data.length > 1) { - ctx.setStrokeStyle('#4CAF50'); // 使用绿色作为主色调 - ctx.setLineWidth(2); - ctx.beginPath(); - - data.forEach((item, index) => { - const x = padding + (plotWidth / 6) * index; - const valueRatio = item.count / maxYAxisValue; - const y = canvasHeight - padding - (valueRatio * plotHeight); - - // 绘制点 - ctx.beginPath(); - ctx.arc(x, y, 4, 0, 2 * Math.PI); - ctx.fillStyle = '#4CAF50'; - ctx.fill(); + return { x, y, count: item.count }; + }); + + // 绘制连线(只有在有多个数据点时) + if (dataPoints > 1) { + points.forEach((point, index) => { + if (index === 0) { + ctx.moveTo(point.x, point.y); + } else { + // 使用贝塞尔曲线使线条更平滑 + const prevPoint = points[index - 1]; + const cpx1 = prevPoint.x + (point.x - prevPoint.x) / 2; + const cpy1 = prevPoint.y; + const cpx2 = prevPoint.x + (point.x - prevPoint.x) / 2; + const cpy2 = point.y; - // 连接点形成曲线 - if (index === 0) { - ctx.moveTo(x, y); - } else { - // 使用贝塞尔曲线使线条更平滑 - const prevItem = data[index - 1]; - const prevX = padding + (plotWidth / 6) * (index - 1); - const prevY = canvasHeight - padding - ((prevItem.count / maxYAxisValue) * plotHeight); - - const cpx1 = prevX + (x - prevX) / 2; - const cpy1 = prevY; - const cpx2 = prevX + (x - prevX) / 2; - const cpy2 = y; - - ctx.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y); - } - }); - - ctx.stroke(); - } - - // 绘制每个数据点的值 - ctx.setFillStyle('#333333'); - ctx.setFontSize(12); - ctx.setTextAlign('center'); - - data.forEach((item, index) => { - const x = padding + (plotWidth / 6) * index; - const valueRatio = item.count / maxYAxisValue; - const y = canvasHeight - padding - (valueRatio * plotHeight); - - // 在点的上方显示数值 - ctx.fillText(item.count.toString(), x, y - 10); + ctx.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, point.x, point.y); + } }); - - // 绘制标题 - ctx.setFillStyle('#333333'); - ctx.setFontSize(16); - ctx.setTextAlign('center'); - ctx.fillText('未来7天复习计划', canvasWidth / 2, padding - 10); - - ctx.draw(); - }, + ctx.stroke(); + } - // 删除不再需要的辅助函数,所有功能现在在drawReviewPlanChart中集中实现 + // 绘制数据点 + points.forEach(point => { + ctx.beginPath(); + ctx.arc(point.x, point.y, 4, 0, 2 * Math.PI); + ctx.fillStyle = '#4CAF50'; + ctx.fill(); + }); - + // 绘制每个数据点的值 + ctx.setFillStyle('#333333'); + ctx.setFontSize(12); + ctx.setTextAlign('center'); + + points.forEach(point => { + // 在点的上方显示数值 + ctx.fillText(point.count.toString(), point.x, point.y - 10); + }); + // 绘制标题 + ctx.setFillStyle('#333333'); + ctx.setFontSize(16); + ctx.setTextAlign('center'); + ctx.fillText('未来7天复习计划', canvasWidth / 2, padding - 10); + + // 如果没有数据点连线,显示提示 + if (dataPoints === 1) { + ctx.setFillStyle('#666666'); + ctx.setFontSize(12); + ctx.fillText('(只有今天有复习计划)', canvasWidth / 2, padding - 25); + } + + ctx.draw(); +}, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh() { // 刷新页面数据 - this.drawForgettingCurve(); - wx.stopPullDownRefresh(); + this.loadPoemsToReview().then(() => { + // 数据加载完成后绘制图表 + this.drawReviewPlanChart(); + wx.stopPullDownRefresh(); + }).catch(error => { + console.error('下拉刷新失败:', error); + wx.stopPullDownRefresh(); + }); }, /** diff --git a/pages/review/review.wxml b/pages/review/review.wxml index 1836a6c..3d28840 100644 --- a/pages/review/review.wxml +++ b/pages/review/review.wxml @@ -21,36 +21,38 @@ + - - 今日推荐复习 - - - - - - {{item.title}} - {{item.author}} - 今日必背 - - - - 上次背诵: {{item.daysSinceLastRecite}}天前 - 正确率: {{item.accuracy}}% - 记忆保留率: {{item.retentionRate}}% - 下次复习: {{item.nextReviewInterval}}天后 - - - - - - + + 今日推荐复习 + + + + + + {{item.title}} + {{item.author}} + 今日必背 + + + + 上次背诵: {{item.daysSinceLastRecite}}天前 + 正确率: {{item.accuracy}}% + 记忆保留率: {{item.retentionRate}}% + 下次复习: {{item.daysUntilNextReview}}天后 + 下次复习: 今天 + + + + + - - - - 暂无推荐复习的古诗 - 开始学习新古诗后,系统会自动生成复习计划 - \ No newline at end of file + + + + 暂无推荐复习的古诗 + 开始学习新古诗后,系统会自动生成复习计划 + + \ No newline at end of file diff --git a/pages/search/search.js b/pages/search/search.js index 0ff533b..c250f0b 100644 --- a/pages/search/search.js +++ b/pages/search/search.js @@ -63,10 +63,13 @@ Page({ // 清空输入框 clearInput() { + console.log('clearInput called'); // 添加日志调试 this.setData({ keyword: '', showResults: false, searchResults: [] + }, () => { + console.log('clearInput completed, keyword:', this.data.keyword); // 确认清空完成 }); }, @@ -142,72 +145,9 @@ Page({ icon: 'none' }); this.setData({ isLoading: false }); - - // 如果云函数调用失败,使用模拟数据作为备选 - this.useMockData(keyword); } }, - // 使用模拟数据(备选方案) - useMockData(keyword) { - const mockPoems = [ - { - _id: '1', - title: '静夜思', - author: '李白', - dynasty: '唐代', - content: '床前明月光,疑是地上霜。举头望明月,低头思故乡。' - }, - { - _id: '2', - title: '望庐山瀑布', - author: '李白', - dynasty: '唐代', - content: '日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。' - }, - { - _id: '3', - title: '早发白帝城', - author: '李白', - dynasty: '唐代', - content: '朝辞白帝彩云间,千里江陵一日还。两岸猿声啼不住,轻舟已过万重山。' - }, - { - _id: '4', - title: '春晓', - author: '孟浩然', - dynasty: '唐代', - content: '春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少。' - }, - { - _id: '5', - title: '登鹳雀楼', - author: '王之涣', - dynasty: '唐代', - content: '白日依山尽,黄河入海流。欲穷千里目,更上一层楼。' - }, - ]; - - // 过滤搜索结果 - const results = mockPoems.filter(poem => - poem.title.includes(keyword) || - poem.author.includes(keyword) || - poem.content.includes(keyword) || - poem.dynasty.includes(keyword) - ); - - this.setData({ - searchResults: results, - showResults: true, - total: results.length, - hasMore: false, - isLoading: false - }); - - // 添加到搜索历史 - this.addToHistory(keyword); - }, - // 通过历史记录搜索 searchByHistory(e) { const keyword = e.currentTarget.dataset.keyword; diff --git a/pages/search/search.wxss b/pages/search/search.wxss index b34eb94..290bf96 100644 --- a/pages/search/search.wxss +++ b/pages/search/search.wxss @@ -13,14 +13,7 @@ margin-bottom: 30rpx; } -.search-input-wrapper { - flex: 1; - position: relative; - background: #ffffff; - border-radius: 50rpx; - padding: 20rpx 30rpx; - box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); -} + .search-input { width: 100%; @@ -30,20 +23,28 @@ .clear-btn { position: absolute; - right: 30rpx; + right: 10rpx; top: 50%; transform: translateY(-50%); width: 40rpx; height: 40rpx; - background: #f0f0f0; + background: #ccc; border-radius: 50%; display: flex; align-items: center; justify-content: center; - color: #999; + z-index: 10; /* 确保在最上层 */ +} + +.clear-btn text { + color: #fff; font-size: 24rpx; } +.search-input-wrapper { + position: relative; + flex: 1; +} .search-btn { background: #07c160; color: white; diff --git a/pages/study/study.js b/pages/study/study.js index 6c575e8..2859802 100644 --- a/pages/study/study.js +++ b/pages/study/study.js @@ -14,7 +14,9 @@ Page({ authorInfo: '', loadError: false, poemType: '', - token: '' + token: '', + isPlaying:false,//音频是否正在播放 + currentPlayingType: '' //当前播放音频类型 }, onLoad(options) { @@ -314,41 +316,80 @@ Page({ }, // 语音播报功能(支持不同类型内容的朗读) - speakPoem(e) { - // 获取要朗读的内容类型 - const contentType = e?.currentTarget?.dataset?.type || 'all'; - let textToSpeak = ''; - let title = ''; - - switch(contentType) { - case 'content': - textToSpeak = this.data.originalText; - title = '朗读原文'; - break; - case 'translation': - textToSpeak = this.data.translationText; - title = '朗读译文'; - break; - case 'background': - textToSpeak = this.data.backgroundInfo; - title = '朗读背景'; - break; - case 'author': - textToSpeak = this.data.authorInfo; - title = '朗读作者简介'; - break; - default: - // 默认朗读全部内容(包括题目、作者、原文、译文、背景、作者简介) - textToSpeak = `${this.data.poemTitle},${this.data.poetInfo},${this.data.originalText},${this.data.translationText},${this.data.backgroundInfo},${this.data.authorInfo}`; - title = '语音播报'; - } - - console.log(`准备朗读${contentType}内容:`, textToSpeak); - - // 调用语音合成播放函数 - this.playstart(textToSpeak); - }, +speakPoem(e) { + const contentType = e?.currentTarget?.dataset?.type || 'all'; + + // 如果正在播放相同类型的内容,则停止播放 + if (this.data.isPlaying && this.data.currentPlayingType === contentType) { + this.stopAudio(); + return; + } + + // 如果正在播放其他类型的内容,先停止再播放新的 + if (this.data.isPlaying) { + this.stopAudio(); + // 给一点延迟确保完全停止 + setTimeout(() => { + this.playAudio(contentType); + }, 300); + } else { + this.playAudio(contentType); + } +}, + +// 播放音频 +playAudio(contentType) { + let textToSpeak = ''; + let title = ''; + + switch(contentType) { + case 'content': + textToSpeak = this.data.originalText; + title = '朗读原文'; + break; + case 'translation': + textToSpeak = this.data.translationText; + title = '朗读译文'; + break; + case 'background': + textToSpeak = this.data.backgroundInfo; + title = '朗读背景'; + break; + case 'author': + textToSpeak = this.data.authorInfo; + title = '朗读作者简介'; + break; + default: + textToSpeak = `${this.data.poemTitle},${this.data.poetInfo},${this.data.originalText},${this.data.translationText},${this.data.backgroundInfo},${this.data.authorInfo}`; + title = '语音播报'; + } + + console.log(`准备朗读${contentType}内容:`, textToSpeak); + + // 更新播放状态 + this.setData({ + isPlaying: true, + currentPlayingType: contentType + }); + + // 调用语音合成播放函数 + this.playstart(textToSpeak); +}, +// 停止音频播放 +stopAudio() { + innerAudioContext.stop(); + this.setData({ + isPlaying: false, + currentPlayingType: '' + }); + + wx.showToast({ + title: '已停止播放', + icon: 'success', + duration: 1000 + }); +}, // 分享功能 onShareAppMessage() { const poem = this.data.poem; @@ -373,11 +414,17 @@ Page({ }, onHide() { - + console.log('页面隐藏,停止语音播放'); + if (this.data.isPlaying) { + this.stopAudio(); + } }, onUnload() { - + console.log('页面卸载,停止语音播放'); + if (this.data.isPlaying) { + this.stopAudio(); + } }, onPullDownRefresh() { diff --git a/project.private.config.json b/project.private.config.json index 495197f..498471f 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -1,42 +1,70 @@ { - "libVersion": "3.10.2", - "projectname": "miniprogram-2", - "setting": { - "urlCheck": false, - "coverView": true, - "lazyloadPlaceholderEnable": false, - "skylineRenderEnable": false, - "preloadBackgroundData": false, - "autoAudits": false, - "showShadowRootInWxmlPanel": true, - "compileHotReLoad": true, - "useApiHook": true, - "useApiHostProcess": true, - "useStaticServer": false, - "useLanDebug": false, - "showES6CompileOption": false, - "checkInvalidKey": true, - "ignoreDevUnusedFiles": true, - "bigPackageSizeSupport": false - }, - "condition": { - "miniprogram": { - "list": [ - { - "name": "pages/question/question", - "pathName": "pages/question/question", - "query": "", - "scene": null, - "launchMode": "default" - }, - { - "name": "pages/managePoems/managePoems", - "pathName": "pages/managePoems/managePoems", - "query": "", - "launchMode": "default", - "scene": null - } - ] + "libVersion": "3.10.2", + "projectname": "miniprogram-2", + "setting": { + "urlCheck": false, + "coverView": true, + "lazyloadPlaceholderEnable": false, + "skylineRenderEnable": false, + "preloadBackgroundData": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "compileHotReLoad": true, + "useApiHook": true, + "useApiHostProcess": true, + "useStaticServer": false, + "useLanDebug": false, + "showES6CompileOption": false, + "checkInvalidKey": true, + "ignoreDevUnusedFiles": true, + "bigPackageSizeSupport": false + }, + "condition": { + "miniprogram": { + "list": [ + { + "name": "pages/guiding/guiding", + "pathName": "pages/guiding/guiding", + "query": "", + "scene": null, + "launchMode": "default" + }, + { + "name": "pages/search/search", + "pathName": "pages/search/search", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/review/review", + "pathName": "pages/review/review", + "query": "userInfo=%5Bobject%20Object%5D", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/index/index", + "pathName": "pages/index/index", + "query": "openid=oXrNu16e8evHW3pAyXuBHPL3lsZo", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/recite/recite", + "pathName": "pages/recite/recite", + "query": "id=poet_064&poemData=%257B%2522_id%2522%253A%2522poet_064%2522%252C%2522author%2522%253A%2522%25E6%259B%25B9%25E6%25A4%258D%2522%252C%2522author_info%2522%253A%257B%2522intro%2522%253A%2522%25E6%259B%25B9%25E6%25A4%258D%25EF%25BC%2588192%25E5%25B9%25B4%25E2%2580%2594232%25E5%25B9%25B4%25EF%25BC%2589%25EF%25BC%258C%25E5%25AD%2597%25E5%25AD%2590%25E5%25BB%25BA%25EF%25BC%258C%25E4%25B8%2589%25E5%259B%25BD%25E6%2597%25B6%25E6%259C%259F%25E8%2591%2597%25E5%2590%258D%25E6%2596%2587%25E5%25AD%25A6%25E5%25AE%25B6%25EF%25BC%258C%25E5%25BB%25BA%25E5%25AE%2589%25E6%2596%2587%25E5%25AD%25A6%25E7%259A%2584%25E4%25BB%25A3%25E8%25A1%25A8%25E4%25BA%25BA%25E7%2589%25A9%25E3%2580%2582%25E4%25B8%258E%25E6%259B%25B9%25E6%2593%258D%25E3%2580%2581%25E6%259B%25B9%25E4%25B8%2595%25E5%2590%2588%25E7%25A7%25B0'%25E4%25B8%2589%25E6%259B%25B9'%25E3%2580%2582%2522%252C%2522name%2522%253A%2522%25E6%259B%25B9%25E6%25A4%258D%2522%257D%252C%2522background%2522%253A%2522%25E8%25BF%2599%25E9%25A6%2596%25E8%25AF%2597%25E7%2594%25A8%25E5%2590%258C%25E6%25A0%25B9%25E8%2580%258C%25E7%2594%259F%25E7%259A%2584%25E8%2590%2581%25E5%2592%258C%25E8%25B1%2586%25E6%259D%25A5%25E6%25AF%2594%25E5%2596%25BB%25E5%2590%258C%25E7%2588%25B6%25E5%2585%25B1%25E6%25AF%258D%25E7%259A%2584%25E5%2585%2584%25E5%25BC%259F%25EF%25BC%258C%25E7%2594%25A8%25E8%2590%2581%25E7%2585%258E%25E5%2585%25B6%25E8%25B1%2586%25E6%259D%25A5%25E6%25AF%2594%25E5%2596%25BB%25E5%2590%258C%25E8%2583%259E%25E9%25AA%25A8%25E8%2582%2589%25E7%259A%2584%25E5%2593%25A5%25E5%2593%25A5%25E6%259B%25B9%25E4%25B8%2595%25E6%25AE%258B%25E5%25AE%25B3%25E5%25BC%259F%25E5%25BC%259F%25EF%25BC%258C%25E8%25A1%25A8%25E8%25BE%25BE%25E4%25BA%2586%25E5%25AF%25B9%25E6%259B%25B9%25E4%25B8%2595%25E7%259A%2584%25E5%25BC%25BA%25E7%2583%2588%25E4%25B8%258D%25E6%25BB%25A1%25EF%25BC%258C%25E7%2594%259F%25E5%258A%25A8%25E5%25BD%25A2%25E8%25B1%25A1%25E3%2580%2581%25E6%25B7%25B1%25E5%2585%25A5%25E6%25B5%2585%25E5%2587%25BA%25E5%259C%25B0%25E5%258F%258D%25E6%2598%25A0%25E4%25BA%2586%25E5%25B0%2581%25E5%25BB%25BA%25E7%25BB%259F%25E6%25B2%25BB%25E9%259B%2586%25E5%259B%25A2%25E5%2586%2585%25E9%2583%25A8%25E7%259A%2584%25E6%25AE%258B%25E9%2585%25B7%25E6%2596%2597%25E4%25BA%2589%25E5%2592%258C%25E8%25AF%2597%25E4%25BA%25BA%25E8%2587%25AA%25E8%25BA%25AB%25E5%25A4%2584%25E5%25A2%2583%25E8%2589%25B0%25E9%259A%25BE%25EF%25BC%258C%25E6%25B2%2589%25E9%2583%2581%25E6%2584%25A4%25E6%25BF%2580%25E7%259A%2584%25E6%2580%259D%25E6%2583%25B3%25E6%2584%259F%25E6%2583%2585%25E3%2580%2582%2522%252C%2522content%2522%253A%2522%25E7%2585%25AE%25E8%25B1%2586%25E7%2587%2583%25E8%25B1%2586%25E8%2590%2581%25EF%25BC%258C%25E8%25B1%2586%25E5%259C%25A8%25E9%2587%259C%25E4%25B8%25AD%25E6%25B3%25A3%25E3%2580%2582%255Cn%25E6%259C%25AC%25E6%2598%25AF%25E5%2590%258C%25E6%25A0%25B9%25E7%2594%259F%25EF%25BC%258C%25E7%259B%25B8%25E7%2585%258E%25E4%25BD%2595%25E5%25A4%25AA%25E6%2580%25A5%25EF%25BC%259F%2522%252C%2522dynasty%2522%253A%2522%25E4%25B8%2589%25E5%259B%25BD%2522%252C%2522theme%2522%253A%255B%2522%25E5%2585%2584%25E5%25BC%259F%2522%252C%2522%25E6%2594%25BF%25E6%25B2%25BB%2522%255D%252C%2522title%2522%253A%2522%25E4%25B8%2583%25E6%25AD%25A5%25E8%25AF%2597%2522%252C%2522translation%2522%253A%2522%25E9%2594%2585%25E9%2587%258C%25E7%2585%25AE%25E7%259D%2580%25E8%25B1%2586%25E5%25AD%2590%25EF%25BC%258C%25E8%25B1%2586%25E7%25A7%25B8%25E5%259C%25A8%25E9%2594%2585%25E5%25BA%2595%25E4%25B8%258B%25E7%2587%2583%25E7%2583%25A7%25EF%25BC%258C%25E8%25B1%2586%25E5%25AD%2590%25E5%259C%25A8%25E9%2594%2585%25E9%2587%258C%25E9%259D%25A2%25E5%2593%25AD%25E6%25B3%25A3%25E3%2580%2582%25E8%25B1%2586%25E5%25AD%2590%25E5%2592%258C%25E8%25B1%2586%25E7%25A7%25B8%25E6%259C%25AC%25E6%259D%25A5%25E6%2598%25AF%25E5%2590%258C%25E4%25B8%2580%25E6%259D%25A1%25E6%25A0%25B9%25E4%25B8%258A%25E7%2594%259F%25E9%2595%25BF%25E5%2587%25BA%25E6%259D%25A5%25E7%259A%2584%25EF%25BC%258C%25E8%25B1%2586%25E7%25A7%25B8%25E6%2580%258E%25E8%2583%25BD%25E8%25BF%2599%25E6%25A0%25B7%25E6%2580%25A5%25E8%25BF%25AB%25E5%259C%25B0%25E7%2585%258E%25E7%2586%25AC%25E8%25B1%2586%25E5%25AD%2590%25E5%2591%25A2%25EF%25BC%259F%2522%252C%2522type%2522%253A%2522%25E8%25AF%2597%2522%257D", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/study/study", + "pathName": "pages/study/study", + "query": "id=poet_064", + "launchMode": "default", + "scene": null } + ] } + } } \ No newline at end of file diff --git a/study_helper b/study_helper deleted file mode 160000 index aa1ac34..0000000 --- a/study_helper +++ /dev/null @@ -1 +0,0 @@ -Subproject commit aa1ac34077d7f850e81caa893d9af18074c7ce33