1 #1

Merged
p56u3ak8w merged 1 commits from lxy into main 3 months ago

@ -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"

@ -1,5 +0,0 @@
{
"permissions": {
"openapi": []
}
}

@ -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('缺少问题IDquestionId');
}
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;
}
};

@ -1,9 +0,0 @@
{
"name": "answerquestion",
"version": "1.0.0",
"description": "管理员回答问题",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -1,6 +0,0 @@
{
"permissions": {
"openapi": [
]
}
}

@ -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
}
}
}

@ -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"
}
}

@ -1,6 +0,0 @@
{
"permissions": {
"openapi": [
]
}
}

@ -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
}
}
}

@ -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"
}
}

@ -1,9 +0,0 @@
{
"permissions": {
"openapi": [],
"db": {
"cloudbase_env": "cloud1-0g2sr1117862afae",
"auth_mode": "admin"
}
}
}

@ -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)
// 索引创建失败不应该阻止整个初始化过程
}
}

@ -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"
}
}

@ -1,9 +0,0 @@
{
"permissions": {
"openapi": [],
"db": {
"query": true,
"read": true
}
}
}

@ -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
}
}

@ -1,9 +0,0 @@
{
"name": "pendingquestion",
"version": "1.0.0",
"description": "获取待处理问题列表",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -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) {
// 验证必要字段

@ -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}`
});
}

@ -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'
};
}
})
})

@ -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
});
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/

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

@ -1,83 +1,2 @@
<!--pages/keyWord/keyWord.wxml-->
<view class="header">
<text class="title">关键词回答</text>
</view>
<view class="content">
<!-- 问题显示框 -->
<view class="form-group">
<text class="label">问题:</text>
<view class="question-box">
<text class="question-text">{{question}}</text>
</view>
</view>
<!-- 关键词管理区域 -->
<view class="form-group">
<text class="label">关键词管理:</text>
<!-- 当前关键词路径显示 -->
<view class="keyword-path-box">
<text class="path-label">当前路径:</text>
<text class="path-text">{{getCurrentKeywordPath()}}</text>
</view>
<!-- 当前层级关键词选择 -->
<view class="keywords-level-section">
<text class="level-title">第{{currentLevel + 1}}级关键词</text>
<!-- 已有关键词列表 -->
<view class="keywords-list">
<block wx:for="{{availableKeywords}}" wx:key="*this">
<view class="keyword-item" bindtap="selectKeyword" data-keyword="{{item}}">
{{item}}
</view>
</block>
</view>
<!-- 新建关键词区域 -->
<view class="new-keyword-section">
<view class="create-new-btn {{isCreatingNew ? 'active' : ''}}" bindtap="toggleCreateNew">
<text class="btn-icon">+</text>
{{isCreatingNew ? '取消新建' : '新建关键词'}}
</view>
<view wx:if="{{isCreatingNew}}" class="new-keyword-input">
<input
value="{{newKeyword}}"
placeholder="请输入新关键词"
bindinput="onNewKeywordInput"
class="keyword-input"
focus="{{isCreatingNew}}"
/>
<button class="confirm-btn" bindtap="confirmNewKeyword">确认</button>
</view>
</view>
</view>
<!-- 关键词操作按钮 -->
<view class="keyword-actions">
<button wx:if="{{currentLevel > 0}}" class="action-btn back-btn" bindtap="goBack">
返回上一级
</button>
<button class="action-btn reset-btn" bindtap="resetKeywords">
重置关键词
</button>
</view>
</view>
<!-- 答案输入框 -->
<view class="form-group">
<text class="label">答案:</text>
<textarea
class="textarea answer-textarea"
placeholder="请输入问题的答案"
value="{{answer}}"
bindinput="onAnswerInput"
auto-height
/>
</view>
<!-- 提交按钮 -->
<button class="submit-btn" type="primary" bindtap="submitAnswer">提交</button>
</view>
<text>pages/keyWord/keyWord.wxml</text>

@ -1,231 +0,0 @@
/* pages/keyWord/keyWord.wxss */
.header {
text-align: center;
margin-bottom: 15rpx;
}
.title {
font-size: 40rpx;
font-weight: bold;
color: #333;
}
.content {
background-color: #fff;
border-radius: 20rpx;
padding: 30rpx;
}
.form-group {
margin-bottom: 40rpx;
}
.label {
font-size: 32rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 15rpx;
}
.question-box {
background-color: #f9f9f9;
border: 1rpx solid #eee;
border-radius: 10rpx;
padding: 20rpx;
min-height: 100rpx;
}
.question-text {
font-size: 30rpx;
color: #666;
line-height: 44rpx;
}
/* 关键词路径显示 */
.keyword-path-box {
background-color: #f0f8ff;
border: 1rpx solid #d1e9ff;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 30rpx;
}
.path-label {
font-size: 28rpx;
color: #1890ff;
font-weight: bold;
}
.path-text {
font-size: 28rpx;
color: #333;
margin-left: 15rpx;
}
/* 关键词层级区域 */
.keywords-level-section {
background-color: #fafafa;
border: 1rpx solid #f0f0f0;
border-radius: 10rpx;
padding: 25rpx;
margin-bottom: 20rpx;
}
.level-title {
font-size: 28rpx;
color: #666;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
/* 关键词列表 */
.keywords-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-bottom: 25rpx;
}
.keyword-item {
background: #1890ff;
color: white;
padding: 16rpx 32rpx;
border-radius: 8rpx;
font-size: 28rpx;
cursor: pointer;
transition: all 0.3s;
}
.keyword-item:active {
background: #096dd9;
transform: scale(0.95);
}
/* 新建关键词区域 */
.new-keyword-section {
border-top: 1rpx dashed #e8e8e8;
padding-top: 25rpx;
}
.create-new-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx;
border: 2rpx dashed #1890ff;
color: #1890ff;
border-radius: 8rpx;
font-size: 28rpx;
transition: all 0.3s;
}
.create-new-btn.active {
background: #e6f7ff;
border-style: solid;
}
.btn-icon {
margin-right: 10rpx;
font-weight: bold;
}
.new-keyword-input {
display: flex;
gap: 15rpx;
align-items: center;
margin-top: 20rpx;
}
.keyword-input {
flex: 1;
border: 1rpx solid #ddd;
padding: 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
background: white;
}
.confirm-btn {
background: #52c41a;
color: white;
border: none;
padding: 20rpx 30rpx;
border-radius: 8rpx;
font-size: 26rpx;
}
.confirm-btn:active {
background: #389e0d;
}
/* 关键词操作按钮 */
.keyword-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
flex: 1;
border: none;
padding: 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
transition: all 0.3s;
}
.back-btn {
background: #faad14;
color: white;
}
.back-btn:active {
background: #d48806;
}
.reset-btn {
background: #ff4d4f;
color: white;
}
.reset-btn:active {
background: #cf1322;
}
/* 答案输入框 */
.textarea {
width: 100%;
border: 1rpx solid #eee;
border-radius: 10rpx;
padding: 20rpx;
font-size: 30rpx;
color: #333;
min-height: 120rpx;
box-sizing: border-box;
}
.answer-textarea {
min-height: 500rpx;
}
/* 提交按钮 */
.submit-btn {
margin-top: 40rpx;
font-size: 32rpx;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
background: #1890ff;
color: white;
}
.submit-btn:active {
background: #096dd9;
}
/* 去除textarea默认样式 */
input, textarea {
font-family: inherit;
outline: none;
}

@ -142,7 +142,7 @@ Page({
// 根据用户角色决定跳转页面role为false时是管理员跳转到pendingQuestion页面
if (userData.role === false) {
wx.reLaunch({
url: `/pages/pendingQuestion/pendingQuestion?openid=${this.data.openid}`
url: `/pages/managePoems/managePoems?openid=${this.data.openid}`
});
} else {
wx.reLaunch({

@ -89,12 +89,3 @@
</view>
</view>
<!-- 底部导航栏 -->
<view class="tab-bar">
<view class="tab-item" bindtap="goToPendingQuestion">
<text class="tab-text">待解决问题</text>
</view>
<view class="tab-item active" bindtap="goToManagePoems">
<text class="tab-text">管理古诗</text>
</view>
</view>

@ -1,169 +0,0 @@
// pages/pendingQuestion/pendingQuestion.js
Page({
data: {
pendingQuestions: [] // 初始化为空数组
},
onLoad(options) {
this.loadPendingQuestions();
},
onShow() {
// 页面显示时重新加载数据,确保数据最新
this.loadPendingQuestions();
},
/**
* 从Question集合加载待解决问题
*/
async loadPendingQuestions() {
try {
const db = wx.cloud.database();
const _ = db.command;
// 从Question集合获取待解决问题status为0或不存在的问题
const result = await db.collection('Question').where({
status: _.in([0, null, undefined])
}).get();
console.log('待解决问题数据:', result.data);
// 转换数据格式,适配现有界面
const pendingQuestions = result.data.map(item => {
return {
id: item._id, // 使用数据库的_id作为唯一标识
question: item.question || '未知问题', // 使用question字段
createdAt: this.formatTime(item.createTime || item.timestamp || new Date()),
status: item.status || 0 // 记录问题状态
};
});
this.setData({
pendingQuestions: pendingQuestions
});
if (pendingQuestions.length === 0) {
console.log('暂无待解决问题');
}
} catch (error) {
console.error('加载待解决问题失败:', error);
// 处理集合不存在的情况
if (error.errCode === -502005) {
console.log('Question集合不存在请先创建集合');
// 保持空数组,界面会显示"暂无待解决问题"
this.setData({
pendingQuestions: []
});
} else {
wx.showToast({
title: '加载失败',
icon: 'none'
});
}
}
},
/**
* 格式化时间
*/
formatTime(date) {
if (!date) return '未知时间';
const d = new Date(date);
const year = d.getFullYear();
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const day = d.getDate().toString().padStart(2, '0');
const hours = d.getHours().toString().padStart(2, '0');
const minutes = d.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
},
/**
* 跳转到关键词回答页面
*/
goToKeywordAnswer(e) {
const questionId = e.currentTarget.dataset.id;
const question = e.currentTarget.dataset.question;
if (!questionId) {
wx.showToast({
title: '问题ID不存在',
icon: 'none'
});
return;
}
wx.navigateTo({
url: `/pages/keyWord/keyWord?id=${questionId}&question=${encodeURIComponent(question)}`
});
},
/**
* 跳转到待解决问题页面当前页面
*/
goToPendingQuestion() {
// 已经在当前页面,刷新数据
this.loadPendingQuestions();
},
/**
* 跳转到古诗管理页面
*/
goToManagePoems() {
wx.navigateTo({
url: '/pages/managePoems/managePoems'
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
this.loadPendingQuestions().then(() => {
wx.stopPullDownRefresh();
}).catch(() => {
wx.stopPullDownRefresh();
});
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
return {
title: '待解决问题',
path: '/pages/pendingQuestion/pendingQuestion'
};
}
})

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

@ -1,38 +0,0 @@
<!--pages/pendingQuestion/pendingQuestion.wxml-->
<view class="header">
<text class="title">待解决问题</text>
</view>
<view class="question-list">
<view
wx:for="{{pendingQuestions}}"
wx:key="id"
class="question-item"
bindtap="goToKeywordAnswer"
data-id="{{item.id}}"
data-question="{{item.question}}"
>
<view class="question-content">
<text class="question-text">{{item.question}}</text>
</view>
<view class="question-meta">
<text class="question-time">{{item.createdAt}}</text>
<text class="question-arrow"></text>
</view>
</view>
<!-- 空状态 -->
<view wx:if="{{pendingQuestions.length === 0}}" class="empty-state">
<text>暂无待解决问题</text>
</view>
</view>
<!-- 底部导航栏 -->
<view class="tab-bar">
<view class="tab-item active" bindtap="goToPendingQuestion">
<text class="tab-text">待解决问题</text>
</view>
<view class="tab-item" bindtap="goToManagePoems">
<text class="tab-text">管理古诗</text>
</view>
</view>

@ -1,102 +0,0 @@
/* pages/pendingQuestion/pendingQuestion.wxss */
page {
background-color: #f5f5f5;
padding-bottom: 120rpx; /* 为底部导航栏留出空间 */
}
.header {
text-align: center;
margin-bottom: 15rpx;
}
.title {
font-size: 40rpx;
font-weight: bold;
color: #333;
}
.question-list {
background-color: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.question-item {
padding: 30rpx;
border-bottom: 1rpx solid rgb(123, 135, 168);
display: flex;
flex-direction: column;
gap: 15rpx;
}
.question-item:last-child {
border-bottom: none;
}
.question-content {
flex: 1;
}
.question-text {
font-size: 32rpx;
color: #333;
line-height: 48rpx;
}
.question-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.question-time {
font-size: 26rpx;
color: #999;
}
/* 底部导航栏样式 */
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 120rpx;
background-color: #fff;
display: flex;
border-top: 1rpx solid #e0e0e0;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
z-index: 999;
}
.tab-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.tab-item.active {
background-color: #f8f8f8;
}
.tab-text {
font-size: 32rpx;
color: #666;
}
.tab-item.active .tab-text {
color: #07c160;
font-weight: bold;
}
.question-arrow {
font-size: 36rpx;
color: #999;
}
.empty-state {
padding: 80rpx 0;
text-align: center;
color: #999;
font-size: 32rpx;
}

@ -23,80 +23,166 @@ Page({
startReciteTime: 0, // 开始背诵时间
reciteDuration: '', // 背诵时长
reciteDateTime: '', // 背诵时间
openid: '' // 添加 openid
openid: '' ,// 添加 openid
isRecording: false, // 是否正在录音
isProcessing: false, // 是否正在处理语音
recognitionProgress: 0, // 识别进度
lastRecognitionTime: 0, // 上次识别时间(用于防抖)
existingRecordId: null // 新增已存在记录的ID
},
onLoad(options) {
console.log('背诵页面参数:', options);
// 接收从学习页面传递的参数
if (options.poemData) {
try {
const poemData = decodeURIComponent(options.poemData);
const poem = JSON.parse(poemData);
console.log('从学习页面传递的诗歌数据:', poem);
this.setData({
poemId: poem._id || options.id,
poemTitle: poem.title || '',
poemAuthor: poem.author || '',
originalText: poem.content || '' // 直接从传递的数据中获取原文
});
} catch (error) {
console.error('解析诗歌数据失败:', error);
// 如果解析失败尝试使用id获取
if (options.id) {
onLoad(options) {
console.log('背诵页面参数:', options);
// 接收从学习页面传递的参数
if (options.poemData) {
try {
console.log('原始poemData参数:', options.poemData);
// 尝试直接解析
let poemData;
try {
poemData = JSON.parse(options.poemData);
console.log('直接解析成功');
} catch (firstError) {
console.log('直接解析失败尝试URL解码:', firstError);
// 尝试一次URL解码
try {
const decodedOnce = decodeURIComponent(options.poemData);
poemData = JSON.parse(decodedOnce);
console.log('一次URL解码成功');
} catch (secondError) {
console.log('一次URL解码失败尝试二次URL解码:', secondError);
// 尝试二次URL解码处理双重编码的情况
try {
const decodedTwice = decodeURIComponent(decodeURIComponent(options.poemData));
poemData = JSON.parse(decodedTwice);
console.log('二次URL解码成功');
} catch (thirdError) {
console.log('所有解析方式都失败,使用备用方案:', thirdError);
// 使用备用方案
this.fallbackToIdOnly(options);
return;
}
}
}
console.log('从学习页面传递的诗歌数据:', poemData);
this.setData({
poemId: poemData._id || options.id,
poemTitle: poemData.title || '',
poemAuthor: poemData.author || '',
originalText: poemData.content || '' // 直接从传递的数据中获取原文
});
// 设置导航栏标题为诗歌名称
this.setNavigationBarTitle(poemData.title || '背诵');
} catch (error) {
console.error('解析诗歌数据失败:', error);
// 如果解析失败,使用备用方案
this.fallbackToIdOnly(options);
}
}
// 接收从index页面传递的参数
else if (options.id && options.title && options.author) {
try {
const poemTitle = this.safeDecodeURIComponent(options.title);
const poemAuthor = this.safeDecodeURIComponent(options.author);
this.setData({
poemId: options.id
poemId: options.id,
poemTitle: poemTitle,
poemAuthor: poemAuthor
});
// 设置导航栏标题
this.setNavigationBarTitle(poemTitle);
// 获取古诗原文
this.getPoemDetail(options.id);
} catch (error) {
console.error('处理标题和作者参数失败:', error);
this.fallbackToIdOnly(options);
}
}
}
// 接收从index页面传递的参数
else if (options.id && options.title && options.author) {
this.setData({
poemId: options.id,
poemTitle: decodeURIComponent(options.title),
poemAuthor: decodeURIComponent(options.author)
});
// 获取古诗原文
this.getPoemDetail(options.id);
}
// 只传递了id的情况
else if (options.id) {
this.setData({
poemId: options.id
// 只传递了id的情况
else if (options.id) {
this.setData({
poemId: options.id
});
// 获取古诗原文
this.getPoemDetail(options.id);
} else {
console.error('缺少必要的参数');
wx.showToast({
title: '参数错误',
icon: 'none'
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
//获取用户openid
this.getUserOpenId();
//获取storge中的token
let that=this;
wx.getStorage({
key:'expires_in',
success(res){
console.log("缓存中有access_token")
console.log("token失效时间",res.data)
const newT = new Date().getTime();
// 用当前时间和存储的时间判断token是否已过期
if (newT > parseInt(res.data)) {
console.log("token过期重新获取token")
that.getToken();
} else {
console.log("获取本地缓存的token")
that.setData({
token:wx.getStorageSync('access_token')
});
}
},fail(){
console.log("缓存中没有access_token")
that.getToken();
}
});
// 获取古诗原文
this.getPoemDetail(options.id);
}
//获取用户openid
this.getUserOpenId();
//获取storge中的token
let that=this;
wx.getStorage({
key:'expires_in',
success(res){
console.log("缓存中有access_token")
console.log("token失效时间",res.data)
const newT = new Date().getTime();
// 用当前时间和存储的时间判断token是否已过期
if (newT > parseInt(res.data)) {
console.log("token过期重新获取token")
that.getToken();
} else {
console.log("获取本地缓存的token")
that.setData({
token:wx.getStorageSync('access_token')
});
}
},fail(){
console.log("缓存中没有access_token")
that.getToken();
}
});
},
},
// 安全的URL解码函数
safeDecodeURIComponent(str) {
try {
return decodeURIComponent(str);
} catch (error) {
console.log('URL解码失败返回原字符串:', error);
return str;
}
},
// 备用方案只使用ID
fallbackToIdOnly(options) {
console.log('使用备用方案仅通过ID获取数据');
if (options.id) {
this.setData({
poemId: options.id
});
// 获取古诗详情
this.getPoemDetail(options.id);
} else {
wx.showToast({
title: '无法获取诗歌数据',
icon: 'none'
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
},
// 获取用户 openid
async getUserOpenId() {
try {
@ -111,35 +197,82 @@ Page({
console.error('获取openid失败:', error)
}
},
// 保存背诵记录到数据库
// 检查是否已存在该用户该诗歌的记录
async checkExistingRecord(poemId, openid) {
try {
console.log('检查是否已存在记录poemId:', poemId, 'openid:', openid);
const result = await wx.cloud.callFunction({
name: 'reviewManagement',
data: {
action: 'checkExistingRecord',
poemId: poemId,
openid: openid
}
});
console.log('检查记录结果:', result);
if (result && result.result && result.result.success) {
if (result.result.exists) {
console.log('已存在记录记录ID:', result.result.recordId);
this.setData({
existingRecordId: result.result.recordId
});
return true;
} else {
console.log('不存在记录,将创建新记录');
this.setData({
existingRecordId: null
});
return false;
}
} else {
console.log('检查记录失败,默认创建新记录');
this.setData({
existingRecordId: null
});
return false;
}
} catch (error) {
console.error('检查记录时出错:', error);
this.setData({
existingRecordId: null
});
return false;
}
},
// 修改保存背诵记录方法,支持更新已有记录
async saveReviewRecord(reviewData) {
try {
console.log('开始保存背诵记录')
console.log('传入的reviewData完整内容:', JSON.stringify(reviewData))
console.log('传入的reviewData数据结构:', Object.keys(reviewData || {}).join(', '))
console.log('开始保存背诵记录');
console.log('传入的reviewData完整内容:', JSON.stringify(reviewData));
console.log('传入的reviewData数据结构:', Object.keys(reviewData || {}).join(', '));
// 验证必要字段
if (!reviewData || !reviewData.poemId || !reviewData.openid) {
console.error('缺少必要字段')
console.error('缺少必要字段');
wx.showToast({
title: '数据保存失败:缺少必要字段',
icon: 'none'
})
return false
});
return false;
}
// 先检查并删除可能存在的content和score字段
if (reviewData.hasOwnProperty('content')) {
console.warn('前端发现content字段将被删除:', reviewData.content)
delete reviewData.content
console.warn('前端发现content字段将被删除:', reviewData.content);
delete reviewData.content;
}
if (reviewData.hasOwnProperty('score')) {
console.warn('前端发现score字段将被删除:', reviewData.score)
delete reviewData.score
console.warn('前端发现score字段将被删除:', reviewData.score);
delete reviewData.score;
}
// 详细日志记录清理后的数据结构
console.log('前端清理后reviewData的数据结构:', Object.keys(reviewData).join(', '))
console.log('前端清理后reviewData的数据结构:', Object.keys(reviewData).join(', '));
// 创建一个全新的干净对象,不继承任何原型属性,只包含我们需要的字段
const dataToSave = {};
@ -152,54 +285,80 @@ Page({
// 再次检查dataToSave中是否存在content和score字段
if (dataToSave.hasOwnProperty('content')) {
console.error('警告: 前端dataToSave中仍存在content字段将被强制删除!')
console.error('警告: 前端dataToSave中仍存在content字段将被强制删除!');
delete dataToSave.content;
}
if (dataToSave.hasOwnProperty('score')) {
console.error('警告: 前端dataToSave中仍存在score字段将被强制删除!')
console.error('警告: 前端dataToSave中仍存在score字段将被强制删除!');
delete dataToSave.score;
}
// 详细日志记录最终发送的数据
console.log('前端最终发送到数据库的数据结构:', Object.keys(dataToSave).join(', '))
console.log('前端最终发送到数据库的数据:', JSON.stringify(dataToSave))
console.log('前端最终发送到数据库的数据结构:', Object.keys(dataToSave).join(', '));
console.log('前端最终发送到数据库的数据:', JSON.stringify(dataToSave));
// 调用云函数
const result = await wx.cloud.callFunction({
name: 'reviewManagement',
data: {
action: 'addReviewRecord',
reviewData: dataToSave
}
})
// 先检查是否已存在记录
const hasExistingRecord = await this.checkExistingRecord(dataToSave.poemId, dataToSave.openid);
let result;
if (hasExistingRecord && this.data.existingRecordId) {
// 更新已有记录
console.log('更新已有记录记录ID:', this.data.existingRecordId);
result = await wx.cloud.callFunction({
name: 'reviewManagement',
data: {
action: 'updateReviewRecord',
recordId: this.data.existingRecordId,
reviewData: dataToSave
}
});
} else {
// 创建新记录
console.log('创建新记录');
result = await wx.cloud.callFunction({
name: 'reviewManagement',
data: {
action: 'addReviewRecord',
reviewData: dataToSave
}
});
}
// 处理结果
if (result && result.result && result.result.success) {
console.log('背诵记录保存成功:', result.result._id)
console.log('云函数返回的保存字段列表:', result.result.savedFields?.join(', ') || '未知')
return true
console.log('背诵记录保存成功:', result.result._id);
console.log('云函数返回的保存字段列表:', result.result.savedFields?.join(', ') || '未知');
// 如果是新增记录保存记录ID
if (!hasExistingRecord && result.result._id) {
this.setData({
existingRecordId: result.result._id
});
}
return true;
} else {
console.error('背诵记录保存失败:', result.result?.message || '未知错误')
return false
console.error('背诵记录保存失败:', result.result?.message || '未知错误');
return false;
}
} catch (error) {
console.error('保存背诵记录异常:', error)
return false
console.error('保存背诵记录异常:', error);
return false;
}
},
// 计算背诵难度
calculateDifficulty(duration, originalText) {
// 基于原文长度和背诵时间计算难度
const textLength = (originalText || '').length
const avgSpeed = textLength / Math.max(1, duration)
const textLength = (originalText || '').length;
const avgSpeed = textLength / Math.max(1, duration);
if (textLength > 200 || avgSpeed < 0.5) {
return 'difficult'
return 'difficult';
} else if (textLength > 100 || avgSpeed < 1) {
return 'medium'
return 'medium';
} else {
return 'easy'
return 'easy';
}
},
@ -454,13 +613,28 @@ Page({
console.log('重置背诵状态');
// 重置所有相关数据
this.setData({
content: '',
showResult: false,
accuracyRate: 0,
startReciteTime: 0,
reciteDuration: '',
reciteDateTime: ''
content: '',
showResult: false,
accuracyRate: 0,
startReciteTime: 0,
reciteDuration: '',
reciteDateTime: '',
isRecording: false,
isProcessing: false,
recognitionProgress: 0
// 注意:不重置 existingRecordId因为重新背诵时还是同一首诗
});
// 停止所有可能的录音
try {
recorderManager.stop();
} catch (e) {
console.log('停止录音时出错:', e);
}
wx.hideLoading();
wx.hideToast();
console.log('重置完成,准备新的背诵');
},
@ -508,114 +682,254 @@ Page({
},
//开始录音
touchStart: function () {
const that = this; // 保存this引用
const that = this;
// 防抖处理,避免快速重复点击
const now = Date.now();
if (now - this.data.lastRecognitionTime < 2000) {
console.log("操作过于频繁,请稍后再试");
return;
}
wx.authorize({
scope: 'scope.record',
success() {
console.log("录音授权成功");
// 记录开始背诵的时间
const startTime = new Date().getTime();
console.log('开始背诵时间:', startTime);
that.setData({
startReciteTime: startTime
});
recorderManager.start(options);
recorderManager.onStart(() => {
console.log('recorder start')
});
},
fail() {
console.log("录音失败");
}
})
scope: 'scope.record',
success() {
console.log("录音授权成功");
// 记录开始背诵的时间
const startTime = new Date().getTime();
console.log('开始背诵时间:', startTime);
that.setData({
startReciteTime: startTime,
isRecording: true,
isProcessing: false,
recognitionProgress: 0,
lastRecognitionTime: now
});
// 显示录音中状态
wx.showToast({
title: '录音中...',
icon: 'none',
duration: 60000 // 最长60秒
});
recorderManager.start(options);
recorderManager.onStart(() => {
console.log('recorder start');
that.setData({
isRecording: true
});
});
// 开始进度模拟(让用户感知进度)
that.startProgressSimulation();
},
fail() {
console.log("录音授权失败");
wx.showToast({
title: '需要麦克风权限',
icon: 'none'
});
}
});
},
//停止录音
touchEnd: function () {
let that = this
let that = this;
if (!this.data.isRecording) {
return;
}
this.setData({
isRecording: false,
isProcessing: true,
recognitionProgress: 50 // 切换到处理阶段
});
// 隐藏录音Toast显示处理Toast
wx.hideToast();
wx.showLoading({
title: '识别中...',
mask: true
});
recorderManager.stop();
recorderManager.onStop((res) => {
console.log('文件路径==', res)
tempFilePath= res.tempFilePath;
//获取文件长度
wx.getFileSystemManager().getFileInfo({
filePath: tempFilePath,
success: function (res) {
filesize = res.size
console.log('文件长度', res)
that.shibie()
}, fail: function (res) {
console.log("读取文件长度错误",res);
}
})
console.log('录音停止,文件路径:', res.tempFilePath);
tempFilePath = res.tempFilePath;
// 更新进度
that.setData({
recognitionProgress: 70
});
// 获取文件长度并开始识别
wx.getFileSystemManager().getFileInfo({
filePath: tempFilePath,
success: function (res) {
filesize = res.size;
console.log('文件长度:', res.size);
that.setData({
recognitionProgress: 80
});
that.shibie();
},
fail: function (res) {
console.log("读取文件长度错误", res);
that.recognitionFailed("文件读取失败");
}
});
});
},
},
//语音识别
shibie(){
let that = this
shibie() {
let that = this;
// 更新进度
this.setData({
recognitionProgress: 90
});
wx.getFileSystemManager().readFile({
filePath: tempFilePath,
encoding: 'base64',
success: function (res) {
wx.request({
url: 'http://vop.baidu.com/server_api',
data: {
token: that.data.token,
cuid: "12_56",
format: 'pcm',
rate: 16000,
channel: 1,
speech: res.data,
len: filesize
},
headers: {
'Content-Type': 'application/json'
},
method: "post",
success: function (res) {
if (res.data.result == '') {
wx.showModal({
title: '提示',
content: '听不清楚,请重新说一遍!',
showCancel: false
})
return;
}
console.log("识别成功==",res.data);
let recognizedText = res.data.result;
console.log("原始识别结果格式:", typeof recognizedText);
console.log("原始识别结果内容:", recognizedText);
// 百度API通常返回数组格式的结果需要正确处理
if (Array.isArray(recognizedText)) {
console.log("处理数组格式结果");
// 百度API的标准格式是返回字符串数组如["识别结果"]
if (recognizedText.length > 0) {
recognizedText = recognizedText[0];
} else {
recognizedText = "";
filePath: tempFilePath,
encoding: 'base64',
success: function (res) {
console.log("开始语音识别请求");
// 设置请求超时
const requestTask = wx.request({
url: 'http://vop.baidu.com/server_api',
data: {
token: that.data.token,
cuid: "12_56",
format: 'pcm',
rate: 16000,
channel: 1,
speech: res.data,
len: filesize
},
headers: {
'Content-Type': 'application/json'
},
method: "post",
timeout: 10000, // 10秒超时
success: function (res) {
wx.hideLoading();
if (res.data.result == '' || !res.data.result) {
that.recognitionFailed('听不清楚,请重新说一遍!');
return;
}
console.log("识别成功:", res.data);
let recognizedText = res.data.result;
// 快速处理识别结果
recognizedText = that.processRecognitionResult(recognizedText);
that.setData({
content: recognizedText,
isProcessing: false,
recognitionProgress: 100
});
console.log('语音识别完成,识别文本:', recognizedText);
// 短暂显示成功状态
wx.showToast({
title: '识别完成',
icon: 'success',
duration: 1000
});
// 自动检查背诵结果(可选)
setTimeout(() => {
that.checkRecitation();
}, 500);
},
fail: function (res) {
that.recognitionFailed('网络请求失败,请重试');
}
}
// 确保最终是字符串格式
if (recognizedText !== null && recognizedText !== undefined) {
recognizedText = String(recognizedText);
} else {
recognizedText = "";
}
console.log("最终处理后的识别文本:", recognizedText);
that.setData({
content: recognizedText
});
console.log('语音识别完成,识别文本:', recognizedText);
},
fail: function (res) {
console.log("失败",res);
}
}); //语音识别结束
}
})
}
});
// 可以取消请求(如果需要)
// that.requestTask = requestTask;
},
fail: function (res) {
that.recognitionFailed('文件读取失败');
}
});
},
// 快速处理识别结果
processRecognitionResult(recognizedText) {
if (Array.isArray(recognizedText)) {
// 百度API的标准格式是返回字符串数组如["识别结果"]
if (recognizedText.length > 0) {
// 直接取第一个结果,不进行复杂处理
recognizedText = recognizedText[0];
} else {
recognizedText = "";
}
}
// 确保是字符串
if (recognizedText !== null && recognizedText !== undefined) {
recognizedText = String(recognizedText);
} else {
recognizedText = "";
}
// 简单的文本清理(去除明显错误字符)
recognizedText = recognizedText.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s""''()《》]/g, '');
return recognizedText;
},
// 开始进度模拟
startProgressSimulation() {
let progress = 0;
const interval = setInterval(() => {
if (!this.data.isRecording && !this.data.isProcessing) {
clearInterval(interval);
return;
}
if (this.data.isRecording) {
// 录音阶段缓慢增长到40%
progress = Math.min(40, progress + 1);
}
this.setData({
recognitionProgress: progress
});
if (progress >= 100) {
clearInterval(interval);
}
}, 100);
},
// 识别失败处理
recognitionFailed(message) {
wx.hideLoading();
this.setData({
isRecording: false,
isProcessing: false,
recognitionProgress: 0
});
wx.showModal({
title: '提示',
content: message,
showCancel: false,
success: () => {
// 重置状态
this.resetRecitation();
}
});
}
})

@ -11,7 +11,27 @@
<text class="section-title">识别结果:</text>
<textarea class="result-textarea" placeholder='等待说话...' value='{{content}}' bindinput="onContentInput"></textarea>
</view>
<!-- 只在识别中显示进度条 -->
<view class="recognition-progress" wx:if="{{isProcessing}}">
<view class="progress-bar">
<view class="progress-inner" style="{{'width: ' + recognitionProgress + '%;'}}"></view>
</view>
<text class="progress-text">
识别中... {{recognitionProgress}}%
</text>
</view>
<!-- 简化录音按钮 -->
<button
class="record-btn {{isRecording ? 'recording' : ''}} {{isProcessing ? 'processing' : ''}}"
bindtouchstart="touchStart"
bindtouchend="touchEnd"
disabled="{{isProcessing}}"
>
{{isRecording ? '录音中...' : isProcessing ? '处理中...' : '按住录音'}}
</button>
<!-- 检查按钮 -->
<view class="check-section">
<button class="btn-check" type="primary" bindtap="checkRecitation" wx:if="{{content && !showResult}}">检查背诵</button>
@ -40,25 +60,10 @@
</view>
</view>
<!-- 进度条 -->
<view class="progress-bar">
<view class="progress-bg">
<view class="progress-fill"
style="width: {{accuracyRate}}%; background: {{accuracyRate >= 90 ? '#4CAF50' : accuracyRate >= 70 ? '#FFC107' : accuracyRate >= 50 ? '#FF9800' : '#F44336'}};">
</view>
</view>
</view>
<!-- 评价文字 -->
<text class="accuracy-comment">{{accuracyRate >= 90 ? '优秀!背诵得非常准确' : accuracyRate >= 70 ? '良好!继续保持' : accuracyRate >= 50 ? '一般,还需要练习' : '需要多加练习'}}</text>
</view>
<button class="btn-reset" type="default" bindtap="resetRecitation">重新背诵</button>
</view>
<!-- 录音控制按钮 -->
<view class="control-buttons" wx:if="{{!showResult}}">
<button class="btn-record-start" type="primary" size="mini" bindtap="touchStart">开始录音</button>
<button class="btn-record-stop" type="warn" size="mini" bindtap="touchEnd">结束录音</button>
</view>
</view>

@ -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 {

@ -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();
});
},
/**

@ -21,36 +21,38 @@
</view>
</view>
</view>
<!-- 复习推荐列表 -->
<view class="review-section">
<view class="section-title">今日推荐复习</view>
<block wx:if="{{poemsToReview.length > 0}}">
<view class="poem-list">
<view wx:for="{{poemsToReview}}" wx:key="id" class="poem-item {{item.needsReviewToday ? 'urgent' : ''}}">
<view class="poem-header">
<text class="poem-title">{{item.title}}</text>
<text class="poem-author">{{item.author}}</text>
<text class="review-tag" wx:if="{{item.needsReviewToday}}">今日必背</text>
</view>
<view class="poem-meta">
<text class="meta-item">上次背诵: {{item.daysSinceLastRecite}}天前</text>
<text class="meta-item">正确率: {{item.accuracy}}%</text>
<text class="meta-item retention-rate" wx:if="{{item.retentionRate}}">记忆保留率: {{item.retentionRate}}%</text>
<text class="meta-item next-review" wx:if="{{!item.needsReviewToday && item.nextReviewInterval}}">下次复习: {{item.nextReviewInterval}}天后</text>
</view>
<view class="action-buttons">
<button class="study-btn" data-id="{{item.id}}" data-title="{{item.title}}" bindtap="goToStudy">学习</button>
<button class="recite-btn" data-id="{{item.id}}" data-title="{{item.title}}" data-author="{{item.author}}" bindtap="goToRecite">背诵</button>
</view>
<view class="review-section">
<view class="section-title">今日推荐复习</view>
<block wx:if="{{poemsToReview.length > 0}}">
<view class="poem-list">
<view wx:for="{{poemsToReview}}" wx:key="id" class="poem-item {{item.needsReviewToday ? 'urgent' : ''}}">
<view class="poem-header">
<text class="poem-title">{{item.title}}</text>
<text class="poem-author">{{item.author}}</text>
<text class="review-tag" wx:if="{{item.needsReviewToday}}">今日必背</text>
</view>
<view class="poem-meta">
<text class="meta-item">上次背诵: {{item.daysSinceLastRecite}}天前</text>
<text class="meta-item">正确率: {{item.accuracy}}%</text>
<text class="meta-item retention-rate" wx:if="{{item.retentionRate}}">记忆保留率: {{item.retentionRate}}%</text>
<text class="meta-item next-review" wx:if="{{!item.needsReviewToday && item.daysUntilNextReview > 0}}">下次复习: {{item.daysUntilNextReview}}天后</text>
<text class="meta-item next-review" wx:if="{{item.needsReviewToday}}">下次复习: 今天</text>
</view>
<view class="action-buttons">
<button class="study-btn" data-id="{{item.id}}" data-title="{{item.title}}" bindtap="goToStudy">学习</button>
<button class="recite-btn" data-id="{{item.id}}" data-title="{{item.title}}" data-author="{{item.author}}" bindtap="goToRecite">背诵</button>
</view>
</view>
</block>
<view wx:else class="empty-state">
<text>暂无推荐复习的古诗</text>
<text class="hint">开始学习新古诗后,系统会自动生成复习计划</text>
</view>
</view>
</block>
<view wx:else class="empty-state">
<text>暂无推荐复习的古诗</text>
<text class="hint">开始学习新古诗后,系统会自动生成复习计划</text>
</view>
</view>

@ -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;

@ -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;

@ -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() {

@ -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
}
]
}
}
}

@ -1 +0,0 @@
Subproject commit aa1ac34077d7f850e81caa893d9af18074c7ce33
Loading…
Cancel
Save