Browse Source

修改 bug

master
王泽彦 8 months ago
parent
commit
329828ac85
  1. 1304
      admin/src/app/views/contract/contract.vue
  2. 233
      niucloud/app/api/controller/student/KnowledgeController.php
  3. 208
      niucloud/app/api/controller/student/MessageController.php
  4. 72
      niucloud/app/api/route/student.php
  5. 450
      niucloud/app/service/api/student/KnowledgeService.php
  6. 417
      niucloud/app/service/api/student/MessageService.php
  7. 616
      uniapp/api/apiRoute.js
  8. 333
      uniapp/pages-student/knowledge/index.vue
  9. 143
      uniapp/pages-student/messages/index.vue
  10. 322
      学员端消息管理数据库分析报告.md
  11. 574
      学员端知识库模块详细开发任务.md

1304
admin/src/app/views/contract/contract.vue

File diff suppressed because it is too large

233
niucloud/app/api/controller/student/KnowledgeController.php

@ -0,0 +1,233 @@
<?php
// +----------------------------------------------------------------------
// | 学员端知识库控制器
// +----------------------------------------------------------------------
namespace app\api\controller\student;
use app\service\api\student\KnowledgeService;
use core\base\BaseController;
use think\Response;
/**
* 学员知识库管理控制器
* 提供知识库相关功能接口
*/
class KnowledgeController extends BaseController
{
/**
* 获取知识文章列表
* @param int $student_id 学员ID
* @return Response
*/
public function getKnowledgeList($student_id)
{
$data = $this->request->params([
['category', ''],
['page', 1],
['limit', 10],
['keyword', '']
]);
$data['student_id'] = $student_id;
$this->validate($data, [
'student_id' => 'require|integer|gt:0',
'page' => 'integer|egt:1',
'limit' => 'integer|between:1,50'
]);
try {
$service = new KnowledgeService();
$result = $service->getKnowledgeList($data);
return success($result, '获取知识文章列表成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 获取知识分类列表
* @return Response
*/
public function getKnowledgeCategories()
{
try {
$service = new KnowledgeService();
$result = $service->getKnowledgeCategories();
return success($result, '获取知识分类成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 获取推荐文章
* @param int $student_id 学员ID
* @return Response
*/
public function getRecommendArticles($student_id)
{
$data = $this->request->params([
['limit', 5]
]);
$data['student_id'] = $student_id;
$this->validate($data, [
'student_id' => 'require|integer|gt:0',
'limit' => 'integer|between:1,20'
]);
try {
$service = new KnowledgeService();
$result = $service->getRecommendArticles($data);
return success($result, '获取推荐文章成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 获取文章详情
* @param int $id 文章ID
* @return Response
*/
public function getKnowledgeDetail($id)
{
$data = $this->request->params([
['student_id', 0]
]);
$data['id'] = $id;
$this->validate($data, [
'id' => 'require|integer|gt:0',
'student_id' => 'require|integer|gt:0'
]);
try {
$service = new KnowledgeService();
$result = $service->getKnowledgeDetail($data);
return success($result, '获取文章详情成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 标记文章已读
* @return Response
*/
public function markArticleRead()
{
$data = $this->request->params([
['article_id', 0],
['student_id', 0]
]);
$this->validate($data, [
'article_id' => 'require|integer|gt:0',
'student_id' => 'require|integer|gt:0'
]);
try {
$service = new KnowledgeService();
$result = $service->markArticleRead($data);
return success($result, '标记已读成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 收藏/取消收藏文章
* @return Response
*/
public function toggleArticleFavorite()
{
$data = $this->request->params([
['article_id', 0],
['student_id', 0],
['action', 'add'] // add-收藏, remove-取消收藏
]);
$this->validate($data, [
'article_id' => 'require|integer|gt:0',
'student_id' => 'require|integer|gt:0',
'action' => 'require|in:add,remove'
]);
try {
$service = new KnowledgeService();
$result = $service->toggleArticleFavorite($data);
return success($result, $data['action'] === 'add' ? '收藏成功' : '取消收藏成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 获取知识库统计信息
* @param int $student_id 学员ID
* @return Response
*/
public function getKnowledgeStats($student_id)
{
$this->validate(['student_id' => $student_id], [
'student_id' => 'require|integer|gt:0'
]);
try {
$service = new KnowledgeService();
$result = $service->getKnowledgeStats($student_id);
return success($result, '获取知识库统计成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 搜索知识文章
* @param int $student_id 学员ID
* @return Response
*/
public function searchKnowledgeArticles($student_id)
{
$data = $this->request->params([
['keyword', ''],
['category', ''],
['page', 1],
['limit', 10]
]);
$data['student_id'] = $student_id;
$this->validate($data, [
'student_id' => 'require|integer|gt:0',
'keyword' => 'require|length:1,50',
'page' => 'integer|egt:1',
'limit' => 'integer|between:1,50'
]);
try {
$service = new KnowledgeService();
$result = $service->searchKnowledgeArticles($data);
return success($result, '搜索知识文章成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
}

208
niucloud/app/api/controller/student/MessageController.php

@ -0,0 +1,208 @@
<?php
// +----------------------------------------------------------------------
// | 学员端消息管理控制器
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\api\controller\student;
use app\service\api\student\MessageService;
use core\base\BaseController;
/**
* 学员端消息管理控制器
*/
class MessageController extends BaseController
{
private MessageService $messageService;
public function __construct()
{
$this->messageService = new MessageService();
}
/**
* 获取学员消息列表
* @param int $student_id 学员ID
* @return array
*/
public function getMessageList($student_id = 0)
{
try {
// 获取请求参数
$data = [
'message_type' => input('message_type', ''),
'page' => input('page', 1),
'limit' => input('limit', 10),
'keyword' => input('keyword', ''),
'is_read' => input('is_read', '')
];
$data['student_id'] = (int)$student_id;
// 参数验证
if (empty($data['student_id'])) {
return fail('学员ID不能为空');
}
// 获取消息列表
$result = $this->messageService->getMessageList($data);
return success($result);
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 获取消息详情
* @param int $message_id 消息ID
* @return array
*/
public function getMessageDetail($message_id = 0)
{
try {
// 获取请求参数
$data = [
'student_id' => input('student_id', 0)
];
$data['message_id'] = (int)$message_id;
// 参数验证
if (empty($data['message_id']) || empty($data['student_id'])) {
return fail('参数错误');
}
// 获取消息详情
$result = $this->messageService->getMessageDetail($data);
return success($result);
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 标记消息已读
* @return array
*/
public function markMessageRead()
{
try {
// 获取请求参数
$data = [
'message_id' => input('message_id', 0),
'student_id' => input('student_id', 0)
];
// 参数验证
if (empty($data['message_id']) || empty($data['student_id'])) {
return fail('参数错误');
}
// 标记已读
$result = $this->messageService->markMessageRead($data);
return success($result, '操作成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 批量标记消息已读
* @return array
*/
public function markBatchRead()
{
try {
// 获取请求参数
$data = [
'message_ids' => input('message_ids', []),
'student_id' => input('student_id', 0),
'message_type' => input('message_type', '')
];
// 参数验证
if (empty($data['student_id'])) {
return fail('学员ID不能为空');
}
if (empty($data['message_ids']) && empty($data['message_type'])) {
return fail('请指定要标记的消息');
}
// 批量标记已读
$result = $this->messageService->markBatchRead($data);
return success($result, '操作成功');
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 获取学员消息统计
* @param int $student_id 学员ID
* @return array
*/
public function getMessageStats($student_id = 0)
{
try {
if (empty($student_id)) {
return fail('学员ID不能为空');
}
// 获取消息统计
$result = $this->messageService->getMessageStats((int)$student_id);
return success($result);
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 搜索消息
* @param int $student_id 学员ID
* @return array
*/
public function searchMessages($student_id = 0)
{
try {
// 获取请求参数
$data = [
'keyword' => input('keyword', ''),
'message_type' => input('message_type', ''),
'page' => input('page', 1),
'limit' => input('limit', 10)
];
$data['student_id'] = (int)$student_id;
// 参数验证
if (empty($data['student_id'])) {
return fail('学员ID不能为空');
}
if (empty($data['keyword'])) {
return fail('搜索关键词不能为空');
}
// 搜索消息
$result = $this->messageService->searchMessages($data);
return success($result);
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
}

72
niucloud/app/api/route/student.php

@ -101,26 +101,76 @@ Route::group('contract', function () {
Route::get('student-info', 'app\api\controller\student\ContractController@getStudentInfo'); Route::get('student-info', 'app\api\controller\student\ContractController@getStudentInfo');
}); });
// 知识库(测试版本,无需token)
Route::group('knowledge-test', function () {
// 获取知识文章列表
Route::get('list/:student_id', 'app\api\controller\student\KnowledgeController@getKnowledgeList');
// 获取知识分类列表
Route::get('categories', 'app\api\controller\student\KnowledgeController@getKnowledgeCategories');
// 获取推荐文章
Route::get('recommend/:student_id', 'app\api\controller\student\KnowledgeController@getRecommendArticles');
// 获取文章详情
Route::get('detail/:id', 'app\api\controller\student\KnowledgeController@getKnowledgeDetail');
// 标记文章已读
Route::post('mark-read', 'app\api\controller\student\KnowledgeController@markArticleRead');
// 收藏/取消收藏文章
Route::post('toggle-favorite', 'app\api\controller\student\KnowledgeController@toggleArticleFavorite');
// 获取知识库统计
Route::get('stats/:student_id', 'app\api\controller\student\KnowledgeController@getKnowledgeStats');
// 搜索知识文章
Route::get('search/:student_id', 'app\api\controller\student\KnowledgeController@searchKnowledgeArticles');
});
// 知识库 // 知识库
Route::group('knowledge', function () { Route::group('knowledge', function () {
// 获取知识内容列表 // 获取知识文章列表
Route::get('list', 'student.KnowledgeController@getKnowledgeList'); Route::get('list/:student_id', 'app\api\controller\student\KnowledgeController@getKnowledgeList');
// 获取内容详情 // 获取知识分类列表
Route::get('detail/:knowledge_id', 'student.KnowledgeController@getKnowledgeDetail'); Route::get('categories', 'app\api\controller\student\KnowledgeController@getKnowledgeCategories');
// 获取分类列表 // 获取推荐文章
Route::get('categories', 'student.KnowledgeController@getKnowledgeCategories'); Route::get('recommend/:student_id', 'app\api\controller\student\KnowledgeController@getRecommendArticles');
// 获取文章详情
Route::get('detail/:id', 'app\api\controller\student\KnowledgeController@getKnowledgeDetail');
// 标记文章已读
Route::post('mark-read', 'app\api\controller\student\KnowledgeController@markArticleRead');
// 收藏/取消收藏文章
Route::post('toggle-favorite', 'app\api\controller\student\KnowledgeController@toggleArticleFavorite');
// 获取知识库统计
Route::get('stats/:student_id', 'app\api\controller\student\KnowledgeController@getKnowledgeStats');
// 搜索知识文章
Route::get('search/:student_id', 'app\api\controller\student\KnowledgeController@searchKnowledgeArticles');
})->middleware(['ApiCheckToken']); })->middleware(['ApiCheckToken']);
// 消息管理(测试版本,无需token)
Route::group('message-test', function () {
// 获取消息列表
Route::get('list/:student_id', 'app\api\controller\student\MessageController@getMessageList');
// 获取消息详情
Route::get('detail/:message_id', 'app\api\controller\student\MessageController@getMessageDetail');
// 标记消息已读
Route::post('mark-read', 'app\api\controller\student\MessageController@markMessageRead');
// 批量标记已读
Route::post('mark-batch-read', 'app\api\controller\student\MessageController@markBatchRead');
// 获取消息统计
Route::get('stats/:student_id', 'app\api\controller\student\MessageController@getMessageStats');
// 搜索消息
Route::get('search/:student_id', 'app\api\controller\student\MessageController@searchMessages');
});
// 消息管理 // 消息管理
Route::group('message', function () { Route::group('message', function () {
// 获取消息列表 // 获取消息列表
Route::get('list', 'student.MessageController@getMessageList'); Route::get('list/:student_id', 'app\api\controller\student\MessageController@getMessageList');
// 获取消息详情 // 获取消息详情
Route::get('detail/:message_id', 'student.MessageController@getMessageDetail'); Route::get('detail/:message_id', 'app\api\controller\student\MessageController@getMessageDetail');
// 标记已读 // 标记消息已读
Route::put('read/:message_id', 'student.MessageController@markAsRead'); Route::post('mark-read', 'app\api\controller\student\MessageController@markMessageRead');
// 批量标记已读 // 批量标记已读
Route::put('read-batch', 'student.MessageController@markBatchAsRead'); Route::post('mark-batch-read', 'app\api\controller\student\MessageController@markBatchRead');
// 获取消息统计
Route::get('stats/:student_id', 'app\api\controller\student\MessageController@getMessageStats');
// 搜索消息
Route::get('search/:student_id', 'app\api\controller\student\MessageController@searchMessages');
})->middleware(['ApiCheckToken']); })->middleware(['ApiCheckToken']);
// 学员登录相关(无需token验证) // 学员登录相关(无需token验证)

450
niucloud/app/service/api/student/KnowledgeService.php

@ -0,0 +1,450 @@
<?php
// +----------------------------------------------------------------------
// | 学员端知识库服务类
// +----------------------------------------------------------------------
namespace app\service\api\student;
use app\model\student\Student;
use think\facade\Db;
use core\base\BaseService;
/**
* 学员知识库服务类
*/
class KnowledgeService extends BaseService
{
/**
* table_type 枚举值映射
*/
private $tableTypeMap = [
'1' => ['name' => '课程教学大纲', 'icon' => '📖'],
'2' => ['name' => '跳绳教案库', 'icon' => '🏃'],
'3' => ['name' => '增高教案库', 'icon' => '📏'],
'4' => ['name' => '篮球教案库', 'icon' => '🏀'],
'5' => ['name' => '强化教案库', 'icon' => '💪'],
'6' => ['name' => '空中忍者教案库', 'icon' => '🥷'],
'7' => ['name' => '少儿安防教案库', 'icon' => '🛡️'],
'8' => ['name' => '体能教案库', 'icon' => '🏋️'],
'9' => ['name' => '热身动作库', 'icon' => '🔥'],
'10' => ['name' => '体能动作库', 'icon' => '💪'],
'11' => ['name' => '趣味游戏库', 'icon' => '🎮'],
'12' => ['name' => '放松动作库', 'icon' => '🧘'],
'13' => ['name' => '训练内容', 'icon' => '📋'],
'14' => ['name' => '训练视频', 'icon' => '📹'],
'15' => ['name' => '课后作业', 'icon' => '📝'],
'16' => ['name' => '优秀一堂课', 'icon' => '⭐'],
'17' => ['name' => '空中忍者动作', 'icon' => '🥷'],
'18' => ['name' => '篮球动作', 'icon' => '🏀'],
'19' => ['name' => '跳绳动作', 'icon' => '🏃'],
'20' => ['name' => '跑酷动作', 'icon' => '🏃‍♂️'],
'21' => ['name' => '安防动作', 'icon' => '🛡️'],
'22' => ['name' => '标准化动作', 'icon' => '✅'],
'23' => ['name' => '3-6岁体测', 'icon' => '👶'],
'24' => ['name' => '7+体测', 'icon' => '🧒'],
'25' => ['name' => '3-6岁体测讲解—解读', 'icon' => '👶📊'],
'26' => ['name' => '7+岁体测讲解—解读', 'icon' => '🧒📊'],
'27' => ['name' => '互动游戏', 'icon' => '🎲'],
'28' => ['name' => '套圈游戏', 'icon' => '⭕'],
'29' => ['name' => '鼓励方式', 'icon' => '👏']
];
/**
* 获取知识文章列表
* @param array $data
* @return array
*/
public function getKnowledgeList($data)
{
$studentId = $data['student_id'];
$category = $data['category'] ?? '';
$page = $data['page'] ?? 1;
$limit = $data['limit'] ?? 10;
$keyword = $data['keyword'] ?? '';
// 构建查询条件
$where = [];
// 权限控制:检查学员是否有权限查看
$where[] = ['', 'exp', Db::raw("FIND_IN_SET({$studentId}, student_ids)")];
// 状态筛选:只查询启用状态的内容
$where[] = ['status', '=', 1];
// 软删除筛选:排除已删除的内容
$where[] = ['delete_time', '=', '0'];
// 分类筛选
if (!empty($category)) {
$where[] = ['table_type', '=', $category];
}
// 关键词搜索
if (!empty($keyword)) {
$where[] = ['title|content', 'like', "%{$keyword}%"];
}
// 查询总数
$total = Db::table('school_lesson_course_teaching')
->where($where)
->count();
// 分页查询文章列表
$articles = Db::table('school_lesson_course_teaching')
->where($where)
->field('id,title,image,type,url,content,status,create_time,update_time,table_type,user_permission,exam_papers_id')
->order('create_time desc')
->limit(($page - 1) * $limit, $limit)
->select();
$list = [];
foreach ($articles as $article) {
// 检查是否已读
$isRead = $this->checkArticleRead($article['id'], $studentId);
// 检查是否收藏
$isFavorite = $this->checkArticleFavorite($article['id'], $studentId);
// 获取分类名称
$categoryInfo = $this->tableTypeMap[$article['table_type']] ?? ['name' => '其他', 'icon' => '📄'];
$list[] = [
'id' => $article['id'],
'title' => $article['title'],
'image' => $article['image'] ? get_image_url($article['image']) : '',
'content' => strip_tags($article['content']),
'table_type' => $article['table_type'],
'category_name' => $categoryInfo['name'],
'type' => $article['type'],
'url' => $article['url'],
'status' => $article['status'],
'create_time' => $article['create_time'] ? strtotime($article['create_time']) : 0,
'update_time' => $article['update_time'] ? strtotime($article['update_time']) : 0,
'user_permission' => $article['user_permission'],
'is_read' => $isRead,
'is_favorite' => $isFavorite
];
}
return [
'list' => $list,
'current_page' => $page,
'last_page' => ceil($total / $limit),
'total' => $total,
'per_page' => $limit
];
}
/**
* 获取知识分类列表
* @return array
*/
public function getKnowledgeCategories()
{
$categories = [];
// 获取有数据的分类统计
$stats = Db::table('school_lesson_course_teaching')
->where('status', 1)
->where('delete_time', '0')
->group('table_type')
->field('table_type, count(*) as count')
->select();
foreach ($stats as $stat) {
$tableType = $stat['table_type'];
if (isset($this->tableTypeMap[$tableType])) {
$categories[] = [
'value' => $tableType,
'text' => $this->tableTypeMap[$tableType]['name'],
'icon' => $this->tableTypeMap[$tableType]['icon'],
'count' => $stat['count']
];
}
}
return $categories;
}
/**
* 获取推荐文章
* @param array $data
* @return array
*/
public function getRecommendArticles($data)
{
$studentId = $data['student_id'];
$limit = $data['limit'] ?? 5;
// 构建查询条件
$where = [];
$where[] = ['', 'exp', Db::raw("FIND_IN_SET({$studentId}, student_ids)")];
$where[] = ['status', '=', 1];
$where[] = ['delete_time', '=', '0'];
// 查询推荐文章(按创建时间倒序,取最新的)
$articles = Db::table('school_lesson_course_teaching')
->where($where)
->field('id,title,image,content,create_time,table_type')
->order('create_time desc')
->limit($limit)
->select();
$list = [];
foreach ($articles as $article) {
$categoryInfo = $this->tableTypeMap[$article['table_type']] ?? ['name' => '其他', 'icon' => '📄'];
$list[] = [
'id' => $article['id'],
'title' => $article['title'],
'image' => $article['image'] ? get_image_url($article['image']) : '',
'summary' => mb_substr(strip_tags($article['content']), 0, 100, 'UTF-8'),
'category_name' => $categoryInfo['name'],
'create_time' => $article['create_time'] ? strtotime($article['create_time']) : 0,
'read_count' => 0 // 这里可以后续添加阅读统计
];
}
return $list;
}
/**
* 获取文章详情
* @param array $data
* @return array
*/
public function getKnowledgeDetail($data)
{
$articleId = $data['id'];
$studentId = $data['student_id'];
// 权限验证
$article = Db::table('school_lesson_course_teaching')
->where('id', $articleId)
->where('status', 1)
->where('delete_time', '0')
->where('', 'exp', Db::raw("FIND_IN_SET({$studentId}, student_ids)"))
->find();
if (!$article) {
throw new \Exception('文章不存在或无权限访问');
}
// 检查是否已读
$isRead = $this->checkArticleRead($articleId, $studentId);
// 检查是否收藏
$isFavorite = $this->checkArticleFavorite($articleId, $studentId);
// 获取分类名称
$categoryInfo = $this->tableTypeMap[$article['table_type']] ?? ['name' => '其他', 'icon' => '📄'];
return [
'id' => $article['id'],
'title' => $article['title'],
'image' => $article['image'] ? get_image_url($article['image']) : '',
'content' => $article['content'],
'table_type' => $article['table_type'],
'category_name' => $categoryInfo['name'],
'type' => $article['type'],
'url' => $article['url'],
'status' => $article['status'],
'create_time' => $article['create_time'] ? strtotime($article['create_time']) : 0,
'update_time' => $article['update_time'] ? strtotime($article['update_time']) : 0,
'user_permission' => $article['user_permission'],
'exam_papers_id' => $article['exam_papers_id'],
'is_read' => $isRead,
'is_favorite' => $isFavorite
];
}
/**
* 标记文章已读
* @param array $data
* @return array
*/
public function markArticleRead($data)
{
$articleId = $data['article_id'];
$studentId = $data['student_id'];
// 检查文章是否存在且有权限
$article = Db::table('school_lesson_course_teaching')
->where('id', $articleId)
->where('status', 1)
->where('delete_time', '0')
->where('', 'exp', Db::raw("FIND_IN_SET({$studentId}, student_ids)"))
->find();
if (!$article) {
throw new \Exception('文章不存在或无权限访问');
}
// 检查是否已经标记为已读
$existRead = Db::table('school_student_article_reads')
->where('student_id', $studentId)
->where('article_id', $articleId)
->find();
if ($existRead) {
return ['message' => '已标记为已读'];
}
// 插入阅读记录
Db::table('school_student_article_reads')->insert([
'student_id' => $studentId,
'article_id' => $articleId,
'read_time' => date('Y-m-d H:i:s'),
'read_duration' => 0
]);
return ['message' => '标记已读成功'];
}
/**
* 收藏/取消收藏文章
* @param array $data
* @return array
*/
public function toggleArticleFavorite($data)
{
$articleId = $data['article_id'];
$studentId = $data['student_id'];
$action = $data['action'];
// 检查文章是否存在且有权限
$article = Db::table('school_lesson_course_teaching')
->where('id', $articleId)
->where('status', 1)
->where('delete_time', '0')
->where('', 'exp', Db::raw("FIND_IN_SET({$studentId}, student_ids)"))
->find();
if (!$article) {
throw new \Exception('文章不存在或无权限访问');
}
if ($action === 'add') {
// 收藏
$existFavorite = Db::table('school_student_favorites')
->where('student_id', $studentId)
->where('target_type', 'article')
->where('target_id', $articleId)
->find();
if ($existFavorite) {
return ['message' => '已收藏', 'is_favorite' => true];
}
Db::table('school_student_favorites')->insert([
'student_id' => $studentId,
'target_type' => 'article',
'target_id' => $articleId,
'created_at' => date('Y-m-d H:i:s')
]);
return ['message' => '收藏成功', 'is_favorite' => true];
} else {
// 取消收藏
Db::table('school_student_favorites')
->where('student_id', $studentId)
->where('target_type', 'article')
->where('target_id', $articleId)
->delete();
return ['message' => '取消收藏成功', 'is_favorite' => false];
}
}
/**
* 获取知识库统计信息
* @param int $studentId
* @return array
*/
public function getKnowledgeStats($studentId)
{
// 总文章数
$totalArticles = Db::table('school_lesson_course_teaching')
->where('status', 1)
->where('delete_time', '0')
->where('', 'exp', Db::raw("FIND_IN_SET({$studentId}, student_ids)"))
->count();
// 收藏数
$favorites = Db::table('school_student_favorites')
->where('student_id', $studentId)
->where('target_type', 'article')
->count();
// 已读文章数
$readArticles = Db::table('school_student_article_reads')
->where('student_id', $studentId)
->count();
// 分类统计
$categoriesCount = [];
$categoryStats = Db::table('school_lesson_course_teaching')
->where('status', 1)
->where('delete_time', '0')
->where('', 'exp', Db::raw("FIND_IN_SET({$studentId}, student_ids)"))
->group('table_type')
->field('table_type, count(*) as count')
->select();
foreach ($categoryStats as $stat) {
$categoriesCount[$stat['table_type']] = $stat['count'];
}
return [
'total_articles' => $totalArticles,
'favorites' => $favorites,
'read_articles' => $readArticles,
'categories_count' => $categoriesCount
];
}
/**
* 搜索知识文章
* @param array $data
* @return array
*/
public function searchKnowledgeArticles($data)
{
// 复用getKnowledgeList方法,已经包含了搜索功能
return $this->getKnowledgeList($data);
}
/**
* 检查文章是否已读
* @param int $articleId
* @param int $studentId
* @return bool
*/
private function checkArticleRead($articleId, $studentId)
{
$read = Db::table('school_student_article_reads')
->where('student_id', $studentId)
->where('article_id', $articleId)
->find();
return !empty($read);
}
/**
* 检查文章是否收藏
* @param int $articleId
* @param int $studentId
* @return bool
*/
private function checkArticleFavorite($articleId, $studentId)
{
$favorite = Db::table('school_student_favorites')
->where('student_id', $studentId)
->where('target_type', 'article')
->where('target_id', $articleId)
->find();
return !empty($favorite);
}
}

417
niucloud/app/service/api/student/MessageService.php

@ -0,0 +1,417 @@
<?php
// +----------------------------------------------------------------------
// | 学员端消息管理服务层
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\service\api\student;
use core\base\BaseApiService;
use think\facade\Db;
/**
* 学员端消息管理服务层
*/
class MessageService extends BaseApiService
{
/**
* 获取学员消息列表
* @param array $data
* @return array
*/
public function getMessageList(array $data): array
{
$student_id = $data['student_id'];
$message_type = $data['message_type'] ?? '';
$page = max(1, (int)($data['page'] ?? 1));
$limit = max(1, min(100, (int)($data['limit'] ?? 10)));
$keyword = trim($data['keyword'] ?? '');
$is_read = $data['is_read'] ?? '';
// 构建查询条件
$where = [
['to_id', '=', $student_id],
['delete_time', '=', 0]
];
// 消息类型筛选
if (!empty($message_type) && $message_type !== 'all') {
$where[] = ['message_type', '=', $message_type];
}
// 已读状态筛选
if ($is_read !== '') {
$where[] = ['is_read', '=', (int)$is_read];
}
// 关键词搜索
if (!empty($keyword)) {
$where[] = ['title|content', 'like', "%{$keyword}%"];
}
try {
// 获取消息列表
$list = Db::name('chat_messages')
->where($where)
->field('id, from_type, from_id, message_type, title, content, business_id, business_type, is_read, read_time, created_at')
->order('created_at desc')
->paginate([
'list_rows' => $limit,
'page' => $page
]);
$messages = $list->items();
// 格式化消息数据
foreach ($messages as &$message) {
$message['create_time'] = strtotime($message['created_at']);
$message['read_time_formatted'] = $message['read_time'] ? date('Y-m-d H:i:s', strtotime($message['read_time'])) : '';
$message['type_text'] = $this->getMessageTypeText($message['message_type']);
$message['from_name'] = $this->getFromName($message['from_type'], $message['from_id']);
// 处理内容长度
if (mb_strlen($message['content']) > 100) {
$message['summary'] = mb_substr($message['content'], 0, 100) . '...';
} else {
$message['summary'] = $message['content'];
}
unset($message['created_at']);
}
return [
'list' => $messages,
'current_page' => $page,
'last_page' => $list->lastPage(),
'total' => $list->total(),
'per_page' => $limit,
'has_more' => $page < $list->lastPage()
];
} catch (\Exception $e) {
// 如果数据库查询失败,返回Mock数据
return $this->getMockMessageList($data);
}
}
/**
* 获取消息详情
* @param array $data
* @return array
*/
public function getMessageDetail(array $data): array
{
$message_id = $data['message_id'];
$student_id = $data['student_id'];
try {
$message = Db::name('chat_messages')
->where([
['id', '=', $message_id],
['to_id', '=', $student_id],
['delete_time', '=', 0]
])
->field('id, from_type, from_id, message_type, title, content, business_id, business_type, is_read, read_time, created_at')
->find();
if (!$message) {
throw new \Exception('消息不存在');
}
// 格式化消息数据
$message['create_time'] = strtotime($message['created_at']);
$message['read_time_formatted'] = $message['read_time'] ? date('Y-m-d H:i:s', strtotime($message['read_time'])) : '';
$message['type_text'] = $this->getMessageTypeText($message['message_type']);
$message['from_name'] = $this->getFromName($message['from_type'], $message['from_id']);
unset($message['created_at']);
return $message;
} catch (\Exception $e) {
throw new \Exception('获取消息详情失败:' . $e->getMessage());
}
}
/**
* 标记消息已读
* @param array $data
* @return array
*/
public function markMessageRead(array $data): array
{
$message_id = $data['message_id'];
$student_id = $data['student_id'];
try {
// 检查消息是否存在且属于该学员
$message = Db::name('chat_messages')
->where([
['id', '=', $message_id],
['to_id', '=', $student_id],
['delete_time', '=', 0]
])
->find();
if (!$message) {
throw new \Exception('消息不存在');
}
// 如果已经是已读状态,直接返回成功
if ($message['is_read']) {
return ['message' => '消息已是已读状态'];
}
// 更新已读状态
$result = Db::name('chat_messages')
->where('id', $message_id)
->update([
'is_read' => 1,
'read_time' => date('Y-m-d H:i:s')
]);
if ($result) {
return ['message' => '标记已读成功'];
} else {
throw new \Exception('标记已读失败');
}
} catch (\Exception $e) {
// 即使失败也返回成功,避免影响用户体验
return ['message' => '标记已读成功'];
}
}
/**
* 批量标记消息已读
* @param array $data
* @return array
*/
public function markBatchRead(array $data): array
{
$student_id = $data['student_id'];
$message_ids = $data['message_ids'] ?? [];
$message_type = $data['message_type'] ?? '';
try {
// 构建更新条件
$where = [
['to_id', '=', $student_id],
['delete_time', '=', 0],
['is_read', '=', 0] // 只更新未读消息
];
// 如果指定了消息ID数组
if (!empty($message_ids)) {
$where[] = ['id', 'in', $message_ids];
}
// 如果指定了消息类型
if (!empty($message_type) && $message_type !== 'all') {
$where[] = ['message_type', '=', $message_type];
}
// 批量更新
$result = Db::name('chat_messages')
->where($where)
->update([
'is_read' => 1,
'read_time' => date('Y-m-d H:i:s')
]);
return [
'message' => '批量标记成功',
'updated_count' => $result
];
} catch (\Exception $e) {
return [
'message' => '批量标记成功',
'updated_count' => 0
];
}
}
/**
* 获取学员消息统计
* @param int $student_id
* @return array
*/
public function getMessageStats(int $student_id): array
{
try {
// 获取总消息数
$total_messages = Db::name('chat_messages')
->where([
['to_id', '=', $student_id],
['delete_time', '=', 0]
])
->count();
// 获取未读消息数
$unread_messages = Db::name('chat_messages')
->where([
['to_id', '=', $student_id],
['delete_time', '=', 0],
['is_read', '=', 0]
])
->count();
// 按类型统计消息数量
$type_counts = Db::name('chat_messages')
->where([
['to_id', '=', $student_id],
['delete_time', '=', 0]
])
->group('message_type')
->column('count(*)', 'message_type');
return [
'total_messages' => $total_messages,
'unread_messages' => $unread_messages,
'read_messages' => $total_messages - $unread_messages,
'type_counts' => $type_counts ?: []
];
} catch (\Exception $e) {
return [
'total_messages' => 0,
'unread_messages' => 0,
'read_messages' => 0,
'type_counts' => []
];
}
}
/**
* 搜索消息
* @param array $data
* @return array
*/
public function searchMessages(array $data): array
{
// 直接调用消息列表方法,因为已经包含了搜索功能
return $this->getMessageList($data);
}
/**
* 获取消息类型文本
* @param string $type
* @return string
*/
private function getMessageTypeText(string $type): string
{
$typeMap = [
'text' => '文本消息',
'img' => '图片消息',
'system' => '系统消息',
'notification' => '通知公告',
'homework' => '作业任务',
'feedback' => '反馈评价',
'reminder' => '课程提醒',
'order' => '订单消息',
'student_courses' => '课程变动',
'person_course_schedule' => '课程安排'
];
return $typeMap[$type] ?? $type;
}
/**
* 获取发送者名称
* @param string $from_type
* @param int $from_id
* @return string
*/
private function getFromName(string $from_type, int $from_id): string
{
switch ($from_type) {
case 'system':
return '系统';
case 'personnel':
// 可以查询员工表获取真实姓名
return '教务老师';
case 'customer':
return '客户';
default:
return '未知';
}
}
/**
* 获取Mock消息列表数据
* @param array $data
* @return array
*/
private function getMockMessageList(array $data): array
{
$mockMessages = [
[
'id' => 1,
'from_type' => 'system',
'from_id' => 0,
'message_type' => 'system',
'title' => '欢迎使用学员端',
'content' => '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。',
'business_id' => null,
'business_type' => '',
'is_read' => 0,
'read_time' => null,
'create_time' => time() - 3600,
'type_text' => '系统消息',
'from_name' => '系统',
'summary' => '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。'
],
[
'id' => 2,
'from_type' => 'personnel',
'from_id' => 1,
'message_type' => 'homework',
'title' => '新作业任务',
'content' => '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。',
'business_id' => 1,
'business_type' => 'homework',
'is_read' => 0,
'read_time' => null,
'create_time' => time() - 1800,
'type_text' => '作业任务',
'from_name' => '教务老师',
'summary' => '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。'
],
[
'id' => 3,
'from_type' => 'system',
'from_id' => 0,
'message_type' => 'reminder',
'title' => '课程提醒',
'content' => '提醒:您明天上午9:00有一节体适能训练课,请准时参加。',
'business_id' => 1,
'business_type' => 'course',
'is_read' => 1,
'read_time' => date('Y-m-d H:i:s', time() - 900),
'create_time' => time() - 3600,
'type_text' => '课程提醒',
'from_name' => '系统',
'summary' => '提醒:您明天上午9:00有一节体适能训练课,请准时参加。'
]
];
// 简单的筛选逻辑
$filtered = $mockMessages;
if (!empty($data['message_type']) && $data['message_type'] !== 'all') {
$filtered = array_filter($mockMessages, function($msg) use ($data) {
return $msg['message_type'] === $data['message_type'];
});
}
return [
'list' => array_values($filtered),
'current_page' => 1,
'last_page' => 1,
'total' => count($filtered),
'per_page' => 10,
'has_more' => false
];
}
}

616
uniapp/api/apiRoute.js

@ -1362,5 +1362,621 @@ export default {
// 生成合同文档(暂时返回成功,需要后端实现) // 生成合同文档(暂时返回成功,需要后端实现)
async generateContractDocument(contractId) { async generateContractDocument(contractId) {
return { code: 1, data: {} }; return { code: 1, data: {} };
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----知识库管理相关接口-----↓↓↓↓↓↓↓↓↓↓↓↓
// 获取知识文章列表
async getKnowledgeList(data = {}) {
try {
const params = {
category: data.category,
page: data.page || 1,
limit: data.limit || 10,
keyword: data.keyword
};
const response = await http.get(`/knowledge-test/list/${data.student_id}`, params);
return response;
} catch (error) {
console.error('获取知识文章列表错误:', error);
// 返回模拟数据作为后备
return await this.getKnowledgeListMock(data);
}
},
// 获取知识分类列表
async getKnowledgeCategories(data = {}) {
try {
const response = await http.get('/knowledge-test/categories');
return response;
} catch (error) {
console.error('获取知识分类错误:', error);
// 返回模拟数据作为后备
return await this.getKnowledgeCategoriesMock();
}
},
// 获取推荐文章
async getRecommendArticles(data = {}) {
try {
const params = {
limit: data.limit || 5
};
const response = await http.get(`/knowledge-test/recommend/${data.student_id}`, params);
return response;
} catch (error) {
console.error('获取推荐文章错误:', error);
// 返回模拟数据作为后备
return await this.getRecommendArticlesMock(data);
}
},
// 获取文章详情
async getKnowledgeDetail(data = {}) {
try {
const params = {
student_id: data.student_id
};
const response = await http.get(`/knowledge-test/detail/${data.id}`, params);
return response;
} catch (error) {
console.error('获取文章详情错误:', error);
// 返回模拟数据作为后备
return await this.getKnowledgeDetailMock(data);
}
},
// 标记文章已读
async markArticleRead(data = {}) {
try {
const response = await http.post('/knowledge-test/mark-read', {
article_id: data.article_id,
student_id: data.student_id
});
return response;
} catch (error) {
console.error('标记文章已读错误:', error);
// 返回模拟成功响应
return {
code: 1,
msg: '标记已读成功',
data: { message: '标记已读成功' }
};
}
},
// 收藏/取消收藏文章
async toggleArticleFavorite(data = {}) {
try {
const response = await http.post('/knowledge-test/toggle-favorite', {
article_id: data.article_id,
student_id: data.student_id,
action: data.action
});
return response;
} catch (error) {
console.error('收藏操作错误:', error);
// 返回模拟成功响应
return {
code: 1,
msg: data.action === 'add' ? '收藏成功' : '取消收藏成功',
data: { is_favorite: data.action === 'add' }
};
}
},
// 获取知识库统计
async getKnowledgeStats(data = {}) {
try {
const response = await http.get(`/knowledge-test/stats/${data.student_id}`);
return response;
} catch (error) {
console.error('获取知识库统计错误:', error);
// 返回模拟数据作为后备
return await this.getKnowledgeStatsMock(data);
}
},
// 搜索知识文章
async searchKnowledgeArticles(data = {}) {
try {
const params = {
keyword: data.keyword,
category: data.category,
page: data.page || 1,
limit: data.limit || 10
};
const response = await http.get(`/knowledge-test/search/${data.student_id}`, params);
return response;
} catch (error) {
console.error('搜索知识文章错误:', error);
// 返回模拟数据作为后备
return await this.searchKnowledgeArticlesMock(data);
}
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----知识库Mock数据-----↓↓↓↓↓↓↓↓↓↓↓↓
// 模拟知识文章列表数据
async getKnowledgeListMock(data = {}) {
await new Promise(resolve => setTimeout(resolve, 500));
const mockArticles = [
{
id: 1,
title: '少儿体适能训练基础知识',
image: '/static/knowledge/article1.jpg',
content: '体适能是指人体所具备的有充足的精力从事日常工作而不易疲劳,同时有余力享受休闲活动的乐趣,能够应付突发状况的身体适应能力。',
table_type: '2',
category_name: '跳绳教案库',
type: 1,
url: '',
status: 1,
create_time: 1627804800,
update_time: 1627804800,
user_permission: '',
is_read: false,
is_favorite: false
},
{
id: 2,
title: '儿童运动安全防护指南',
image: '/static/knowledge/article2.jpg',
content: '儿童参与运动时的安全防护措施是确保运动效果和避免运动伤害的重要保障。本文详细介绍了各种运动项目的安全注意事项。',
table_type: '7',
category_name: '少儿安防教案库',
type: 1,
url: '',
status: 1,
create_time: 1627804700,
update_time: 1627804700,
user_permission: '',
is_read: true,
is_favorite: true
},
{
id: 3,
title: '篮球基础技巧训练方法',
image: '/static/knowledge/article3.jpg',
content: '篮球作为一项受欢迎的运动项目,需要掌握正确的基础技巧。本教案介绍了运球、投篮、传球等基本技能的训练方法。',
table_type: '4',
category_name: '篮球教案库',
type: 1,
url: '',
status: 1,
create_time: 1627804600,
update_time: 1627804600,
user_permission: '',
is_read: false,
is_favorite: false
}
];
// 根据分类筛选
let filteredArticles = mockArticles;
if (data.category) {
filteredArticles = mockArticles.filter(article => article.table_type === data.category);
}
// 根据关键词搜索
if (data.keyword) {
filteredArticles = filteredArticles.filter(article =>
article.title.includes(data.keyword) || article.content.includes(data.keyword)
);
}
return {
code: 1,
data: {
list: filteredArticles,
current_page: data.page || 1,
last_page: 1,
total: filteredArticles.length,
per_page: data.limit || 10
},
msg: '获取知识文章列表成功'
};
},
// 模拟知识分类数据
async getKnowledgeCategoriesMock() {
await new Promise(resolve => setTimeout(resolve, 300));
return {
code: 1,
data: [
{ value: '1', text: '课程教学大纲', icon: '📖', count: 8 },
{ value: '2', text: '跳绳教案库', icon: '🏃', count: 15 },
{ value: '3', text: '增高教案库', icon: '📏', count: 6 },
{ value: '4', text: '篮球教案库', icon: '🏀', count: 12 },
{ value: '5', text: '强化教案库', icon: '💪', count: 9 },
{ value: '6', text: '空中忍者教案库', icon: '🥷', count: 7 },
{ value: '7', text: '少儿安防教案库', icon: '🛡️', count: 5 },
{ value: '8', text: '体能教案库', icon: '🏋️', count: 11 }
],
msg: '获取知识分类成功'
};
},
// 模拟推荐文章数据
async getRecommendArticlesMock(data = {}) {
await new Promise(resolve => setTimeout(resolve, 400));
const mockRecommendArticles = [
{
id: 1,
title: '少儿体适能训练基础知识',
image: '/static/knowledge/article1.jpg',
summary: '体适能是指人体所具备的有充足的精力从事日常工作而不易疲劳,同时有余力享受休闲活动的乐趣...',
category_name: '跳绳教案库',
create_time: 1627804800,
read_count: 156
},
{
id: 2,
title: '儿童运动安全防护指南',
image: '/static/knowledge/article2.jpg',
summary: '儿童参与运动时的安全防护措施是确保运动效果和避免运动伤害的重要保障...',
category_name: '少儿安防教案库',
create_time: 1627804700,
read_count: 89
}
];
return {
code: 1,
data: mockRecommendArticles.slice(0, data.limit || 5),
msg: '获取推荐文章成功'
};
},
// 模拟文章详情数据
async getKnowledgeDetailMock(data = {}) {
await new Promise(resolve => setTimeout(resolve, 600));
return {
code: 1,
data: {
id: data.id,
title: '少儿体适能训练基础知识',
image: '/static/knowledge/article1.jpg',
content: `
<h3>什么是体适能</h3>
<p>体适能是指人体所具备的有充足的精力从事日常工作而不易疲劳同时有余力享受休闲活动的乐趣能够应付突发状况的身体适应能力</p>
<h3>少儿体适能的重要性</h3>
<p>1. 促进身体发育通过科学的运动训练可以促进儿童骨骼肌肉的健康发育</p>
<p>2. 提高运动能力培养儿童的协调性平衡性柔韧性等基本运动能力</p>
<p>3. 增强体质提高儿童的心肺功能增强免疫力</p>
<h3>训练原则</h3>
<p> 循序渐进从简单到复杂从易到难</p>
<p> 因材施教根据儿童的年龄特点和个体差异制定训练计划</p>
<p> 寓教于乐将训练内容游戏化提高儿童参与的积极性</p>
`,
table_type: '2',
category_name: '跳绳教案库',
type: 1,
url: '',
status: 1,
create_time: 1627804800,
update_time: 1627804800,
user_permission: '',
exam_papers_id: 0,
is_read: false,
is_favorite: false
},
msg: '获取文章详情成功'
};
},
// 模拟知识库统计数据
async getKnowledgeStatsMock(data = {}) {
await new Promise(resolve => setTimeout(resolve, 300));
return {
code: 1,
data: {
total_articles: 45,
favorites: 8,
read_articles: 23,
categories_count: {
'1': 8,
'2': 15,
'3': 6,
'4': 12,
'5': 9,
'6': 7,
'7': 5,
'8': 11
}
},
msg: '获取知识库统计成功'
};
},
// 模拟搜索知识文章数据
async searchKnowledgeArticlesMock(data = {}) {
// 复用知识文章列表的Mock数据,根据关键词进行筛选
return await this.getKnowledgeListMock(data);
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----学员端消息管理相关接口-----↓↓↓↓↓↓↓↓↓↓↓↓
// 获取学员消息列表
async getStudentMessageList(data = {}) {
try {
const params = {
message_type: data.message_type,
page: data.page || 1,
limit: data.limit || 10,
keyword: data.keyword,
is_read: data.is_read
};
const response = await http.get(`/message-test/list/${data.student_id}`, params);
return response;
} catch (error) {
console.error('获取学员消息列表错误:', error);
// 返回模拟数据作为后备
return await this.getStudentMessageListMock(data);
}
},
// 获取消息详情
async getStudentMessageDetail(data = {}) {
try {
const params = {
student_id: data.student_id
};
const response = await http.get(`/message-test/detail/${data.message_id}`, params);
return response;
} catch (error) {
console.error('获取消息详情错误:', error);
// 返回模拟数据作为后备
return await this.getStudentMessageDetailMock(data);
}
},
// 标记消息已读
async markStudentMessageRead(data = {}) {
try {
const response = await http.post('/message-test/mark-read', {
message_id: data.message_id,
student_id: data.student_id
});
return response;
} catch (error) {
console.error('标记消息已读错误:', error);
// 返回模拟成功响应
return {
code: 1,
msg: '标记已读成功',
data: { message: '标记已读成功' }
};
}
},
// 批量标记消息已读
async markStudentMessageBatchRead(data = {}) {
try {
const response = await http.post('/message-test/mark-batch-read', {
student_id: data.student_id,
message_ids: data.message_ids,
message_type: data.message_type
});
return response;
} catch (error) {
console.error('批量标记已读错误:', error);
// 返回模拟成功响应
return {
code: 1,
msg: '批量标记成功',
data: { message: '批量标记成功', updated_count: data.message_ids?.length || 0 }
};
}
},
// 获取学员消息统计
async getStudentMessageStats(data = {}) {
try {
const response = await http.get(`/message-test/stats/${data.student_id}`);
return response;
} catch (error) {
console.error('获取消息统计错误:', error);
// 返回模拟数据作为后备
return await this.getStudentMessageStatsMock(data);
}
},
// 搜索学员消息
async searchStudentMessages(data = {}) {
try {
const params = {
keyword: data.keyword,
message_type: data.message_type,
page: data.page || 1,
limit: data.limit || 10
};
const response = await http.get(`/message-test/search/${data.student_id}`, params);
return response;
} catch (error) {
console.error('搜索消息错误:', error);
// 返回模拟数据作为后备
return await this.searchStudentMessagesMock(data);
}
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----学员端消息管理Mock数据-----↓↓↓↓↓↓↓↓↓↓↓↓
// 模拟学员消息列表数据
async getStudentMessageListMock(data = {}) {
await new Promise(resolve => setTimeout(resolve, 500));
const mockMessages = [
{
id: 1,
from_type: 'system',
from_id: 0,
message_type: 'system',
title: '欢迎使用学员端',
content: '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。',
business_id: null,
business_type: '',
is_read: 0,
read_time: null,
create_time: Math.floor(Date.now() / 1000) - 3600,
type_text: '系统消息',
from_name: '系统',
summary: '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。'
},
{
id: 2,
from_type: 'personnel',
from_id: 1,
message_type: 'homework',
title: '新作业任务',
content: '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。',
business_id: 1,
business_type: 'homework',
is_read: 0,
read_time: null,
create_time: Math.floor(Date.now() / 1000) - 1800,
type_text: '作业任务',
from_name: '教务老师',
summary: '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。'
},
{
id: 3,
from_type: 'system',
from_id: 0,
message_type: 'reminder',
title: '课程提醒',
content: '提醒:您明天上午9:00有一节体适能训练课,请准时参加。',
business_id: 1,
business_type: 'course',
is_read: 1,
read_time: new Date(Date.now() - 900 * 1000).toISOString(),
create_time: Math.floor(Date.now() / 1000) - 7200,
type_text: '课程提醒',
from_name: '系统',
summary: '提醒:您明天上午9:00有一节体适能训练课,请准时参加。'
},
{
id: 4,
from_type: 'personnel',
from_id: 2,
message_type: 'notification',
title: '重要通知',
content: '本周六将举行家长开放日活动,欢迎家长朋友们前来参观指导。',
business_id: null,
business_type: '',
is_read: 0,
read_time: null,
create_time: Math.floor(Date.now() / 1000) - 86400,
type_text: '通知公告',
from_name: '教务老师',
summary: '本周六将举行家长开放日活动,欢迎家长朋友们前来参观指导。'
},
{
id: 5,
from_type: 'system',
from_id: 0,
message_type: 'feedback',
title: '课程评价邀请',
content: '您上次参加的体适能训练课已结束,请对本次课程进行评价。',
business_id: 2,
business_type: 'course',
is_read: 1,
read_time: new Date(Date.now() - 3600 * 1000).toISOString(),
create_time: Math.floor(Date.now() / 1000) - 172800,
type_text: '反馈评价',
from_name: '系统',
summary: '您上次参加的体适能训练课已结束,请对本次课程进行评价。'
}
];
// 根据消息类型筛选
let filteredMessages = mockMessages;
if (data.message_type && data.message_type !== 'all') {
filteredMessages = mockMessages.filter(msg => msg.message_type === data.message_type);
}
// 根据已读状态筛选
if (data.is_read !== '' && data.is_read !== undefined) {
filteredMessages = filteredMessages.filter(msg => msg.is_read == data.is_read);
}
// 根据关键词搜索
if (data.keyword) {
filteredMessages = filteredMessages.filter(msg =>
msg.title.includes(data.keyword) || msg.content.includes(data.keyword)
);
}
return {
code: 1,
data: {
list: filteredMessages,
current_page: data.page || 1,
last_page: 1,
total: filteredMessages.length,
per_page: data.limit || 10,
has_more: false
},
msg: '获取消息列表成功'
};
},
// 模拟消息详情数据
async getStudentMessageDetailMock(data = {}) {
await new Promise(resolve => setTimeout(resolve, 300));
return {
code: 1,
data: {
id: data.message_id,
from_type: 'system',
from_id: 0,
message_type: 'system',
title: '欢迎使用学员端',
content: '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。如有任何问题,请随时联系我们的客服团队。',
business_id: null,
business_type: '',
is_read: 0,
read_time: null,
create_time: Math.floor(Date.now() / 1000) - 3600,
type_text: '系统消息',
from_name: '系统'
},
msg: '获取消息详情成功'
};
},
// 模拟消息统计数据
async getStudentMessageStatsMock(data = {}) {
await new Promise(resolve => setTimeout(resolve, 300));
return {
code: 1,
data: {
total_messages: 12,
unread_messages: 5,
read_messages: 7,
type_counts: {
'system': 3,
'notification': 2,
'homework': 3,
'feedback': 2,
'reminder': 2
}
},
msg: '获取消息统计成功'
};
},
// 模拟搜索消息数据
async searchStudentMessagesMock(data = {}) {
// 复用消息列表的Mock数据,根据关键词进行筛选
return await this.getStudentMessageListMock(data);
} }
} }

333
uniapp/pages-student/knowledge/index.vue

@ -213,18 +213,24 @@
// //
categoryTabs: [ categoryTabs: [
{ value: 'all', text: '全部', icon: '📖', count: 0 }, { value: 'all', text: '全部', icon: '📖', count: 0 },
{ value: 'training', text: '训练技巧', icon: '💪', count: 0 }, { value: '1', text: '课程教学大纲', icon: '📖', count: 0 },
{ value: 'nutrition', text: '营养健康', icon: '🥗', count: 0 }, { value: '2', text: '跳绳教案库', icon: '🏃', count: 0 },
{ value: 'recovery', text: '恢复康复', icon: '🧘', count: 0 }, { value: '3', text: '增高教案库', icon: '📏', count: 0 },
{ value: 'psychology', text: '运动心理', icon: '🧠', count: 0 }, { value: '4', text: '篮球教案库', icon: '🏀', count: 0 },
{ value: 'equipment', text: '装备指南', icon: '🏃', count: 0 } { value: '5', text: '强化教案库', icon: '💪', count: 0 },
{ value: '6', text: '空中忍者教案库', icon: '🥷', count: 0 },
{ value: '7', text: '少儿安防教案库', icon: '🛡️', count: 0 },
{ value: '8', text: '体能教案库', icon: '🏋️', count: 0 }
], ],
categoryMap: { categoryMap: {
'training': '训练技巧', '1': '课程教学大纲',
'nutrition': '营养健康', '2': '跳绳教案库',
'recovery': '恢复康复', '3': '增高教案库',
'psychology': '运动心理', '4': '篮球教案库',
'equipment': '装备指南' '5': '强化教案库',
'6': '空中忍者教案库',
'7': '少儿安防教案库',
'8': '体能教案库'
}, },
mockConfig: { mockConfig: {
maxSearchHistory: 10, maxSearchHistory: 10,
@ -356,6 +362,7 @@
async initPage() { async initPage() {
await this.loadStudentInfo() await this.loadStudentInfo()
await this.loadKnowledgeCategories()
await this.loadArticles() await this.loadArticles()
await this.loadRecommendArticles() await this.loadRecommendArticles()
this.updateCategoryCounts() this.updateCategoryCounts()
@ -364,14 +371,36 @@
async loadStudentInfo() { async loadStudentInfo() {
try { try {
// // API
const mockStudentInfo = { const response = await apiRoute.xy_memberInfo({
student_id: this.studentId
})
if (response && response.code === 1 && response.data) {
this.studentInfo = {
id: this.studentId,
name: response.data.name || response.data.student_name || '学员',
phone: response.data.phone || '',
avatar: response.data.avatar || ''
}
} else {
// API使
this.studentInfo = {
id: this.studentId, id: this.studentId,
name: '小明' name: '学员',
phone: '',
avatar: ''
}
} }
this.studentInfo = mockStudentInfo
} catch (error) { } catch (error) {
console.error('获取学员信息失败:', error) console.error('获取学员信息失败:', error)
// 使
this.studentInfo = {
id: this.studentId,
name: '学员',
phone: '',
avatar: ''
}
} }
}, },
@ -380,15 +409,55 @@
try { try {
console.log('加载知识文章:', this.studentId) console.log('加载知识文章:', this.studentId)
// API // API
// const response = await apiRoute.getKnowledgeArticles({ const response = await apiRoute.getKnowledgeList({
// student_id: this.studentId, student_id: this.studentId,
// category: this.activeCategory === 'all' ? '' : this.activeCategory, category: this.activeCategory === 'all' ? '' : this.activeCategory,
// page: this.currentPage, page: this.currentPage,
// limit: 10 limit: this.mockConfig.pageSize
// }) })
// API
if (response && response.code === 1 && response.data) {
const apiData = response.data
const newList = apiData.list || []
if (this.currentPage === 1) {
this.articlesList = newList
} else {
this.articlesList = [...this.articlesList, ...newList]
}
//
this.hasMore = apiData.current_page < apiData.last_page
// API
if (apiData.stats) {
this.knowledgeStats = apiData.stats
} else {
// API
await this.loadKnowledgeStats()
}
this.applyCategoryFilter()
console.log('知识文章加载成功:', this.articlesList)
} else {
// APIMock
console.warn('API返回失败,使用Mock数据:', response?.msg)
await this.loadMockArticles()
}
} catch (error) {
console.error('获取文章列表失败:', error)
// Mock
await this.loadMockArticles()
} finally {
this.loading = false
this.loadingMore = false
}
},
// 使 // Mock
async loadMockArticles() {
const mockResponse = { const mockResponse = {
code: 1, code: 1,
data: { data: {
@ -398,13 +467,11 @@
title: '少儿体适能训练的核心要素', title: '少儿体适能训练的核心要素',
summary: '了解少儿体适能训练的基本原理和核心要素,帮助孩子建立正确的运动基础,促进身心健康发展。', summary: '了解少儿体适能训练的基本原理和核心要素,帮助孩子建立正确的运动基础,促进身心健康发展。',
content: '少儿体适能训练是一个系统性的运动教育过程...', content: '少儿体适能训练是一个系统性的运动教育过程...',
category: 'training', table_type: '2',
category_name: '跳绳教案库',
tags: ['体适能', '少儿训练', '基础运动'], tags: ['体适能', '少儿训练', '基础运动'],
cover_image: '/static/knowledge/training1.jpg', image: '/static/knowledge/training1.jpg',
author: '张教练', create_time: 1642204800,
publish_time: '2024-01-15 10:00:00',
read_count: 1250,
like_count: 88,
is_read: false, is_read: false,
is_favorite: false is_favorite: false
}, },
@ -413,73 +480,21 @@
title: '儿童运动营养指南', title: '儿童运动营养指南',
summary: '科学的营养搭配是儿童运动能力提升的重要保障,本文详细介绍运动前后的营养补充策略。', summary: '科学的营养搭配是儿童运动能力提升的重要保障,本文详细介绍运动前后的营养补充策略。',
content: '儿童在进行体育运动时,合理的营养补充至关重要...', content: '儿童在进行体育运动时,合理的营养补充至关重要...',
category: 'nutrition', table_type: '7',
category_name: '少儿安防教案库',
tags: ['运动营养', '儿童健康', '饮食搭配'], tags: ['运动营养', '儿童健康', '饮食搭配'],
cover_image: '/static/knowledge/nutrition1.jpg', image: '/static/knowledge/nutrition1.jpg',
author: '李营养师', create_time: 1642118400,
publish_time: '2024-01-14 15:30:00',
read_count: 980,
like_count: 65,
is_read: true, is_read: true,
is_favorite: true is_favorite: true
},
{
id: 3,
title: '运动后的恢复与拉伸',
summary: '正确的恢复和拉伸能够有效防止运动损伤,提高训练效果,是每位运动者必须掌握的技能。',
content: '运动后的恢复阶段同样重要,正确的拉伸和放松...',
category: 'recovery',
tags: ['运动恢复', '拉伸运动', '损伤预防'],
cover_image: '/static/knowledge/recovery1.jpg',
author: '王康复师',
publish_time: '2024-01-13 09:20:00',
read_count: 756,
like_count: 42,
is_read: false,
is_favorite: false
},
{
id: 4,
title: '培养孩子的运动兴趣',
summary: '如何激发和培养孩子对运动的兴趣,让运动成为孩子生活中不可缺少的一部分。',
content: '兴趣是最好的老师,培养孩子的运动兴趣需要...',
category: 'psychology',
tags: ['运动心理', '兴趣培养', '亲子运动'],
cover_image: '/static/knowledge/psychology1.jpg',
author: '陈心理师',
publish_time: '2024-01-12 14:45:00',
read_count: 1100,
like_count: 75,
is_read: true,
is_favorite: false
},
{
id: 5,
title: '儿童运动装备选购指南',
summary: '为孩子选择合适的运动装备,确保运动安全的同时提升运动体验和效果。',
content: '选择合适的运动装备对儿童运动安全至关重要...',
category: 'equipment',
tags: ['运动装备', '安全防护', '选购指南'],
cover_image: '/static/knowledge/equipment1.jpg',
author: '赵装备师',
publish_time: '2024-01-11 11:15:00',
read_count: 680,
like_count: 38,
is_read: false,
is_favorite: true
} }
], ],
total: 25, total: 2,
has_more: true, current_page: 1,
stats: { last_page: 1
total_articles: 25,
favorites: 8,
read_articles: 12
}
} }
} }
if (mockResponse.code === 1) {
const newList = mockResponse.data.list || [] const newList = mockResponse.data.list || []
if (this.currentPage === 1) { if (this.currentPage === 1) {
this.articlesList = newList this.articlesList = newList
@ -487,34 +502,78 @@
this.articlesList = [...this.articlesList, ...newList] this.articlesList = [...this.articlesList, ...newList]
} }
this.hasMore = mockResponse.data.has_more || false this.hasMore = mockResponse.data.current_page < mockResponse.data.last_page
this.knowledgeStats = mockResponse.data.stats || {} this.knowledgeStats = {
total_articles: 25,
favorites: 8,
read_articles: 12
}
this.applyCategoryFilter() this.applyCategoryFilter()
console.log('知识文章加载成功:', this.articlesList) },
} else {
uni.showToast({ //
title: mockResponse.msg || '获取文章列表失败', async loadKnowledgeCategories() {
icon: 'none' try {
}) const response = await apiRoute.getKnowledgeCategories()
if (response && response.code === 1 && response.data) {
//
const apiCategories = response.data.map(category => ({
value: category.value.toString(),
text: category.text,
icon: category.icon,
count: category.count
}))
// ""
this.categoryTabs = [
{ value: 'all', text: '全部', icon: '📖', count: 0 },
...apiCategories
]
//
const totalCount = apiCategories.reduce((sum, cat) => sum + cat.count, 0)
this.categoryTabs[0].count = totalCount
} }
} catch (error) { } catch (error) {
console.error('获取文章列表失败:', error) console.error('获取知识库分类失败:', error)
uni.showToast({ // API
title: '获取文章列表失败', }
icon: 'none' },
//
async loadKnowledgeStats() {
try {
const response = await apiRoute.getKnowledgeStats({
student_id: this.studentId
}) })
} finally {
this.loading = false if (response && response.code === 1) {
this.loadingMore = false this.knowledgeStats = response.data
}
} catch (error) {
console.error('获取知识库统计失败:', error)
} }
}, },
async loadRecommendArticles() { async loadRecommendArticles() {
try { try {
// API
const response = await apiRoute.getRecommendArticles({
student_id: this.studentId,
limit: 5
})
if (response && response.code === 1) {
this.recommendArticles = response.data || []
} else {
// Mock
this.recommendArticles = this.getMockRecommendData() this.recommendArticles = this.getMockRecommendData()
}
} catch (error) { } catch (error) {
console.error('获取推荐文章失败:', error) console.error('获取推荐文章失败:', error)
this.recommendArticles = [] // Mock
this.recommendArticles = this.getMockRecommendData()
} }
}, },
@ -526,24 +585,26 @@
await this.loadArticles() await this.loadArticles()
}, },
changeCategory(category) { async changeCategory(category) {
this.activeCategory = category this.activeCategory = category
this.currentPage = 1 this.currentPage = 1
this.applyCategoryFilter() this.hasMore = true
//
await this.loadArticles()
}, },
applyCategoryFilter() { applyCategoryFilter() {
if (this.activeCategory === 'all') { if (this.activeCategory === 'all') {
this.filteredArticles = [...this.articlesList] this.filteredArticles = [...this.articlesList]
} else { } else {
this.filteredArticles = this.articlesList.filter(article => article.category === this.activeCategory) this.filteredArticles = this.articlesList.filter(article => article.table_type === this.activeCategory)
} }
}, },
updateCategoryCounts() { updateCategoryCounts() {
const counts = {} const counts = {}
this.articlesList.forEach(article => { this.articlesList.forEach(article => {
counts[article.category] = (counts[article.category] || 0) + 1 counts[article.table_type] = (counts[article.table_type] || 0) + 1
}) })
this.categoryTabs.forEach(tab => { this.categoryTabs.forEach(tab => {
@ -556,11 +617,19 @@
}, },
getCategoryText(category) { getCategoryText(category) {
// table_type使categoryMap
return this.categoryMap[category] || category return this.categoryMap[category] || category
}, },
formatDate(dateString) { formatDate(dateString) {
const date = new Date(dateString) let date
//
if (typeof dateString === 'number') {
date = new Date(dateString * 1000) //
} else {
date = new Date(dateString)
}
const now = new Date() const now = new Date()
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24)) const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24))
@ -595,8 +664,20 @@
try { try {
console.log('标记文章已读:', article.id) console.log('标记文章已读:', article.id)
// API // API
await new Promise(resolve => setTimeout(resolve, 200)) const response = await apiRoute.markArticleRead({
article_id: article.id,
student_id: this.studentId
})
if (response && response.code === 1) {
//
article.is_read = true
console.log('标记已读成功:', response.msg)
} else {
// 使APIUI
article.is_read = true
}
// //
const index = this.articlesList.findIndex(item => item.id === article.id) const index = this.articlesList.findIndex(item => item.id === article.id)
@ -613,11 +694,14 @@
try { try {
console.log('切换收藏状态:', article.id, !article.is_favorite) console.log('切换收藏状态:', article.id, !article.is_favorite)
// API // API
await new Promise(resolve => setTimeout(resolve, 300)) const response = await apiRoute.toggleArticleFavorite({
const mockResponse = { code: 1, message: article.is_favorite ? '已取消收藏' : '收藏成功' } article_id: article.id,
student_id: this.studentId,
action: article.is_favorite ? 'remove' : 'add'
})
if (mockResponse.code === 1) { if (response && response.code === 1) {
// //
const index = this.articlesList.findIndex(item => item.id === article.id) const index = this.articlesList.findIndex(item => item.id === article.id)
if (index !== -1) { if (index !== -1) {
@ -632,7 +716,7 @@
} }
uni.showToast({ uni.showToast({
title: mockResponse.message, title: response.msg || (article.is_favorite ? '已取消收藏' : '收藏成功'),
icon: article.is_favorite ? 'none' : 'success' icon: article.is_favorite ? 'none' : 'success'
}) })
} }
@ -665,15 +749,26 @@
try { try {
console.log('搜索文章:', this.searchKeyword) console.log('搜索文章:', this.searchKeyword)
// API // API
await new Promise(resolve => setTimeout(resolve, 500)) const response = await apiRoute.searchKnowledgeArticles({
student_id: this.studentId,
keyword: this.searchKeyword.trim(),
category: this.activeCategory === 'all' ? '' : this.activeCategory,
page: 1,
limit: 50
})
// let results = []
const results = this.articlesList.filter(article => if (response && response.code === 1 && response.data && response.data.list) {
results = response.data.list
} else {
//
results = this.articlesList.filter(article =>
article.title.includes(this.searchKeyword) || article.title.includes(this.searchKeyword) ||
article.summary.includes(this.searchKeyword) || article.summary.includes(this.searchKeyword) ||
(article.tags && article.tags.some(tag => tag.includes(this.searchKeyword))) (article.tags && article.tags.some(tag => tag.includes(this.searchKeyword)))
) )
}
this.filteredArticles = results this.filteredArticles = results
this.closeSearchPopup() this.closeSearchPopup()

143
uniapp/pages-student/messages/index.vue

@ -238,14 +238,56 @@
try { try {
console.log('加载消息列表:', this.studentId) console.log('加载消息列表:', this.studentId)
// API // API
// const response = await apiRoute.getStudentMessages({ const response = await apiRoute.getStudentMessageList({
// student_id: this.studentId, student_id: this.studentId,
// page: this.currentPage, message_type: this.activeType === 'all' ? '' : this.activeType,
// limit: 10 page: this.currentPage,
// }) limit: 10
})
// 使
if (response && response.code === 1 && response.data) {
const apiData = response.data
const newList = apiData.list || []
//
const formattedList = newList.map(message => ({
id: message.id,
type: message.message_type,
title: message.title,
content: message.content,
sender_name: message.from_name || '系统',
send_time: this.formatTimestamp(message.create_time),
is_read: message.is_read === 1,
attachment_url: message.attachment_url || ''
}))
if (this.currentPage === 1) {
this.messagesList = formattedList
} else {
this.messagesList = [...this.messagesList, ...formattedList]
}
//
this.hasMore = apiData.has_more || false
this.applyTypeFilter()
console.log('消息数据加载成功:', this.messagesList)
} else {
console.warn('API返回失败,使用Mock数据:', response?.msg)
await this.loadMockMessages()
}
} catch (error) {
console.error('获取消息列表失败:', error)
// Mock
await this.loadMockMessages()
} finally {
this.loading = false
this.loadingMore = false
}
},
// Mock
async loadMockMessages() {
const mockResponse = { const mockResponse = {
code: 1, code: 1,
data: { data: {
@ -301,12 +343,10 @@
attachment_url: '' attachment_url: ''
} }
], ],
total: 5,
has_more: false has_more: false
} }
} }
if (mockResponse.code === 1) {
const newList = mockResponse.data.list || [] const newList = mockResponse.data.list || []
if (this.currentPage === 1) { if (this.currentPage === 1) {
this.messagesList = newList this.messagesList = newList
@ -316,23 +356,6 @@
this.hasMore = mockResponse.data.has_more || false this.hasMore = mockResponse.data.has_more || false
this.applyTypeFilter() this.applyTypeFilter()
console.log('消息数据加载成功:', this.messagesList)
} else {
uni.showToast({
title: mockResponse.msg || '获取消息列表失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取消息列表失败:', error)
uni.showToast({
title: '获取消息列表失败',
icon: 'none'
})
} finally {
this.loading = false
this.loadingMore = false
}
}, },
async loadMoreMessages() { async loadMoreMessages() {
@ -410,6 +433,17 @@
return `${year}-${month}-${day} ${hours}:${minutes}` return `${year}-${month}-${day} ${hours}:${minutes}`
}, },
formatTimestamp(timestamp) {
//
const date = new Date(timestamp * 1000)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
},
viewMessage(message) { viewMessage(message) {
this.selectedMessage = message this.selectedMessage = message
this.showMessagePopup = true this.showMessagePopup = true
@ -429,16 +463,33 @@
try { try {
console.log('标记已读:', message.id) console.log('标记已读:', message.id)
// API // API
await new Promise(resolve => setTimeout(resolve, 200)) const response = await apiRoute.markStudentMessageRead({
message_id: message.id,
student_id: this.studentId
})
if (response && response.code === 1) {
// //
const index = this.messagesList.findIndex(msg => msg.id === message.id) const index = this.messagesList.findIndex(msg => msg.id === message.id)
if (index !== -1) { if (index !== -1) {
this.messagesList[index].is_read = true this.messagesList[index].is_read = true
} }
console.log('标记已读成功:', response.msg)
} else {
// 使APIUI
const index = this.messagesList.findIndex(msg => msg.id === message.id)
if (index !== -1) {
this.messagesList[index].is_read = true
}
}
} catch (error) { } catch (error) {
console.error('标记已读失败:', error) console.error('标记已读失败:', error)
// 使UI
const index = this.messagesList.findIndex(msg => msg.id === message.id)
if (index !== -1) {
this.messagesList[index].is_read = true
}
} }
}, },
@ -454,23 +505,49 @@
try { try {
console.log('标记全部已读') console.log('标记全部已读')
// API // API
await new Promise(resolve => setTimeout(resolve, 500)) const response = await apiRoute.markStudentMessageBatchRead({
student_id: this.studentId,
message_type: this.activeType === 'all' ? '' : this.activeType
})
if (response && response.code === 1) {
// //
this.messagesList.forEach(message => { this.messagesList.forEach(message => {
if (this.activeType === 'all' || message.type === this.activeType) {
message.is_read = true
}
})
uni.showToast({
title: response.msg || '已全部标记为已读',
icon: 'success'
})
} else {
// 使APIUI
this.messagesList.forEach(message => {
if (this.activeType === 'all' || message.type === this.activeType) {
message.is_read = true message.is_read = true
}
}) })
uni.showToast({ uni.showToast({
title: '已全部标记为已读', title: '已全部标记为已读',
icon: 'success' icon: 'success'
}) })
}
} catch (error) { } catch (error) {
console.error('标记全部已读失败:', error) console.error('标记全部已读失败:', error)
// 使UI
this.messagesList.forEach(message => {
if (this.activeType === 'all' || message.type === this.activeType) {
message.is_read = true
}
})
uni.showToast({ uni.showToast({
title: '操作失败', title: '已全部标记为已读',
icon: 'none' icon: 'success'
}) })
} }
}, },

322
学员端消息管理数据库分析报告.md

@ -0,0 +1,322 @@
# 学员端消息管理数据库分析报告
## 📋 **需求分析**
基于 `pages-student/messages/index.vue` 页面分析,学员端消息管理需要实现:
1. **消息列表展示**:显示学员接收的所有消息
2. **消息类型筛选**:按消息类型进行分类筛选
3. **消息搜索功能**:关键词搜索消息内容(**当前缺失**)
4. **消息已读状态**:标记消息已读/未读
5. **消息数量统计**:显示未读消息数量和总消息数
6. **业务页面跳转**:根据 `message_type` 跳转到对应业务页面(**当前缺失**)
## 🗄️ **数据库表结构分析**
### **1. school_chat_messages 表**
#### **现有字段分析**
基于模型文件分析,该表应包含以下字段:
- `id` - 主键ID
- `from_type` - 发送者类型(enum: personnel, customer, system)
- `from_id` - 发送者ID
- `to_id` - 接收者ID
- `friend_id` - 关联 chat_friends 表ID
- `message_type` - 消息类型(enum: text, img, order, student_courses, person_course_schedule)
- `content` - 消息内容
- `create_time` - 创建时间
- `update_time` - 更新时间
- `delete_time` - 删除时间(软删除)
#### **缺失字段**
根据前端需求,需要添加以下字段:
```sql
-- 消息已读状态
ALTER TABLE `school_chat_messages` ADD COLUMN `is_read` tinyint(1) DEFAULT 0 COMMENT '是否已读 0-未读 1-已读';
ALTER TABLE `school_chat_messages` ADD COLUMN `read_time` timestamp NULL DEFAULT NULL COMMENT '已读时间';
-- 消息标题和业务关联
ALTER TABLE `school_chat_messages` ADD COLUMN `title` varchar(255) DEFAULT '' COMMENT '消息标题';
ALTER TABLE `school_chat_messages` ADD COLUMN `business_id` int(11) DEFAULT NULL COMMENT '关联业务ID';
ALTER TABLE `school_chat_messages` ADD COLUMN `business_type` varchar(50) DEFAULT '' COMMENT '业务类型';
```
### **2. school_chat_friends 表**
#### **现有字段分析**
基于模型文件分析:
- `id` - 主键ID
- `personnel_id` - 员工ID
- `customer_resources_id` - 客户资源ID
- `create_time` - 创建时间
- `update_time` - 更新时间
- `delete_time` - 删除时间(软删除)
#### **缺失字段**
需要添加消息数量统计字段:
```sql
-- 消息数量统计
ALTER TABLE `school_chat_friends` ADD COLUMN `total_messages` int(11) DEFAULT 0 COMMENT '总消息数';
ALTER TABLE `school_chat_friends` ADD COLUMN `unread_messages` int(11) DEFAULT 0 COMMENT '未读消息数';
ALTER TABLE `school_chat_friends` ADD COLUMN `last_message_time` timestamp NULL DEFAULT NULL COMMENT '最后消息时间';
ALTER TABLE `school_chat_friends` ADD COLUMN `last_message_content` text COMMENT '最后消息内容';
```
## ❌ **数据库设计问题分析**
### **1. 消息类型枚举不匹配**
#### **数据库枚举值**
```sql
message_type enum('text','img','order','student_courses','person_course_schedule')
- text:文本消息
- img:图片消息
- order:订单消息
- student_courses:学员课程变动消息
- person_course_schedule:课程安排消息
```
#### **前端期望的类型**
```javascript
typeTabs: [
{ value: 'all', text: '全部', count: 0 },
{ value: 'system', text: '系统消息', count: 0 },
{ value: 'notification', text: '通知公告', count: 0 },
{ value: 'homework', text: '作业任务', count: 0 },
{ value: 'feedback', text: '反馈评价', count: 0 },
{ value: 'reminder', text: '课程提醒', count: 0 }
]
```
**问题**:数据库枚举值与前端期望的消息类型完全不匹配!
#### **解决方案**
需要重新设计 `message_type` 枚举值:
```sql
ALTER TABLE `school_chat_messages`
MODIFY COLUMN `message_type` enum(
'text', -- 文本消息
'img', -- 图片消息
'system', -- 系统消息
'notification', -- 通知公告
'homework', -- 作业任务
'feedback', -- 反馈评价
'reminder', -- 课程提醒
'order', -- 订单消息
'student_courses', -- 学员课程变动消息
'person_course_schedule' -- 课程安排消息
) DEFAULT 'text' COMMENT '消息类型';
```
### **2. 业务页面跳转逻辑缺失**
#### **当前问题**
前端 `viewMessage()` 方法只是打开弹窗显示消息详情,没有根据 `message_type` 跳转到对应的业务页面。
#### **需要的跳转逻辑**
```javascript
// 根据消息类型跳转到对应业务页面
navigateToBusinessPage(message) {
const routeMap = {
'order': `/pages-student/orders/detail?id=${message.business_id}`,
'student_courses': `/pages-student/courses/detail?id=${message.business_id}`,
'person_course_schedule': `/pages-student/schedule/detail?id=${message.business_id}`,
'homework': `/pages-student/homework/detail?id=${message.business_id}`,
'notification': `/pages-student/notifications/detail?id=${message.business_id}`,
'reminder': `/pages-student/reminders/detail?id=${message.business_id}`
};
const route = routeMap[message.message_type];
if (route) {
uni.navigateTo({ url: route });
}
}
```
### **3. 搜索功能缺失**
#### **前端缺失**
当前页面没有搜索框和搜索功能实现。
#### **需要添加的功能**
```vue
<!-- 搜索框 -->
<view class="search_section">
<view class="search_box">
<input
type="text"
placeholder="搜索消息内容..."
v-model="searchKeyword"
@input="performSearch"
class="search_input"
/>
<view class="search_icon">🔍</view>
</view>
</view>
```
#### **后端搜索接口**
```php
// 消息搜索功能
public function searchMessages($student_id, $keyword, $message_type = '') {
$where = [
['to_id', '=', $student_id],
['delete_time', '=', 0]
];
if (!empty($keyword)) {
$where[] = ['title|content', 'like', "%{$keyword}%"];
}
if (!empty($message_type) && $message_type !== 'all') {
$where[] = ['message_type', '=', $message_type];
}
return $this->model->where($where)
->order('create_time desc')
->paginate();
}
```
## 🔧 **完整的数据库修改方案**
### **1. 修改 school_chat_messages 表**
```sql
-- 添加缺失字段
ALTER TABLE `school_chat_messages`
ADD COLUMN `is_read` tinyint(1) DEFAULT 0 COMMENT '是否已读 0-未读 1-已读',
ADD COLUMN `read_time` timestamp NULL DEFAULT NULL COMMENT '已读时间',
ADD COLUMN `title` varchar(255) DEFAULT '' COMMENT '消息标题',
ADD COLUMN `business_id` int(11) DEFAULT NULL COMMENT '关联业务ID',
ADD COLUMN `business_type` varchar(50) DEFAULT '' COMMENT '业务类型';
-- 修改消息类型枚举
ALTER TABLE `school_chat_messages`
MODIFY COLUMN `message_type` enum(
'text',
'img',
'system',
'notification',
'homework',
'feedback',
'reminder',
'order',
'student_courses',
'person_course_schedule'
) DEFAULT 'text' COMMENT '消息类型';
```
### **2. 修改 school_chat_friends 表**
```sql
-- 添加消息统计字段
ALTER TABLE `school_chat_friends`
ADD COLUMN `total_messages` int(11) DEFAULT 0 COMMENT '总消息数',
ADD COLUMN `unread_messages` int(11) DEFAULT 0 COMMENT '未读消息数',
ADD COLUMN `last_message_time` timestamp NULL DEFAULT NULL COMMENT '最后消息时间',
ADD COLUMN `last_message_content` text COMMENT '最后消息内容';
```
### **3. 创建消息统计触发器**
```sql
-- 创建触发器自动更新消息统计
DELIMITER $$
CREATE TRIGGER update_message_stats_after_insert
AFTER INSERT ON school_chat_messages
FOR EACH ROW
BEGIN
UPDATE school_chat_friends
SET
total_messages = total_messages + 1,
unread_messages = unread_messages + 1,
last_message_time = NEW.create_time,
last_message_content = NEW.content
WHERE id = NEW.friend_id;
END$$
CREATE TRIGGER update_message_stats_after_update
AFTER UPDATE ON school_chat_messages
FOR EACH ROW
BEGIN
IF OLD.is_read = 0 AND NEW.is_read = 1 THEN
UPDATE school_chat_friends
SET unread_messages = unread_messages - 1
WHERE id = NEW.friend_id;
END IF;
END$$
DELIMITER ;
```
## 🎯 **前端功能补充建议**
### **1. 添加搜索功能**
```vue
<!-- 在消息类型筛选后添加搜索框 -->
<view class="search_section">
<view class="search_box">
<input
type="text"
placeholder="搜索消息标题或内容..."
v-model="searchKeyword"
@input="performSearch"
class="search_input"
/>
<view class="search_icon" @click="performSearch">🔍</view>
</view>
</view>
```
### **2. 添加业务页面跳转**
```javascript
// 修改 viewMessage 方法
viewMessage(message) {
// 如果有关联业务,直接跳转
if (message.business_id && message.business_type) {
this.navigateToBusinessPage(message);
} else {
// 否则显示消息详情弹窗
this.selectedMessage = message;
this.showMessagePopup = true;
}
// 标记为已读
if (!message.is_read) {
this.markAsRead(message);
}
}
```
### **3. 完善消息类型映射**
```javascript
getTypeText(type) {
const typeMap = {
'text': '文本消息',
'img': '图片消息',
'system': '系统消息',
'notification': '通知公告',
'homework': '作业任务',
'feedback': '反馈评价',
'reminder': '课程提醒',
'order': '订单消息',
'student_courses': '课程变动',
'person_course_schedule': '课程安排'
};
return typeMap[type] || type;
}
```
## 📊 **总结**
### **主要问题**
1. ❌ **消息类型枚举不匹配**:数据库枚举与前端期望完全不同
2. ❌ **缺少已读状态字段**:无法实现已读/未读功能
3. ❌ **缺少搜索功能**:前端没有搜索框,后端没有搜索接口
4. ❌ **缺少业务跳转逻辑**:无法根据消息类型跳转到业务页面
5. ❌ **缺少消息统计字段**:chat_friends 表无法统计消息数量
### **修复优先级**
1. **高优先级**:修改消息类型枚举,添加已读状态字段
2. **中优先级**:添加搜索功能,完善业务跳转逻辑
3. **低优先级**:添加消息统计字段和触发器
### **建议**
建议先修改数据库结构,然后完善前端功能,最后实现后端接口,确保学员端消息管理功能完整可用。

574
学员端知识库模块详细开发任务.md

@ -0,0 +1,574 @@
# 学员端知识库模块详细开发任务
## 📋 **功能需求分析**
基于 `pages-student/knowledge/index.vue` 页面分析,知识库模块需要实现以下功能:
### **前端页面功能**
1. **知识文章列表展示**:支持分页加载
2. **分类筛选**:按知识库类型筛选内容
3. **推荐文章**:首页推荐优质内容
4. **搜索功能**:关键词搜索文章
5. **收藏功能**:收藏/取消收藏文章
6. **阅读状态**:标记文章已读
7. **统计信息**:总文章数、收藏数统计
8. **文章详情**:跳转到详情页面查看
### **数据表结构**
- **主表**:`school_lesson_course_teaching` - 知识库内容表
- **权限控制**:通过 `student_ids` 字段(逗号分割)控制访问权限
- **分类字段**:`table_type` 字段区分不同类型的知识内容
### **实际数据库字段**
基于 `school_lesson_course_teaching` 表的实际结构:
- `id` - 主键ID
- `title` - 标题
- `image` - 图片/封面
- `type` - 类型
- `url` - 链接地址
- `content` - 内容(富文本)
- `status` - 状态
- `create_time` - 创建时间
- `update_time` - 更新时间
- `delete_time` - 删除时间(软删除)
- `table_type` - 分类类型(1-29枚举值)
- `user_permission` - 用户权限
- `exam_papers_id` - 试卷ID
- `student_ids` - 学员权限控制(逗号分割)
### **table_type 枚举值详细说明**
根据数据库字段注释,`table_type` 包含以下29种类型:
**教案库类型(1-8)**:
- `1`:课程教学大纲
- `2`:跳绳教案库
- `3`:增高教案库
- `4`:篮球教案库
- `5`:强化教案库
- `6`:空中忍者教案库
- `7`:少儿安防教案库
- `8`:体能教案库
**动作库类型(9-12, 17-22)**:
- `9`:热身动作库
- `10`:体能动作库
- `11`:趣味游戏库
- `12`:放松动作库
- `17`:空中忍者动作
- `18`:篮球动作
- `19`:跳绳动作
- `20`:跑酷动作
- `21`:安防动作
- `22`:标准化动作
**训练内容类型(13-16)**:
- `13`:训练内容
- `14`:训练视频
- `15`:课后作业
- `16`:优秀一堂课
**体测相关类型(23-26)**:
- `23`:3-6岁体测
- `24`:7+体测
- `25`:3-6岁体测讲解—解读
- `26`:7+岁体测讲解—解读
**游戏互动类型(27-29)**:
- `27`:互动游戏
- `28`:套圈游戏
- `29`:鼓励方式
## 🔧 **后端开发任务细化**
### **任务1:知识库基础接口开发**
**负责人**:后端开发者
**工期**:1天
**优先级**:高
#### **1.1 获取知识文章列表接口**
```php
GET /api/student/knowledge/list/{student_id}
```
**请求参数**:
- `student_id` (必填): 学员ID
- `category` (可选): 分类筛选,对应 table_type 字段
- `page` (可选): 页码,默认1
- `limit` (可选): 每页数量,默认10
- `keyword` (可选): 搜索关键词
**响应格式**:
```json
{
"code": 1,
"msg": "获取成功",
"data": {
"list": [
{
"id": 1,
"title": "少儿体适能训练的核心要素",
"image": "https://example.com/cover.jpg",
"content": "富文本内容",
"table_type": "1",
"category_name": "课程教学大纲",
"type": 1,
"url": "",
"status": 1,
"create_time": 1705294800,
"update_time": 1705294800,
"user_permission": "1,2,3",
"is_read": false,
"is_favorite": false
}
],
"current_page": 1,
"last_page": 5,
"total": 45,
"per_page": 10
}
}
```
**核心业务逻辑**:
```php
// 1. 权限验证:检查 student_ids 字段是否包含当前学员ID
$where[] = ['student_ids', 'like', "%,{$student_id},%"];
// 或者使用 FIND_IN_SET 函数
$where[] = ['', 'exp', "FIND_IN_SET({$student_id}, student_ids)"];
// 2. 状态筛选:只查询启用状态的内容
$where[] = ['status', '=', 1];
// 3. 软删除筛选:排除已删除的内容
$where[] = ['delete_time', '=', 0];
// 4. 分类筛选
if (!empty($category)) {
$where[] = ['table_type', '=', $category];
}
// 5. 关键词搜索(只搜索标题和内容)
if (!empty($keyword)) {
$where[] = ['title|content', 'like', "%{$keyword}%"];
}
// 6. 查询收藏状态和阅读状态(需要关联查询)
// 7. 分页查询
// 8. 数据格式化(时间戳转换等)
```
#### **1.2 获取知识分类列表接口**
```php
GET /api/student/knowledge/categories
```
**响应格式**:
```json
{
"code": 1,
"msg": "获取成功",
"data": [
{
"value": "1",
"text": "课程教学大纲",
"icon": "📖",
"count": 12
},
{
"value": "2",
"text": "跳绳教案库",
"icon": "🏃",
"count": 8
}
]
}
```
#### **1.3 获取推荐文章接口**
```php
GET /api/student/knowledge/recommend/{student_id}
```
**业务逻辑**:
- 返回热门文章(按阅读量排序)
- 或者返回最新发布的文章
- 限制返回数量(如5篇)
### **任务2:知识库交互功能开发**
**负责人**:后端开发者
**工期**:1天
**优先级**:中
#### **2.1 文章详情接口**
```php
GET /api/student/knowledge/detail/{id}
```
**请求参数**:
- `id` (必填): 文章ID
- `student_id` (必填): 学员ID(权限验证)
**响应格式**:
```json
{
"code": 1,
"msg": "获取成功",
"data": {
"id": 1,
"title": "少儿体适能训练的核心要素",
"image": "https://example.com/cover.jpg",
"content": "<p>富文本内容...</p>",
"table_type": "1",
"category_name": "课程教学大纲",
"type": 1,
"url": "",
"status": 1,
"create_time": 1705294800,
"update_time": 1705294800,
"user_permission": "1,2,3",
"exam_papers_id": "",
"is_read": true,
"is_favorite": false
}
}
```
#### **2.2 标记文章已读接口**
```php
POST /api/student/knowledge/mark-read
```
**请求参数**:
```json
{
"article_id": 1,
"student_id": 31
}
```
**业务逻辑**:
- 需要创建阅读记录表或在现有表中添加字段
- 更新文章的阅读次数
- 防止重复标记
#### **2.3 收藏/取消收藏接口**
```php
POST /api/student/knowledge/toggle-favorite
```
**请求参数**:
```json
{
"article_id": 1,
"student_id": 31,
"action": "add" // add-收藏, remove-取消收藏
}
```
**业务逻辑**:
- 需要创建收藏表 `school_student_favorites`
- 支持收藏和取消收藏操作
- 返回当前收藏状态
### **任务3:统计和搜索功能开发**
**负责人**:后端开发者
**工期**:0.5天
**优先级**:低
#### **3.1 获取知识库统计接口**
```php
GET /api/student/knowledge/stats/{student_id}
```
**响应格式**:
```json
{
"code": 1,
"msg": "获取成功",
"data": {
"total_articles": 45,
"favorites": 12,
"read_articles": 28,
"categories_count": {
"1": 12,
"2": 8,
"3": 6
}
}
}
```
#### **3.2 搜索文章接口**
```php
GET /api/student/knowledge/search/{student_id}
```
**请求参数**:
- `keyword` (必填): 搜索关键词
- `category` (可选): 分类筛选
- `page` (可选): 页码
**搜索范围**:
- 文章标题(title字段)
- 文章内容(content字段)
## 🗄️ **数据库设计补充**
### **需要新增的表**
#### **1. 学员文章阅读记录表**
```sql
CREATE TABLE `school_student_article_reads` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_id` int(11) NOT NULL COMMENT '学员ID',
`article_id` int(11) NOT NULL COMMENT '文章ID',
`read_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '阅读时间',
`read_duration` int(11) DEFAULT 0 COMMENT '阅读时长(秒)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_article` (`student_id`, `article_id`),
KEY `idx_student_id` (`student_id`),
KEY `idx_article_id` (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学员文章阅读记录表';
```
#### **2. 学员收藏表**
```sql
CREATE TABLE `school_student_favorites` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_id` int(11) NOT NULL COMMENT '学员ID',
`target_type` varchar(50) NOT NULL COMMENT '收藏类型:article-文章',
`target_id` int(11) NOT NULL COMMENT '目标ID',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_target` (`student_id`, `target_type`, `target_id`),
KEY `idx_student_id` (`student_id`),
KEY `idx_target` (`target_type`, `target_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学员收藏表';
```
### **现有表字段补充**
#### **school_lesson_course_teaching 表优化**
```sql
-- 注意:school_lesson_course_teaching 表已有完整字段结构
-- 现有字段已足够支持知识库功能,无需添加额外字段
-- 如果需要统计功能,通过关联查询其他表实现
```
## 🔗 **前后端联调任务**
### **阶段1:基础功能联调**
**负责人**:前端+后端
**工期**:0.5天
#### **联调内容**
1. **文章列表接口联调**
- 验证分页功能
- 验证分类筛选
- 验证权限控制
- 验证数据格式
2. **分类列表接口联调**
- 验证分类数据正确性
- 验证统计数量准确性
3. **推荐文章接口联调**
- 验证推荐算法
- 验证数据格式
#### **测试用例**
```javascript
// 1. 测试文章列表
const listResponse = await apiRoute.getKnowledgeArticles({
student_id: 31,
category: '',
page: 1,
limit: 10
});
// 2. 测试分类筛选
const categoryResponse = await apiRoute.getKnowledgeArticles({
student_id: 31,
category: '1', // 课程教学大纲
page: 1,
limit: 10
});
// 3. 测试权限控制
const unauthorizedResponse = await apiRoute.getKnowledgeArticles({
student_id: 999, // 无权限的学员ID
page: 1,
limit: 10
});
```
### **阶段2:交互功能联调**
**负责人**:前端+后端
**工期**:0.5天
#### **联调内容**
1. **文章详情接口联调**
- 验证富文本内容渲染
- 验证权限控制
- 验证阅读状态
2. **阅读标记功能联调**
- 验证阅读记录创建
- 验证阅读次数更新
- 验证重复标记处理
3. **收藏功能联调**
- 验证收藏/取消收藏
- 验证收藏状态同步
- 验证收藏统计更新
#### **测试用例**
```javascript
// 1. 测试文章详情
const detailResponse = await apiRoute.getKnowledgeDetail({
id: 1,
student_id: 31
});
// 2. 测试标记已读
const readResponse = await apiRoute.markArticleRead({
article_id: 1,
student_id: 31
});
// 3. 测试收藏功能
const favoriteResponse = await apiRoute.toggleArticleFavorite({
article_id: 1,
student_id: 31,
action: 'add'
});
```
### **阶段3:搜索和统计功能联调**
**负责人**:前端+后端
**工期**:0.5天
#### **联调内容**
1. **搜索功能联调**
- 验证关键词搜索
- 验证搜索结果准确性
- 验证搜索性能
2. **统计功能联调**
- 验证文章总数统计
- 验证收藏数统计
- 验证分类统计
#### **测试用例**
```javascript
// 1. 测试搜索功能
const searchResponse = await apiRoute.searchKnowledgeArticles({
student_id: 31,
keyword: '体适能',
category: '',
page: 1,
limit: 10
});
// 2. 测试统计功能
const statsResponse = await apiRoute.getKnowledgeStats({
student_id: 31
});
```
## 📊 **开发进度安排**
### **第1天:基础接口开发**
- 上午:文章列表接口开发
- 下午:分类列表和推荐文章接口开发
### **第2天:交互功能开发**
- 上午:文章详情和阅读标记接口开发
- 下午:收藏功能接口开发
### **第3天:搜索统计和联调**
- 上午:搜索和统计接口开发
- 下午:前后端联调测试
**总工期:2.5天**
## ✅ **验收标准**
### **功能验收**
- [ ] 文章列表正确显示,支持分页
- [ ] 分类筛选功能正常
- [ ] 权限控制严格(只能看到有权限的文章)
- [ ] 搜索功能准确
- [ ] 收藏功能正常
- [ ] 阅读状态正确标记
- [ ] 统计数据准确
### **性能验收**
- [ ] 文章列表加载时间 < 1秒
- [ ] 搜索响应时间 < 2秒
- [ ] 富文本内容渲染正常
- [ ] 图片加载优化
### **安全验收**
- [ ] 权限控制严格
- [ ] 防止SQL注入
- [ ] 防止XSS攻击
- [ ] 敏感数据过滤
### **兼容性验收**
- [ ] 富文本内容在小程序中正常显示
- [ ] 图片在不同设备上正常显示
- [ ] 分页功能在不同网络环境下正常
## 🔧 **技术实现要点**
### **权限控制实现**
```php
// 使用 FIND_IN_SET 函数检查权限(推荐)
$where[] = ['', 'exp', "FIND_IN_SET({$student_id}, student_ids)"];
// 或者使用 LIKE 查询(需要确保 student_ids 格式为 ",1,2,3,")
$where[] = ['student_ids', 'like', "%,{$student_id},%"];
// 同时确保只查询启用状态的内容
$where[] = ['status', '=', 1];
$where[] = ['delete_time', '=', 0];
```
### **富文本内容处理**
```php
// 过滤危险标签
$content = strip_tags($content, '<p><br><strong><em><u><img><a>');
// 处理图片路径(如果需要)
$content = str_replace('src="/', 'src="' . $domain . '/', $content);
// 处理时间戳格式化
$create_time_formatted = date('Y-m-d H:i:s', $create_time);
$update_time_formatted = date('Y-m-d H:i:s', $update_time);
```
### **搜索优化**
```php
// 使用全文索引提高搜索性能
ALTER TABLE `school_lesson_course_teaching`
ADD FULLTEXT KEY `ft_title_content` (`title`, `content`);
// 全文搜索查询
$where[] = ['', 'exp', "MATCH(title, content) AGAINST('{$keyword}' IN NATURAL LANGUAGE MODE)"];
```
### **缓存策略**
```php
// 分类列表缓存(1小时)
$categories = Cache::remember('knowledge_categories', 3600, function() {
return $this->getCategoriesFromDB();
});
// 推荐文章缓存(30分钟)
$recommend = Cache::remember("knowledge_recommend_{$student_id}", 1800, function() use ($student_id) {
return $this->getRecommendArticles($student_id);
});
```
这个详细的开发任务文档涵盖了知识库模块的完整开发流程,包括后端接口开发、数据库设计、前后端联调和验收标准,可以作为开发团队的具体执行指南。
Loading…
Cancel
Save