Compare commits
7 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
51b67bd439 | 2 months ago |
|
|
93146f6311 | 2 months ago |
|
|
1560489597 | 2 months ago |
|
|
49ca78c5ef | 2 months ago |
|
|
ef7a21b075 | 2 months ago |
|
|
86972710f8 | 3 months ago |
|
|
259bfc7cbc | 3 months ago |
@ -1,6 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"openapi": [
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
// cloudfunctions/getOpenId/index.js
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({
|
||||
env: cloud.DYNAMIC_CURRENT_ENV
|
||||
})
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const wxContext = cloud.getWXContext()
|
||||
|
||||
return {
|
||||
openid: wxContext.OPENID,
|
||||
appid: wxContext.APPID,
|
||||
unionid: wxContext.UNIONID,
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "getOpenId",
|
||||
"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,14 +0,0 @@
|
||||
{
|
||||
"name": "getPoemDetail",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 341 KiB |
|
After Width: | Height: | Size: 471 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 478 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 452 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 455 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 478 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 468 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 402 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 3.6 MiB |
@ -1,3 +0,0 @@
|
||||
{
|
||||
"codeChanged": true
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
{
|
||||
"files.autoSave": "afterDelay",
|
||||
"workbench.startupEditor": "readme",
|
||||
"explorer.compactFolders": false,
|
||||
"editor.formatOnSave": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/*.crswap": true,
|
||||
".tcb_tmp": true
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
# 云函数空白模板
|
||||
|
||||
此模板为云函数的空白文档,使用 Nodejs 运行环境部署。
|
||||
|
||||
运行后会返回:{"hello":"world"},以及请求参数,环境参数。
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "auth",
|
||||
"version": "1.0.0",
|
||||
"description": "用户认证云函数",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "~2.6.3"
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"codeChanged": false
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
{
|
||||
"files.autoSave": "afterDelay",
|
||||
"workbench.startupEditor": "readme",
|
||||
"explorer.compactFolders": false,
|
||||
"editor.formatOnSave": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/*.crswap": true,
|
||||
".tcb_tmp": true
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
# 云函数空白模板
|
||||
|
||||
此模板为云函数的空白文档,使用 Nodejs 运行环境部署。
|
||||
|
||||
运行后会返回:{"hello":"world"},以及请求参数,环境参数。
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "poemManagement",
|
||||
"version": "1.0.0",
|
||||
"description": "古诗管理云函数",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "~2.6.3"
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
// pages/keyWord/keyWord.js
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
@ -1,2 +0,0 @@
|
||||
<!--pages/keyWord/keyWord.wxml-->
|
||||
<text>pages/keyWord/keyWord.wxml</text>
|
||||
@ -1,10 +0,0 @@
|
||||
<!--pages/login/login.wxml-->
|
||||
<view class="container">
|
||||
<view class="guiding_text">古诗学习助手</view>
|
||||
|
||||
<text wx:if="{{isLoading}}" class="loading_text">正在请求微信授权...</text>
|
||||
<text wx:if="{{errorLoading}}" class="loading_text">微信授权失败,请重新登录</text>
|
||||
|
||||
<button catch:tap="request_login">微信授权登录</button>
|
||||
<text class="login_text2">登录即同意用户协议</text>
|
||||
</view>
|
||||
@ -1,365 +0,0 @@
|
||||
Page({
|
||||
data: {
|
||||
poems: [], // 当前显示的古诗列表
|
||||
searchQuery: '',
|
||||
showModal: false,
|
||||
editingPoem: false,
|
||||
currentPoem: {
|
||||
_id: '',
|
||||
title: '',
|
||||
dynasty: '',
|
||||
author: '',
|
||||
content: '',
|
||||
translation: '',
|
||||
background: '',
|
||||
author_info: {},
|
||||
theme: [],
|
||||
type: '诗'
|
||||
},
|
||||
selectedPoemId: '',
|
||||
isSearchMode: false // 标记当前是否为搜索模式
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.loadInitialPoems();
|
||||
},
|
||||
|
||||
// 加载初始的100条古诗
|
||||
loadInitialPoems() {
|
||||
wx.showLoading({
|
||||
title: '加载中...',
|
||||
});
|
||||
|
||||
wx.cloud.callFunction({
|
||||
name: 'poemManagement',
|
||||
data: {
|
||||
action: 'getInitialPoems'
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.result.success) {
|
||||
const poems = res.result.data.map(item => ({
|
||||
_id: item._id,
|
||||
title: item.title || '',
|
||||
dynasty: item.dynasty || '',
|
||||
author: item.author || '',
|
||||
content: item.content || '',
|
||||
translation: item.translation || '',
|
||||
background: item.background || '',
|
||||
author_info: item.author_info || {},
|
||||
theme: item.theme || [],
|
||||
type: item.type || '诗'
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
poems: poems,
|
||||
isSearchMode: false
|
||||
});
|
||||
} else {
|
||||
throw new Error(res.result.message || '加载失败');
|
||||
}
|
||||
|
||||
wx.hideLoading();
|
||||
}).catch(error => {
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchQuery: e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
onSearch() {
|
||||
const { searchQuery } = this.data;
|
||||
|
||||
if (!searchQuery.trim()) {
|
||||
// 如果搜索框为空,回到初始状态
|
||||
this.loadInitialPoems();
|
||||
return;
|
||||
}
|
||||
|
||||
wx.showLoading({
|
||||
title: '搜索中...',
|
||||
});
|
||||
|
||||
wx.cloud.callFunction({
|
||||
name: 'poemManagement',
|
||||
data: {
|
||||
action: 'searchPoemsManager',
|
||||
searchQuery: searchQuery
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.result.success) {
|
||||
const searchedPoems = res.result.data.map(item => ({
|
||||
_id: item._id,
|
||||
title: item.title || '',
|
||||
dynasty: item.dynasty || '',
|
||||
author: item.author || '',
|
||||
content: item.content || '',
|
||||
translation: item.translation || '',
|
||||
background: item.background || '',
|
||||
author_info: item.author_info || {},
|
||||
theme: item.theme || [],
|
||||
type: item.type || '诗'
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
poems: searchedPoems,
|
||||
isSearchMode: true
|
||||
});
|
||||
} else {
|
||||
throw new Error(res.result.message || '搜索失败');
|
||||
}
|
||||
|
||||
wx.hideLoading();
|
||||
}).catch(error => {
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '搜索失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onPoemSelect(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
this.setData({
|
||||
selectedPoemId: id
|
||||
});
|
||||
},
|
||||
|
||||
onAddPoem() {
|
||||
this.setData({
|
||||
showModal: true,
|
||||
editingPoem: false,
|
||||
currentPoem: {
|
||||
_id: '',
|
||||
title: '',
|
||||
dynasty: '',
|
||||
author: '',
|
||||
content: '',
|
||||
translation: '',
|
||||
background: '',
|
||||
author_info: {},
|
||||
theme: [],
|
||||
type: '诗'
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onEditPoem(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
const poem = this.data.poems.find(p => p._id === id);
|
||||
if (poem) {
|
||||
this.setData({
|
||||
showModal: true,
|
||||
editingPoem: true,
|
||||
currentPoem: { ...poem }
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onDeletePoem(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
const poem = this.data.poems.find(p => p._id === id);
|
||||
|
||||
wx.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除《${poem.title}》吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.deletePoemFromCloud(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.setData({
|
||||
showModal: false
|
||||
});
|
||||
},
|
||||
|
||||
onTitleInput(e) {
|
||||
this.setData({
|
||||
'currentPoem.title': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
onDynastyInput(e) {
|
||||
this.setData({
|
||||
'currentPoem.dynasty': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
onAuthorInput(e) {
|
||||
this.setData({
|
||||
'currentPoem.author': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
onContentInput(e) {
|
||||
this.setData({
|
||||
'currentPoem.content': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
onTranslationInput(e) {
|
||||
this.setData({
|
||||
'currentPoem.translation': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
onBackgroundInput(e) {
|
||||
this.setData({
|
||||
'currentPoem.background': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
deletePoemFromCloud(poemId) {
|
||||
wx.showLoading({
|
||||
title: '删除中...',
|
||||
});
|
||||
|
||||
wx.cloud.callFunction({
|
||||
name: 'poemManagement',
|
||||
data: {
|
||||
action: 'deletePoem',
|
||||
poemId: poemId
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.result.success) {
|
||||
// 从当前显示的列表中移除
|
||||
const updatedPoems = this.data.poems.filter(p => p._id !== poemId);
|
||||
this.setData({
|
||||
poems: updatedPoems
|
||||
});
|
||||
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
throw new Error(res.result.message || '删除失败');
|
||||
}
|
||||
}).catch(error => {
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
savePoem() {
|
||||
const { currentPoem, editingPoem } = this.data;
|
||||
|
||||
if (!currentPoem.title.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入古诗名称',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPoem.dynasty.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入朝代',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPoem.author.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入作者姓名',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPoem.content.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入古诗内容',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
wx.showLoading({
|
||||
title: '保存中...',
|
||||
});
|
||||
|
||||
const saveData = {
|
||||
title: currentPoem.title.trim(),
|
||||
dynasty: currentPoem.dynasty.trim(),
|
||||
author: currentPoem.author.trim(),
|
||||
content: currentPoem.content.trim(),
|
||||
translation: currentPoem.translation ? currentPoem.translation.trim() : '',
|
||||
background: currentPoem.background ? currentPoem.background.trim() : '',
|
||||
author_info: currentPoem.author_info || {},
|
||||
theme: currentPoem.theme || [],
|
||||
type: currentPoem.type || '诗'
|
||||
};
|
||||
|
||||
const cloudFunctionData = {
|
||||
action: editingPoem ? 'updatePoem' : 'addPoem',
|
||||
data: saveData
|
||||
};
|
||||
|
||||
if (editingPoem) {
|
||||
cloudFunctionData.poemId = currentPoem._id;
|
||||
}
|
||||
|
||||
wx.cloud.callFunction({
|
||||
name: 'poemManagement',
|
||||
data: cloudFunctionData
|
||||
}).then(res => {
|
||||
if (res.result.success) {
|
||||
wx.hideLoading();
|
||||
|
||||
this.setData({
|
||||
showModal: false
|
||||
});
|
||||
|
||||
// 重新加载当前视图
|
||||
if (this.data.isSearchMode) {
|
||||
// 如果是搜索模式,重新搜索
|
||||
this.onSearch();
|
||||
} else {
|
||||
// 如果是初始模式,重新加载初始数据
|
||||
this.loadInitialPoems();
|
||||
}
|
||||
|
||||
wx.showToast({
|
||||
title: editingPoem ? '修改成功' : '添加成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
throw new Error(res.result.message || '保存失败');
|
||||
}
|
||||
}).catch(error => {
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
goToPendingQuestion() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/pendingQuestion/pendingQuestion'
|
||||
});
|
||||
},
|
||||
|
||||
goToManagePoems() {
|
||||
// 已经在当前页面
|
||||
}
|
||||
})
|
||||
@ -1,69 +0,0 @@
|
||||
<!--pages/recite/recite.wxml-->
|
||||
<view class="recite-container">
|
||||
<!-- 头部标题 -->
|
||||
<view class="header">
|
||||
<text class="title">背诵《{{poemTitle}}》</text>
|
||||
<text class="subtitle">{{poemAuthor}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 语音识别结果 -->
|
||||
<view class="recognition-section">
|
||||
<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>
|
||||
</view>
|
||||
|
||||
<!-- 正确率显示 -->
|
||||
<view class="result-section" wx:if="{{showResult}}">
|
||||
<!-- 进度条样式正确率显示 -->
|
||||
<view class="accuracy-progress-container">
|
||||
<view class="accuracy-header">
|
||||
<text class="accuracy-title">背诵评分</text>
|
||||
<text class="accuracy-value">{{accuracyRate}}%</text>
|
||||
</view>
|
||||
|
||||
<!-- 背诵时长和时间显示 -->
|
||||
<view class="info-section">
|
||||
<!-- 背诵用时 -->
|
||||
<view class="info-item">
|
||||
<text class="info-label">背诵用时</text>
|
||||
<text class="info-value duration-value">{{reciteDuration}}</text>
|
||||
</view>
|
||||
<!-- 背诵时间 -->
|
||||
<view class="info-item">
|
||||
<text class="info-label">背诵时间</text>
|
||||
<text class="info-value date-value">{{reciteDateTime}}</text>
|
||||
</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>
|
||||
@ -1,264 +0,0 @@
|
||||
/* pages/recite/recite.wxss */
|
||||
.recite-container {
|
||||
padding: 20rpx;
|
||||
background-color: #f8f8f8;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
padding: 30rpx 0;
|
||||
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
|
||||
border-radius: 15rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 原文和识别结果区域 */
|
||||
.original-section,
|
||||
.recognition-section {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
border-radius: 15rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.original-text {
|
||||
font-size: 34rpx;
|
||||
line-height: 1.8;
|
||||
color: #444;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.result-textarea {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 34rpx;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #f9f9f9;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 正确率显示区域 */
|
||||
.result-section {
|
||||
text-align: center;
|
||||
margin: 40rpx 0;
|
||||
}
|
||||
|
||||
/* 进度条样式正确率显示 */
|
||||
.accuracy-progress-container {
|
||||
background: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.accuracy-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 背诵信息显示区域 */
|
||||
.info-section {
|
||||
margin-bottom: 30rpx;
|
||||
padding: 15rpx 0;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 信息项样式 */
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 背诵时长样式 */
|
||||
.duration-value {
|
||||
color: #007AFF;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
/* 背诵时间样式 */
|
||||
.date-value {
|
||||
color: #34C759;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.accuracy-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.accuracy-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 进度条容器 */
|
||||
.progress-container {
|
||||
margin: 40rpx 0;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
/* 进度条背景 */
|
||||
.progress-bg {
|
||||
flex: 1;
|
||||
height: 24rpx;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 进度条填充 */
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 12rpx;
|
||||
transition: width 0.5s ease-in-out, background 0.5s ease;
|
||||
min-width: 0%;
|
||||
}
|
||||
|
||||
/* 评价文字 */
|
||||
.accuracy-comment {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
padding: 20rpx 0;
|
||||
background: #f9f9f9;
|
||||
border-radius: 10rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
/* 检查按钮区域 */
|
||||
.check-section {
|
||||
text-align: center;
|
||||
margin: 40rpx 0;
|
||||
}
|
||||
|
||||
.btn-check {
|
||||
width: 60%;
|
||||
font-size: 36rpx;
|
||||
padding: 20rpx 0;
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 重置按钮 */
|
||||
.btn-reset {
|
||||
margin-top: 30rpx;
|
||||
font-size: 32rpx;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
/* 控制按钮区域 */
|
||||
.control-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 40rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.btn-record-start,
|
||||
.btn-record-stop {
|
||||
width: 280rpx;
|
||||
font-size: 34rpx;
|
||||
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 {
|
||||
max-width: 800rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
@ -1,271 +0,0 @@
|
||||
// pages/search/search.js
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
keyword: '',
|
||||
showResults: false,
|
||||
searchResults: [],
|
||||
searchHistory: [],
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadSearchHistory();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
this.loadSearchHistory();
|
||||
},
|
||||
|
||||
// 从本地存储加载搜索历史
|
||||
loadSearchHistory() {
|
||||
const history = wx.getStorageSync('searchHistory') || [];
|
||||
this.setData({
|
||||
searchHistory: history
|
||||
});
|
||||
},
|
||||
|
||||
// 输入处理
|
||||
onInput(e) {
|
||||
const value = e.detail.value;
|
||||
this.setData({
|
||||
keyword: value
|
||||
});
|
||||
|
||||
// 如果输入为空,隐藏结果
|
||||
if (!value.trim()) {
|
||||
this.setData({
|
||||
showResults: false,
|
||||
searchResults: []
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 清空输入框
|
||||
clearInput() {
|
||||
console.log('clearInput called'); // 添加日志调试
|
||||
this.setData({
|
||||
keyword: '',
|
||||
showResults: false,
|
||||
searchResults: []
|
||||
}, () => {
|
||||
console.log('clearInput completed, keyword:', this.data.keyword); // 确认清空完成
|
||||
});
|
||||
},
|
||||
|
||||
// 执行搜索
|
||||
async doSearch() {
|
||||
const { keyword, page } = this.data;
|
||||
|
||||
if (!keyword.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入搜索关键词',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
if (page === 1) {
|
||||
this.setData({
|
||||
isLoading: true
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用云函数搜索
|
||||
const result = await wx.cloud.callFunction({
|
||||
name: 'poemManagement',
|
||||
data: {
|
||||
action: 'searchPoems',
|
||||
keyword: keyword.trim(),
|
||||
page: page,
|
||||
pageSize: this.data.pageSize
|
||||
}
|
||||
});
|
||||
|
||||
if (result.result.success) {
|
||||
const { poems, total } = result.result.data;
|
||||
|
||||
const newResults = page === 1 ? poems : [...this.data.searchResults, ...poems];
|
||||
|
||||
this.setData({
|
||||
searchResults: newResults,
|
||||
showResults: true,
|
||||
total: total,
|
||||
hasMore: (page * this.data.pageSize) < total,
|
||||
isLoading: false
|
||||
});
|
||||
|
||||
// 添加到搜索历史
|
||||
this.addToHistory(keyword.trim());
|
||||
|
||||
// 显示搜索结果提示
|
||||
if (page === 1 && poems.length > 0) {
|
||||
wx.showToast({
|
||||
title: `找到 ${total} 条结果`,
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: result.result.message || '搜索失败',
|
||||
icon: 'none'
|
||||
});
|
||||
this.setData({ isLoading: false });
|
||||
|
||||
// 如果云函数失败,使用模拟数据作为备选
|
||||
this.useMockData(keyword);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索错误:', error);
|
||||
wx.showToast({
|
||||
title: '搜索失败,请检查网络',
|
||||
icon: 'none'
|
||||
});
|
||||
this.setData({ isLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
// 通过历史记录搜索
|
||||
searchByHistory(e) {
|
||||
const keyword = e.currentTarget.dataset.keyword;
|
||||
this.setData({
|
||||
keyword: keyword,
|
||||
page: 1,
|
||||
searchResults: [],
|
||||
hasMore: true
|
||||
});
|
||||
this.doSearch();
|
||||
},
|
||||
|
||||
// 添加到搜索历史
|
||||
addToHistory(keyword) {
|
||||
let { searchHistory } = this.data;
|
||||
// 移除已存在的相同关键词
|
||||
searchHistory = searchHistory.filter(item => item !== keyword);
|
||||
// 添加到最前面
|
||||
searchHistory.unshift(keyword);
|
||||
// 限制历史记录数量
|
||||
if (searchHistory.length > 10) {
|
||||
searchHistory = searchHistory.slice(0, 10);
|
||||
}
|
||||
// 保存到本地存储
|
||||
wx.setStorageSync('searchHistory', searchHistory);
|
||||
this.setData({
|
||||
searchHistory: searchHistory
|
||||
});
|
||||
},
|
||||
|
||||
// 清除搜索历史
|
||||
clearHistory() {
|
||||
wx.showModal({
|
||||
title: '确认清除',
|
||||
content: '确定要清除所有搜索历史吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.removeStorageSync('searchHistory');
|
||||
this.setData({
|
||||
searchHistory: []
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到学习页面
|
||||
goToStudy(e) {
|
||||
const poem = e.currentTarget.dataset.poem;
|
||||
if (poem && poem._id) {
|
||||
wx.navigateTo({
|
||||
url: `/pages/study/study?id=${poem._id}`
|
||||
});
|
||||
} else {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
if (id) {
|
||||
wx.navigateTo({
|
||||
url: `/pages/study/study?id=${id}`
|
||||
});
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '诗歌信息错误',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({
|
||||
page: 1,
|
||||
searchResults: [],
|
||||
hasMore: true
|
||||
});
|
||||
|
||||
if (this.data.keyword.trim()) {
|
||||
this.doSearch().then(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
} else {
|
||||
wx.stopPullDownRefresh();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.hasMore && !this.data.isLoading) {
|
||||
this.setData({
|
||||
page: this.data.page + 1
|
||||
}, () => {
|
||||
this.doSearch();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
@ -1,70 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
古诗学习助手,用到微信小程序云开发环境# study_helper
|
||||
|
||||
@ -0,0 +1,513 @@
|
||||
/**
|
||||
* 云函数主入口
|
||||
* 处理用户认证相关的各种操作
|
||||
*/
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
/**
|
||||
* 初始化云开发环境
|
||||
*/
|
||||
cloud.init({
|
||||
env: cloud.DYNAMIC_CURRENT_ENV
|
||||
})
|
||||
|
||||
/**
|
||||
* 数据库实例
|
||||
*/
|
||||
const db = cloud.database()
|
||||
|
||||
/**
|
||||
* 云函数主函数
|
||||
* @param {Object} event - 事件参数
|
||||
* @param {string} event.action - 操作类型
|
||||
* @param {Object} context - 上下文对象
|
||||
* @returns {Object} 执行结果
|
||||
*/
|
||||
exports.main = async (event, context) => {
|
||||
const { action } = event
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'loginWithCode':
|
||||
return await handleLoginWithCode(event)
|
||||
case 'registerUser':
|
||||
return await handleRegisterUser(event)
|
||||
case 'getUserByOpenid':
|
||||
return await handleGetUserByOpenid(event)
|
||||
case 'getOpenId':
|
||||
return await getOpenIdByCode(event)
|
||||
case 'checkFavorite': // 检查收藏状态
|
||||
return await handleCheckFavorite(event)
|
||||
case 'addFavorite': // 添加收藏
|
||||
return await handleAddFavorite(event)
|
||||
case 'removeFavorite': // 移除收藏
|
||||
return await handleRemoveFavorite(event)
|
||||
default:
|
||||
return {
|
||||
success: false,
|
||||
message: '未知操作'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('云函数执行错误:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用微信登录code进行登录
|
||||
* @param {Object} event - 事件参数
|
||||
* @param {string} event.code - 微信登录code
|
||||
* @returns {Object} 登录结果
|
||||
*/
|
||||
async function handleLoginWithCode(event) {
|
||||
const { code } = event
|
||||
|
||||
if (!code) {
|
||||
return {
|
||||
success: false,
|
||||
message: '缺少code参数'
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 通过code获取用户openid
|
||||
const authResult = await getOpenIdByCode(code)
|
||||
|
||||
if (!authResult.openid) {
|
||||
return {
|
||||
success: false,
|
||||
message: '获取openid失败'
|
||||
}
|
||||
}
|
||||
|
||||
const openid = authResult.openid
|
||||
|
||||
// 查询数据库中是否存在该用户
|
||||
const userResult = await db.collection('users')
|
||||
.where({
|
||||
openid: openid
|
||||
})
|
||||
.get()
|
||||
|
||||
// 如果用户存在,返回用户信息
|
||||
if (userResult.data.length > 0) {
|
||||
const user = userResult.data[0]
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
userInfo: {
|
||||
_id: user._id,
|
||||
openid: user.openid,
|
||||
nickname: user.nickname,
|
||||
avatarUrl: user.avatarUrl,
|
||||
role: user.role,
|
||||
favorites: finalUser.favorites || []
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 用户不存在,返回未注册状态
|
||||
return {
|
||||
success: false,
|
||||
message: '用户未注册',
|
||||
code: 'USER_NOT_REGISTERED',
|
||||
openid: openid
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('登录处理错误:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: '登录失败'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册新用户
|
||||
* @param {Object} event - 事件参数
|
||||
* @param {string} event.openid - 用户openid
|
||||
* @param {Object} event.userInfo - 用户信息
|
||||
* @returns {Object} 注册结果
|
||||
*/
|
||||
async function handleRegisterUser(event) {
|
||||
const { openid, userInfo } = event
|
||||
|
||||
if (!openid || !userInfo) {
|
||||
return {
|
||||
success: false,
|
||||
message: '缺少必要参数'
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建新用户数据对象
|
||||
const newUser = {
|
||||
openid: openid,
|
||||
nickname: userInfo.nickName || '勤奋的小朋友',
|
||||
avatarUrl: userInfo.avatarUrl || 'defaultAvatar',
|
||||
createdAt: db.serverDate(),
|
||||
updatedAt: db.serverDate(),
|
||||
role: true,
|
||||
favorites: userInfo.favorites || []
|
||||
}
|
||||
|
||||
// 检查用户是否已存在
|
||||
const existingUser = await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.get()
|
||||
|
||||
let userId
|
||||
let finalUser
|
||||
|
||||
if (existingUser.data.length > 0) {
|
||||
// 用户存在,更新信息
|
||||
await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.update({
|
||||
data:{
|
||||
updatedAt: newUser.updatedAt
|
||||
}
|
||||
})
|
||||
userId = existingUser.data[0]._id
|
||||
const updatedUser = await db.collection('users').doc(userId).get()
|
||||
finalUser = updatedUser.data
|
||||
} else {
|
||||
// 用户不存在,创建新用户
|
||||
const addResult = await db.collection('users').add({
|
||||
data: newUser
|
||||
})
|
||||
userId = addResult._id
|
||||
// 获取新创建的用户信息
|
||||
const user = await db.collection('users').doc(addResult._id).get()
|
||||
finalUser = user.data
|
||||
}
|
||||
|
||||
// 返回用户信息
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
userInfo: {
|
||||
_id: userId,
|
||||
openid: finalUser.openid,
|
||||
nickname: finalUser.nickname,
|
||||
avatarUrl: finalUser.avatarUrl,
|
||||
role: finalUser.role,
|
||||
favorites: finalUser.favorites || []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('注册用户错误:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: '注册失败'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过微信登录code获取用户openid
|
||||
* @param {string} code - 微信登录code
|
||||
* @returns {Object} 用户身份信息
|
||||
*/
|
||||
async function getOpenIdByCode(code) {
|
||||
// 在云函数环境中直接获取用户身份信息
|
||||
const wxContext = cloud.getWXContext()
|
||||
|
||||
return {
|
||||
openid: wxContext.OPENID,
|
||||
appid: wxContext.APPID,
|
||||
unionid: wxContext.UNIONID
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据openid获取用户信息
|
||||
* @param {Object} event - 事件参数
|
||||
* @param {string} event.openid - 用户openid
|
||||
* @returns {Object} 用户信息查询结果
|
||||
*/
|
||||
async function handleGetUserByOpenid(event) {
|
||||
const { openid } = event;
|
||||
|
||||
if (!openid) {
|
||||
return {
|
||||
success: false,
|
||||
message: '缺少openid参数'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// 查询用户信息
|
||||
const userResult = await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.get();
|
||||
|
||||
if (userResult.data.length > 0) {
|
||||
const user = userResult.data[0];
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
userInfo: {
|
||||
_id: user._id,
|
||||
openid: user.openid,
|
||||
nickname: user.nickname,
|
||||
avatarUrl: user.avatarUrl,
|
||||
role: user.role,
|
||||
reviewedPoems: user.reviewedPoems || {},
|
||||
learningStats: user.learningStats || {
|
||||
totalLearned: 0,
|
||||
totalReviewed: 0,
|
||||
lastLearnDate: null
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '获取用户信息失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查诗歌是否被用户收藏
|
||||
* @param {Object} event - 事件参数
|
||||
* @param {string} event.poemId - 诗歌ID
|
||||
* @returns {Object} 收藏状态检查结果
|
||||
*/
|
||||
async function handleCheckFavorite(event) {
|
||||
const { poemId } = event;
|
||||
|
||||
if (!poemId) {
|
||||
return {
|
||||
success: false,
|
||||
message: '缺少诗歌ID参数'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户openid
|
||||
const wxContext = cloud.getWXContext();
|
||||
const openid = wxContext.OPENID;
|
||||
|
||||
if (!openid) {
|
||||
return {
|
||||
success: false,
|
||||
message: '未获取到用户信息'
|
||||
};
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
const userResult = await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.get();
|
||||
|
||||
if (userResult.data.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
};
|
||||
}
|
||||
|
||||
const user = userResult.data[0];
|
||||
const favorites = user.favorites || [];
|
||||
|
||||
// 检查诗歌是否在收藏列表中
|
||||
const isFavorite = favorites.some(favorite => favorite.poemId === poemId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
isFavorite: isFavorite
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查收藏状态错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '检查收藏状态失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加诗歌到用户收藏
|
||||
* @param {Object} event - 事件参数
|
||||
* @param {string} event.poemId - 诗歌ID
|
||||
* @param {Object} event.poemData - 诗歌数据
|
||||
* @returns {Object} 添加收藏结果
|
||||
*/
|
||||
async function handleAddFavorite(event) {
|
||||
const { poemId, poemData } = event;
|
||||
|
||||
if (!poemId) {
|
||||
return {
|
||||
success: false,
|
||||
message: '缺少诗歌ID参数'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户openid
|
||||
const wxContext = cloud.getWXContext();
|
||||
const openid = wxContext.OPENID;
|
||||
|
||||
if (!openid) {
|
||||
return {
|
||||
success: false,
|
||||
message: '未获取到用户信息'
|
||||
};
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
const userResult = await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.get();
|
||||
|
||||
if (userResult.data.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
};
|
||||
}
|
||||
|
||||
const user = userResult.data[0];
|
||||
const favorites = user.favorites || [];
|
||||
|
||||
// 检查是否已经收藏
|
||||
const alreadyFavorite = favorites.some(favorite => favorite.poemId === poemId);
|
||||
if (alreadyFavorite) {
|
||||
return {
|
||||
success: false,
|
||||
message: '已经收藏过该诗歌'
|
||||
};
|
||||
}
|
||||
|
||||
// 构建收藏项
|
||||
const favoriteItem = {
|
||||
poemId: poemId,
|
||||
title: poemData?.title || '未知标题',
|
||||
author: poemData?.author || '未知作者',
|
||||
dynasty: poemData?.dynasty || '',
|
||||
favoriteTime: db.serverDate()
|
||||
};
|
||||
|
||||
// 添加到收藏列表
|
||||
favorites.push(favoriteItem);
|
||||
|
||||
// 更新用户收藏列表
|
||||
await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.update({
|
||||
data: {
|
||||
favorites: favorites,
|
||||
updatedAt: db.serverDate()
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '收藏成功'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('添加收藏错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '收藏失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从用户收藏中移除诗歌
|
||||
* @param {Object} event - 事件参数
|
||||
* @param {string} event.poemId - 诗歌ID
|
||||
* @returns {Object} 移除收藏结果
|
||||
*/
|
||||
async function handleRemoveFavorite(event) {
|
||||
const { poemId } = event;
|
||||
|
||||
if (!poemId) {
|
||||
return {
|
||||
success: false,
|
||||
message: '缺少诗歌ID参数'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户openid
|
||||
const wxContext = cloud.getWXContext();
|
||||
const openid = wxContext.OPENID;
|
||||
|
||||
if (!openid) {
|
||||
return {
|
||||
success: false,
|
||||
message: '未获取到用户信息'
|
||||
};
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
const userResult = await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.get();
|
||||
|
||||
if (userResult.data.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
};
|
||||
}
|
||||
|
||||
const user = userResult.data[0];
|
||||
const favorites = user.favorites || [];
|
||||
|
||||
// 过滤掉要移除的诗歌
|
||||
const updatedFavorites = favorites.filter(favorite => favorite.poemId !== poemId);
|
||||
|
||||
// 如果收藏列表没有变化,说明原本就没有收藏
|
||||
if (updatedFavorites.length === favorites.length) {
|
||||
return {
|
||||
success: false,
|
||||
message: '该诗歌不在收藏列表中'
|
||||
};
|
||||
}
|
||||
|
||||
// 更新用户收藏列表
|
||||
await db.collection('users')
|
||||
.where({ openid: openid })
|
||||
.update({
|
||||
data: {
|
||||
favorites: updatedFavorites,
|
||||
updatedAt: db.serverDate()
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '取消收藏成功'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('移除收藏错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '取消收藏失败'
|
||||
};
|
||||
}
|
||||
}
|
||||