diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ce29a7..965bfae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +### [v1.3.4](https://gitee.com/koogua/course-tencent-cloud/releases/v1.3.4)(2021-05-13) + +### 更新 + +- 前台增加问答功能 +- 优化标签功能 +- 优化文章功能以及全文搜索 +- 优化课程评价,咨询,文章等相关统计 +- 后台增加提问和回答审核功能 +- 后台增加查看用户在线记录 修正后台编辑角色权限错误 +- 优化前台界面 + ### [v1.3.3](https://gitee.com/koogua/course-tencent-cloud/releases/v1.3.3)(2021-04-30) ### 更新 diff --git a/app/Builders/AnswerList.php b/app/Builders/AnswerList.php new file mode 100644 index 00000000..4633f7b6 --- /dev/null +++ b/app/Builders/AnswerList.php @@ -0,0 +1,70 @@ +getQuestions($answers); + + foreach ($answers as $key => $answer) { + $answers[$key]['question'] = $questions[$answer['question_id']] ?? new \stdClass(); + } + + return $answers; + } + + public function handleUsers(array $answers) + { + $users = $this->getUsers($answers); + + foreach ($answers as $key => $answer) { + $answers[$key]['owner'] = $users[$answer['owner_id']] ?? new \stdClass(); + } + + return $answers; + } + + public function getQuestions(array $answers) + { + $ids = kg_array_column($answers, 'question_id'); + + $questionRepo = new QuestionRepo(); + + $questions = $questionRepo->findByIds($ids, ['id', 'title']); + + $result = []; + + foreach ($questions->toArray() as $question) { + $result[$question['id']] = $question; + } + + return $result; + } + + public function getUsers(array $answers) + { + $ids = kg_array_column($answers, 'owner_id'); + + $userRepo = new UserRepo(); + + $users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($users->toArray() as $user) { + $user['avatar'] = $baseUrl . $user['avatar']; + $result[$user['id']] = $user; + } + + return $result; + } + +} diff --git a/app/Builders/ArticleFavoriteList.php b/app/Builders/ArticleFavoriteList.php index 20e856a6..b81a55d7 100644 --- a/app/Builders/ArticleFavoriteList.php +++ b/app/Builders/ArticleFavoriteList.php @@ -4,6 +4,7 @@ namespace App\Builders; use App\Repos\Article as ArticleRepo; use App\Repos\User as UserRepo; +use Phalcon\Text; class ArticleFavoriteList extends Builder { @@ -38,7 +39,8 @@ class ArticleFavoriteList extends Builder $columns = [ 'id', 'title', 'cover', - 'view_count', 'like_count', 'comment_count', 'favorite_count', + 'view_count', 'like_count', + 'comment_count', 'favorite_count', ]; $articles = $articleRepo->findByIds($ids, $columns); @@ -48,7 +50,11 @@ class ArticleFavoriteList extends Builder $result = []; foreach ($articles->toArray() as $article) { - $article['cover'] = $baseUrl . $article['cover']; + + if (!empty($article['cover']) && !Text::startsWith($article['cover'], 'http')) { + $article['cover'] = $baseUrl . $article['cover']; + } + $result[$article['id']] = $article; } diff --git a/app/Builders/QuestionFavoriteList.php b/app/Builders/QuestionFavoriteList.php new file mode 100644 index 00000000..0f2cba55 --- /dev/null +++ b/app/Builders/QuestionFavoriteList.php @@ -0,0 +1,84 @@ +getQuestions($relations); + + foreach ($relations as $key => $value) { + $relations[$key]['question'] = $questions[$value['question_id']] ?? new \stdClass(); + } + + return $relations; + } + + public function handleUsers(array $relations) + { + $users = $this->getUsers($relations); + + foreach ($relations as $key => $value) { + $relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass(); + } + + return $relations; + } + + public function getQuestions(array $relations) + { + $ids = kg_array_column($relations, 'question_id'); + + $questionRepo = new QuestionRepo(); + + $columns = [ + 'id', 'title', 'cover', + 'view_count', 'like_count', + 'answer_count', 'favorite_count', + ]; + + $questions = $questionRepo->findByIds($ids, $columns); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($questions->toArray() as $question) { + + if (!empty($question['cover']) && !Text::startsWith($question['cover'], 'http')) { + $question['cover'] = $baseUrl . $question['cover']; + } + + $result[$question['id']] = $question; + } + + return $result; + } + + public function getUsers(array $relations) + { + $ids = kg_array_column($relations, 'user_id'); + + $userRepo = new UserRepo(); + + $users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($users->toArray() as $user) { + $user['avatar'] = $baseUrl . $user['avatar']; + $result[$user['id']] = $user; + } + + return $result; + } + +} diff --git a/app/Builders/QuestionList.php b/app/Builders/QuestionList.php new file mode 100644 index 00000000..3987fac9 --- /dev/null +++ b/app/Builders/QuestionList.php @@ -0,0 +1,86 @@ + $question) { + $questions[$key]['tags'] = json_decode($question['tags'], true); + } + + return $questions; + } + + public function handleCategories(array $articles) + { + $categories = $this->getCategories(); + + foreach ($articles as $key => $article) { + $articles[$key]['category'] = $categories[$article['category_id']] ?? new \stdClass(); + } + + return $articles; + } + + public function handleUsers(array $questions) + { + $users = $this->getUsers($questions); + + foreach ($questions as $key => $question) { + $questions[$key]['owner'] = $users[$question['owner_id']] ?? new \stdClass(); + $questions[$key]['last_replier'] = $users[$question['last_replier_id']] ?? new \stdClass(); + } + + return $questions; + } + + public function getCategories() + { + $cache = new CategoryListCache(); + + $items = $cache->get(CategoryModel::TYPE_QUESTION); + + if (empty($items)) return []; + + $result = []; + + foreach ($items as $item) { + $result[$item['id']] = [ + 'id' => $item['id'], + 'name' => $item['name'], + ]; + } + + return $result; + } + + public function getUsers($questions) + { + $ownerIds = kg_array_column($questions, 'owner_id'); + $lastReplierIds = kg_array_column($questions, 'last_replier_id'); + $ids = array_merge($ownerIds, $lastReplierIds); + + $userRepo = new UserRepo(); + + $users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($users->toArray() as $user) { + $user['avatar'] = $baseUrl . $user['avatar']; + $result[$user['id']] = $user; + } + + return $result; + } + +} diff --git a/app/Builders/TagFollowList.php b/app/Builders/TagFollowList.php new file mode 100644 index 00000000..c4b83377 --- /dev/null +++ b/app/Builders/TagFollowList.php @@ -0,0 +1,75 @@ +getTags($relations); + + foreach ($relations as $key => $value) { + $relations[$key]['tag'] = $tags[$value['tag_id']] ?? new \stdClass(); + } + + return $relations; + } + + public function handleUsers(array $relations) + { + $users = $this->getUsers($relations); + + foreach ($relations as $key => $value) { + $relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass(); + } + + return $relations; + } + + public function getTags(array $relations) + { + $ids = kg_array_column($relations, 'tag_id'); + + $tagRepo = new TagRepo(); + + $columns = ['id', 'name', 'alias', 'icon', 'follow_count']; + + $tags = $tagRepo->findByIds($ids, $columns); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($tags->toArray() as $tag) { + $tag['icon'] = $baseUrl . $tag['icon']; + $result[$tag['id']] = $tag; + } + + return $result; + } + + public function getUsers(array $relations) + { + $ids = kg_array_column($relations, 'user_id'); + + $userRepo = new UserRepo(); + + $users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($users->toArray() as $user) { + $user['avatar'] = $baseUrl . $user['avatar']; + $result[$user['id']] = $user; + } + + return $result; + } + +} diff --git a/app/Caches/HotQuestionList.php b/app/Caches/HotQuestionList.php new file mode 100644 index 00000000..22d115af --- /dev/null +++ b/app/Caches/HotQuestionList.php @@ -0,0 +1,114 @@ +findWeeklyHotQuestions(); + + if ($questions->count() > 0) { + return $this->handleQuestions($questions); + } + + $questions = $this->findMonthlyHotQuestions(); + + if ($questions->count() > 0) { + return $this->handleQuestions($questions); + } + + $questions = $this->findYearlyHotQuestions(); + + if ($questions->count() > 0) { + return $this->handleQuestions($questions); + } + + return []; + } + + /** + * @param QuestionModel[] $questions + * @return array + */ + protected function handleQuestions($questions) + { + $result = []; + + foreach ($questions as $question) { + $result[] = [ + 'id' => $question->id, + 'title' => $question->title, + ]; + } + + return $result; + } + + /** + * @param int $limit + * @return ResultsetInterface|Resultset|QuestionModel[] + */ + protected function findWeeklyHotQuestions($limit = 10) + { + $createTime = strtotime('monday this week'); + + return $this->findHotQuestions($createTime, $limit); + } + + /** + * @param int $limit + * @return ResultsetInterface|Resultset|QuestionModel[] + */ + protected function findMonthlyHotQuestions($limit = 10) + { + $createTime = strtotime(date('Y-m-01')); + + return $this->findHotQuestions($createTime, $limit); + } + + /** + * @param int $limit + * @return ResultsetInterface|Resultset|QuestionModel[] + */ + protected function findYearlyHotQuestions($limit = 10) + { + $createTime = strtotime(date('Y-01-01')); + + return $this->findHotQuestions($createTime, $limit); + } + + /** + * @param int $createTime + * @param int $limit + * @return ResultsetInterface|Resultset|QuestionModel[] + */ + protected function findHotQuestions($createTime, $limit = 10) + { + return QuestionModel::query() + ->where('create_time > :create_time:', ['create_time' => $createTime]) + ->orderBy('score DESC') + ->limit($limit) + ->execute(); + } + +} diff --git a/app/Caches/MaxAnswerId.php b/app/Caches/MaxAnswerId.php new file mode 100644 index 00000000..02320473 --- /dev/null +++ b/app/Caches/MaxAnswerId.php @@ -0,0 +1,29 @@ +lifetime; + } + + public function getKey($id = null) + { + return 'max_answer_id'; + } + + public function getContent($id = null) + { + $answer = AnswerModel::findFirst(['order' => 'id DESC']); + + return $answer->id ?? 0; + } + +} diff --git a/app/Caches/MaxQuestionId.php b/app/Caches/MaxQuestionId.php new file mode 100644 index 00000000..71b92f58 --- /dev/null +++ b/app/Caches/MaxQuestionId.php @@ -0,0 +1,29 @@ +lifetime; + } + + public function getKey($id = null) + { + return 'max_question_id'; + } + + public function getContent($id = null) + { + $question = QuestionModel::findFirst(['order' => 'id DESC']); + + return $question->id ?? 0; + } + +} diff --git a/app/Caches/Question.php b/app/Caches/Question.php new file mode 100644 index 00000000..b536fe37 --- /dev/null +++ b/app/Caches/Question.php @@ -0,0 +1,31 @@ +lifetime; + } + + public function getKey($id = null) + { + return "question:{$id}"; + } + + public function getContent($id = null) + { + $questionRepo = new QuestionRepo(); + + $question = $questionRepo->findById($id); + + return $question ?: null; + } + +} diff --git a/app/Caches/ArticleRelatedList.php b/app/Caches/TaggedArticleList.php similarity index 75% rename from app/Caches/ArticleRelatedList.php rename to app/Caches/TaggedArticleList.php index 78c97f4b..722e484d 100644 --- a/app/Caches/ArticleRelatedList.php +++ b/app/Caches/TaggedArticleList.php @@ -5,11 +5,9 @@ namespace App\Caches; use App\Models\Article as ArticleModel; use App\Repos\Article as ArticleRepo; -class ArticleRelatedList extends Cache +class TaggedArticleList extends Cache { - protected $articleId; - protected $limit = 5; protected $lifetime = 1 * 86400; @@ -21,25 +19,15 @@ class ArticleRelatedList extends Cache public function getKey($id = null) { - return "article_related_list:{$id}"; + return "tagged_article_list:{$id}"; } public function getContent($id = null) { - $this->articleId = $id; - $articleRepo = new ArticleRepo(); - $article = $articleRepo->findById($id); - - if (empty($article->tags)) return []; - - $tagIds = kg_array_column($article->tags, 'id'); - - $randKey = array_rand($tagIds); - $where = [ - 'tag_id' => $tagIds[$randKey], + 'tag_id' => $id, 'published' => ArticleModel::PUBLISH_APPROVED, ]; @@ -61,7 +49,7 @@ class ArticleRelatedList extends Cache $count = 0; foreach ($articles as $article) { - if ($article->id != $this->articleId && $count < $this->limit) { + if ($count < $this->limit) { $result[] = [ 'id' => $article->id, 'title' => $article->title, diff --git a/app/Caches/TaggedQuestionList.php b/app/Caches/TaggedQuestionList.php new file mode 100644 index 00000000..e1bb7c2a --- /dev/null +++ b/app/Caches/TaggedQuestionList.php @@ -0,0 +1,69 @@ +lifetime; + } + + public function getKey($id = null) + { + return "tagged_question_list:{$id}"; + } + + public function getContent($id = null) + { + + $questionRepo = new QuestionRepo(); + + $where = [ + 'tag_id' => $id, + 'published' => QuestionModel::PUBLISH_APPROVED, + ]; + + $pager = $questionRepo->paginate($where); + + if ($pager->total_items == 0) return []; + + return $this->handleContent($pager->items); + } + + /** + * @param QuestionModel[] $questions + * @return array + */ + public function handleContent($questions) + { + $result = []; + + $count = 0; + + foreach ($questions as $question) { + if ($count < $this->limit) { + $result[] = [ + 'id' => $question->id, + 'title' => $question->title, + 'view_count' => $question->view_count, + 'like_count' => $question->like_count, + 'answer_count' => $question->answer_count, + 'favorite_count' => $question->favorite_count, + ]; + $count++; + } + } + + return $result; + } + +} diff --git a/app/Caches/TopAnswererList.php b/app/Caches/TopAnswererList.php new file mode 100644 index 00000000..c4f0aaf9 --- /dev/null +++ b/app/Caches/TopAnswererList.php @@ -0,0 +1,103 @@ +lifetime; + } + + public function getKey($id = null) + { + return 'question_top_answerer_list'; + } + + public function getContent($id = null) + { + $rankings = $this->findWeeklyAuthorRankings(); + + if ($rankings->count() > 0) { + $userIds = kg_array_column($rankings->toArray(), 'author_id'); + return $this->handleUsers($userIds); + } + + $randOwners = $this->findRandArticleOwners(); + + if ($randOwners->count() > 0) { + $userIds = kg_array_column($randOwners->toArray(), 'owner_id'); + return $this->handleUsers($userIds); + } + + return []; + } + + protected function handleUsers($userIds) + { + $userRepo = new UserRepo(); + + $users = $userRepo->findByIds($userIds); + + $result = []; + + foreach ($users as $user) { + $result[] = [ + 'id' => $user->id, + 'name' => $user->name, + 'avatar' => $user->avatar, + 'title' => $user->title, + 'about' => $user->about, + 'vip' => $user->vip, + ]; + } + + return $result; + } + + /** + * @param int $limit + * @return ResultsetInterface|Resultset + */ + protected function findRandArticleOwners($limit = 10) + { + return ArticleModel::query() + ->columns(['owner_id']) + ->orderBy('RAND()') + ->limit($limit) + ->execute(); + } + + /** + * @param int $limit + * @return ResultsetInterface|Resultset + */ + protected function findWeeklyAuthorRankings($limit = 10) + { + $createTime = strtotime('monday this week'); + + $columns = [ + 'author_id' => 'a.owner_id', + 'like_count' => 'count(al.user_id)', + ]; + + return $this->modelsManager->createBuilder() + ->columns($columns) + ->addFrom(ArticleLikeModel::class, 'al') + ->join(ArticleModel::class, 'al.article_id = a.id', 'a') + ->where('al.create_time > :create_time:', ['create_time' => $createTime]) + ->groupBy('author_id') + ->orderBy('like_count DESC') + ->limit($limit)->getQuery()->execute(); + } + +} diff --git a/app/Console/Tasks/ArticleIndexTask.php b/app/Console/Tasks/ArticleIndexTask.php index addf2e4f..5528fbe0 100644 --- a/app/Console/Tasks/ArticleIndexTask.php +++ b/app/Console/Tasks/ArticleIndexTask.php @@ -118,7 +118,7 @@ class ArticleIndexTask extends Task protected function findArticles() { return ArticleModel::query() - ->where('published = 1') + ->where('published = :published:', ['published' => ArticleModel::PUBLISH_APPROVED]) ->execute(); } diff --git a/app/Console/Tasks/QuestionIndexTask.php b/app/Console/Tasks/QuestionIndexTask.php new file mode 100644 index 00000000..b6bfbc6a --- /dev/null +++ b/app/Console/Tasks/QuestionIndexTask.php @@ -0,0 +1,125 @@ +searchQuestions($query); + + var_export($result); + } + + /** + * 清空索引 + * + * @command: php console.php question_index clean + */ + public function cleanAction() + { + $this->cleanQuestionIndex(); + } + + /** + * 重建索引 + * + * @command: php console.php question_index rebuild + */ + public function rebuildAction() + { + $this->rebuildQuestionIndex(); + } + + /** + * 清空索引 + */ + protected function cleanQuestionIndex() + { + $handler = new QuestionSearcher(); + + $index = $handler->getXS()->getIndex(); + + echo '------ start clean question index ------' . PHP_EOL; + + $index->clean(); + + echo '------ end clean question index ------' . PHP_EOL; + } + + /** + * 重建索引 + */ + protected function rebuildQuestionIndex() + { + $questions = $this->findQuestions(); + + if ($questions->count() == 0) return; + + $handler = new QuestionSearcher(); + + $documenter = new QuestionDocument(); + + $index = $handler->getXS()->getIndex(); + + echo '------ start rebuild question index ------' . PHP_EOL; + + $index->beginRebuild(); + + foreach ($questions as $question) { + $document = $documenter->setDocument($question); + $index->add($document); + } + + $index->endRebuild(); + + echo '------ end rebuild question index ------' . PHP_EOL; + } + + /** + * 搜索文章 + * + * @param string $query + * @return array + * @throws \XSException + */ + protected function searchQuestions($query) + { + $handler = new QuestionSearcher(); + + return $handler->search($query); + } + + /** + * 查找文章 + * + * @return ResultsetInterface|Resultset|QuestionModel[] + */ + protected function findQuestions() + { + return QuestionModel::query() + ->where('published = :published:', ['published' => QuestionModel::PUBLISH_APPROVED]) + ->execute(); + } + +} diff --git a/app/Console/Tasks/SitemapTask.php b/app/Console/Tasks/SitemapTask.php index ad686103..a86751c8 100644 --- a/app/Console/Tasks/SitemapTask.php +++ b/app/Console/Tasks/SitemapTask.php @@ -8,6 +8,7 @@ use App\Models\Course as CourseModel; use App\Models\Help as HelpModel; use App\Models\ImGroup as ImGroupModel; use App\Models\Page as PageModel; +use App\Models\Question as QuestionModel; use App\Models\Topic as TopicModel; use App\Models\User as UserModel; use App\Services\Service as AppService; @@ -37,6 +38,7 @@ class SitemapTask extends Task $this->addIndex(); $this->addCourses(); $this->addArticles(); + $this->addQuestions(); $this->addTeachers(); $this->addTopics(); $this->addImGroups(); @@ -66,7 +68,11 @@ class SitemapTask extends Task /** * @var Resultset|CourseModel[] $courses */ - $courses = CourseModel::query()->where('published = 1')->execute(); + $courses = CourseModel::query() + ->where('published = 1') + ->orderBy('id DESC') + ->limit(500) + ->execute(); if ($courses->count() == 0) return; @@ -81,7 +87,11 @@ class SitemapTask extends Task /** * @var Resultset|ArticleModel[] $articles */ - $articles = ArticleModel::query()->where('published = 1')->execute(); + $articles = ArticleModel::query() + ->where('published = :published:', ['published' => ArticleModel::PUBLISH_APPROVED]) + ->orderBy('id DESC') + ->limit(500) + ->execute(); if ($articles->count() == 0) return; @@ -91,6 +101,25 @@ class SitemapTask extends Task } } + protected function addQuestions() + { + /** + * @var Resultset|QuestionModel[] $questions + */ + $questions = QuestionModel::query() + ->where('published = :published:', ['published' => QuestionModel::PUBLISH_APPROVED]) + ->orderBy('id DESC') + ->limit(500) + ->execute(); + + if ($questions->count() == 0) return; + + foreach ($questions as $question) { + $loc = sprintf('%s/question/%s', $this->siteUrl, $question->id); + $this->sitemap->addItem($loc, 0.8); + } + } + protected function addTeachers() { /** diff --git a/app/Console/Tasks/SyncArticleIndexTask.php b/app/Console/Tasks/SyncArticleIndexTask.php index ace75ba4..dbd0ea51 100644 --- a/app/Console/Tasks/SyncArticleIndexTask.php +++ b/app/Console/Tasks/SyncArticleIndexTask.php @@ -2,6 +2,7 @@ namespace App\Console\Tasks; +use App\Models\Article as ArticleModel; use App\Repos\Article as ArticleRepo; use App\Services\Search\ArticleDocument; use App\Services\Search\ArticleSearcher; @@ -38,7 +39,7 @@ class SyncArticleIndexTask extends Task $doc = $document->setDocument($article); - if ($article->published == 1) { + if ($article->published == ArticleModel::PUBLISH_APPROVED) { $index->update($doc); } else { $index->del($article->id); diff --git a/app/Console/Tasks/SyncArticleScoreTask.php b/app/Console/Tasks/SyncArticleScoreTask.php new file mode 100644 index 00000000..b6ee3376 --- /dev/null +++ b/app/Console/Tasks/SyncArticleScoreTask.php @@ -0,0 +1,44 @@ +getRedis(); + + $key = $this->getSyncKey(); + + $articleIds = $redis->sRandMember($key, 1000); + + if (!$articleIds) return; + + $articleRepo = new ArticleRepo(); + + $articles = $articleRepo->findByIds($articleIds); + + if ($articles->count() == 0) return; + + $service = new ArticleScoreService(); + + foreach ($articles as $article) { + $service->handle($article); + } + + $redis->sRem($key, ...$articleIds); + } + + protected function getSyncKey() + { + $sync = new ArticleScoreSync(); + + return $sync->getSyncKey(); + } + +} diff --git a/app/Console/Tasks/SyncCourseScoreTask.php b/app/Console/Tasks/SyncCourseScoreTask.php index 3a789633..b5cf1901 100644 --- a/app/Console/Tasks/SyncCourseScoreTask.php +++ b/app/Console/Tasks/SyncCourseScoreTask.php @@ -3,8 +3,8 @@ namespace App\Console\Tasks; use App\Repos\Course as CourseRepo; -use App\Services\CourseStat as CourseStatService; use App\Services\Sync\CourseScore as CourseScoreSync; +use App\Services\Utils\CourseScore as CourseScoreService; class SyncCourseScoreTask extends Task { @@ -25,10 +25,10 @@ class SyncCourseScoreTask extends Task if ($courses->count() == 0) return; - $statService = new CourseStatService(); + $service = new CourseScoreService(); foreach ($courses as $course) { - $statService->updateScore($course->id); + $service->handle($course); } $redis->sRem($key, ...$courseIds); diff --git a/app/Console/Tasks/SyncQuestionIndexTask.php b/app/Console/Tasks/SyncQuestionIndexTask.php new file mode 100644 index 00000000..6c99a6a5 --- /dev/null +++ b/app/Console/Tasks/SyncQuestionIndexTask.php @@ -0,0 +1,61 @@ +getRedis(); + + $key = $this->getSyncKey(); + + $questionIds = $redis->sRandMember($key, 1000); + + if (!$questionIds) return; + + $questionRepo = new QuestionRepo(); + + $questions = $questionRepo->findByIds($questionIds); + + if ($questions->count() == 0) return; + + $document = new QuestionDocument(); + + $handler = new QuestionSearcher(); + + $index = $handler->getXS()->getIndex(); + + $index->openBuffer(); + + foreach ($questions as $question) { + + $doc = $document->setDocument($question); + + if ($question->published == QuestionModel::PUBLISH_APPROVED) { + $index->update($doc); + } else { + $index->del($question->id); + } + } + + $index->closeBuffer(); + + $redis->sRem($key, ...$questionIds); + } + + protected function getSyncKey() + { + $sync = new QuestionIndexSync(); + + return $sync->getSyncKey(); + } + +} diff --git a/app/Console/Tasks/SyncQuestionScoreTask.php b/app/Console/Tasks/SyncQuestionScoreTask.php new file mode 100644 index 00000000..44bc0086 --- /dev/null +++ b/app/Console/Tasks/SyncQuestionScoreTask.php @@ -0,0 +1,44 @@ +getRedis(); + + $key = $this->getSyncKey(); + + $questionIds = $redis->sRandMember($key, 1000); + + if (!$questionIds) return; + + $questionRepo = new QuestionRepo(); + + $questions = $questionRepo->findByIds($questionIds); + + if ($questions->count() == 0) return; + + $service = new QuestionScoreService(); + + foreach ($questions as $question) { + $service->handle($question); + } + + $redis->sRem($key, ...$questionIds); + } + + protected function getSyncKey() + { + $sync = new QuestionScoreSync(); + + return $sync->getSyncKey(); + } + +} diff --git a/app/Http/Admin/Controllers/AnswerController.php b/app/Http/Admin/Controllers/AnswerController.php new file mode 100644 index 00000000..e22ca8f3 --- /dev/null +++ b/app/Http/Admin/Controllers/AnswerController.php @@ -0,0 +1,201 @@ +getPublishTypes(); + + $this->view->setVar('publish_types', $publishTypes); + } + + /** + * @Get("/list", name="admin.answer.list") + */ + public function listAction() + { + $answerService = new AnswerService(); + + $pager = $answerService->getAnswers(); + + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/add", name="admin.answer.add") + */ + public function addAction() + { + $id = $this->request->getQuery('question_id', 'int', 0); + + $questionService = new QuestionService(); + + $question = $questionService->getQuestion($id); + + $referer = $this->request->getHTTPReferer(); + + $this->view->setVar('question', $question); + $this->view->setVar('referer', $referer); + } + + /** + * @Get("/{id:[0-9]+}/edit", name="admin.answer.edit") + */ + public function editAction($id) + { + $answerService = new AnswerService(); + + $answer = $answerService->getAnswer($id); + + $questionService = new QuestionService(); + + $question = $questionService->getQuestion($answer->question_id); + + $referer = $this->request->getHTTPReferer(); + + $this->view->setVar('referer', $referer); + $this->view->setVar('question', $question); + $this->view->setVar('answer', $answer); + } + + /** + * @Get("/{id:[0-9]+}/show", name="admin.answer.show") + */ + public function showAction($id) + { + $answerService = new AnswerService(); + + $answer = $answerService->getAnswer($id); + + $this->view->setVar('answer', $answer); + } + + /** + * @Post("/create", name="admin.answer.create") + */ + public function createAction() + { + $answerService = new AnswerService(); + + $answerService->createAnswer(); + + $location = $this->request->getPost('referer'); + + if (empty($location)) { + $location = $this->url->get(['for' => 'admin.question.list']); + } + + $content = [ + 'location' => $location, + 'msg' => '回答问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/update", name="admin.answer.update") + */ + public function updateAction($id) + { + $answerService = new AnswerService(); + + $answerService->updateAnswer($id); + + $location = $this->request->getPost('referer'); + + if (empty($location)) { + $location = $this->url->get(['for' => 'admin.answer.list']); + } + + $content = [ + 'location' => $location, + 'msg' => '更新回答成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/delete", name="admin.answer.delete") + */ + public function deleteAction($id) + { + $answerService = new AnswerService(); + + $answerService->deleteAnswer($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '删除回答成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/restore", name="admin.answer.restore") + */ + public function restoreAction($id) + { + $answerService = new AnswerService(); + + $answerService->restoreAnswer($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '还原回答成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Route("/{id:[0-9]+}/review", name="admin.answer.review") + */ + public function reviewAction($id) + { + $answerService = new AnswerService(); + + $answer = $answerService->getAnswer($id); + + if ($this->request->isPost()) { + + $answerService->reviewAnswer($id); + + $location = $this->url->get(['for' => 'admin.mod.answers']); + + $content = [ + 'location' => $location, + 'msg' => '审核回答成功', + ]; + + return $this->jsonSuccess($content); + } + + $reasons = $answerService->getReasons(); + + $questionService = new QuestionService(); + + $question = $questionService->getQuestion($answer->question_id); + + $this->view->setVar('reasons', $reasons); + $this->view->setVar('question', $question); + $this->view->setVar('answer', $answer); + } + +} diff --git a/app/Http/Admin/Controllers/ArticleController.php b/app/Http/Admin/Controllers/ArticleController.php index 7fd4a3b4..26080afb 100644 --- a/app/Http/Admin/Controllers/ArticleController.php +++ b/app/Http/Admin/Controllers/ArticleController.php @@ -54,19 +54,6 @@ class ArticleController extends Controller $this->view->setVar('pager', $pager); } - /** - * @Get("/list/pending", name="admin.article.pending_list") - */ - public function pendingListAction() - { - $articleService = new ArticleService(); - - $pager = $articleService->getPendingArticles(); - - $this->view->pick('article/pending_list'); - $this->view->setVar('pager', $pager); - } - /** * @Get("/add", name="admin.article.add") */ @@ -106,10 +93,8 @@ class ArticleController extends Controller { $articleService = new ArticleService(); - $rejectOptions = $articleService->getRejectOptions(); $article = $articleService->getArticle($id); - $this->view->setVar('reject_options', $rejectOptions); $this->view->setVar('article', $article); } @@ -184,22 +169,31 @@ class ArticleController extends Controller } /** - * @Post("/{id:[0-9]+}/review", name="admin.article.review") + * @Route("/{id:[0-9]+}/review", name="admin.article.review") */ public function reviewAction($id) { $articleService = new ArticleService(); - $articleService->reviewArticle($id); + if ($this->request->isPost()) { - $location = $this->url->get(['for' => 'admin.article.pending_list']); + $articleService->reviewArticle($id); - $content = [ - 'location' => $location, - 'msg' => '审核文章成功', - ]; + $location = $this->url->get(['for' => 'admin.mod.articles']); - return $this->jsonSuccess($content); + $content = [ + 'location' => $location, + 'msg' => '审核文章成功', + ]; + + return $this->jsonSuccess($content); + } + + $reasons = $articleService->getReasons(); + $article = $articleService->getArticle($id); + + $this->view->setVar('reasons', $reasons); + $this->view->setVar('article', $article); } } diff --git a/app/Http/Admin/Controllers/ModerationController.php b/app/Http/Admin/Controllers/ModerationController.php index 75c81808..0ec36a01 100644 --- a/app/Http/Admin/Controllers/ModerationController.php +++ b/app/Http/Admin/Controllers/ModerationController.php @@ -22,4 +22,28 @@ class ModerationController extends Controller $this->view->setVar('pager', $pager); } + /** + * @Get("/questions", name="admin.mod.questions") + */ + public function questionsAction() + { + $modService = new ModerationService(); + + $pager = $modService->getQuestions(); + + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/answers", name="admin.mod.answers") + */ + public function answersAction() + { + $modService = new ModerationService(); + + $pager = $modService->getAnswers(); + + $this->view->setVar('pager', $pager); + } + } diff --git a/app/Http/Admin/Controllers/QuestionController.php b/app/Http/Admin/Controllers/QuestionController.php new file mode 100644 index 00000000..ef92e49d --- /dev/null +++ b/app/Http/Admin/Controllers/QuestionController.php @@ -0,0 +1,195 @@ +url->get( + ['for' => 'admin.category.list'], + ['type' => CategoryModel::TYPE_ARTICLE] + ); + + $this->response->redirect($location); + } + + /** + * @Get("/search", name="admin.question.search") + */ + public function searchAction() + { + $questionService = new QuestionService(); + + $publishTypes = $questionService->getPublishTypes(); + $categories = $questionService->getCategories(); + $xmTags = $questionService->getXmTags(0); + + $this->view->setVar('publish_types', $publishTypes); + $this->view->setVar('categories', $categories); + $this->view->setVar('xm_tags', $xmTags); + } + + /** + * @Get("/list", name="admin.question.list") + */ + public function listAction() + { + $questionService = new QuestionService(); + + $pager = $questionService->getQuestions(); + + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/add", name="admin.question.add") + */ + public function addAction() + { + $questionService = new QuestionService(); + + $categories = $questionService->getCategories(); + + $this->view->setVar('categories', $categories); + } + + /** + * @Get("/{id:[0-9]+}/edit", name="admin.question.edit") + */ + public function editAction($id) + { + $questionService = new QuestionService(); + + $publishTypes = $questionService->getPublishTypes(); + $categories = $questionService->getCategories(); + $question = $questionService->getQuestion($id); + $xmTags = $questionService->getXmTags($id); + + $this->view->setVar('publish_types', $publishTypes); + $this->view->setVar('categories', $categories); + $this->view->setVar('question', $question); + $this->view->setVar('xm_tags', $xmTags); + } + + /** + * @Get("/{id:[0-9]+}/show", name="admin.question.show") + */ + public function showAction($id) + { + $questionService = new QuestionService(); + + $question = $questionService->getQuestion($id); + + $this->view->setVar('question', $question); + } + + /** + * @Post("/create", name="admin.question.create") + */ + public function createAction() + { + $questionService = new QuestionService(); + + $question = $questionService->createQuestion(); + + $location = $this->url->get([ + 'for' => 'admin.question.edit', + 'id' => $question->id, + ]); + + $content = [ + 'location' => $location, + 'msg' => '创建问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/update", name="admin.question.update") + */ + public function updateAction($id) + { + $questionService = new QuestionService(); + + $questionService->updateQuestion($id); + + $content = ['msg' => '更新问题成功']; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/delete", name="admin.question.delete") + */ + public function deleteAction($id) + { + $questionService = new QuestionService(); + + $questionService->deleteQuestion($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '删除问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/restore", name="admin.question.restore") + */ + public function restoreAction($id) + { + $questionService = new QuestionService(); + + $questionService->restoreQuestion($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '还原问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Route("/{id:[0-9]+}/review", name="admin.question.review") + */ + public function reviewAction($id) + { + $questionService = new QuestionService(); + + if ($this->request->isPost()) { + + $questionService->reviewQuestion($id); + + $location = $this->url->get(['for' => 'admin.mod.questions']); + + $content = [ + 'location' => $location, + 'msg' => '审核问题成功', + ]; + + return $this->jsonSuccess($content); + } + + $reasons = $questionService->getReasons(); + $question = $questionService->getQuestion($id); + + $this->view->setVar('reasons', $reasons); + $this->view->setVar('question', $question); + } + +} diff --git a/app/Http/Admin/Controllers/UploadController.php b/app/Http/Admin/Controllers/UploadController.php index 25edb530..b34c5f3a 100644 --- a/app/Http/Admin/Controllers/UploadController.php +++ b/app/Http/Admin/Controllers/UploadController.php @@ -53,6 +53,27 @@ class UploadController extends Controller return $this->jsonSuccess(['data' => $data]); } + /** + * @Post("/icon/img", name="admin.upload.icon_img") + */ + public function uploadIconImageAction() + { + $service = new StorageService(); + + $file = $service->uploadIconImage(); + + if (!$file) { + return $this->jsonError(['msg' => '上传文件失败']); + } + + $data = [ + 'src' => $service->getImageUrl($file->path), + 'title' => $file->name, + ]; + + return $this->jsonSuccess(['data' => $data]); + } + /** * @Post("/cover/img", name="admin.upload.cover_img") */ @@ -127,7 +148,6 @@ class UploadController extends Controller $items['user_avatar'] = $service->uploadDefaultUserAvatar(); $items['group_avatar'] = $service->uploadDefaultGroupAvatar(); - $items['article_cover'] = $service->uploadDefaultArticleCover(); $items['course_cover'] = $service->uploadDefaultCourseCover(); $items['package_cover'] = $service->uploadDefaultPackageCover(); $items['gift_cover'] = $service->uploadDefaultGiftCover(); diff --git a/app/Http/Admin/Controllers/UserController.php b/app/Http/Admin/Controllers/UserController.php index 5d106768..b53e405d 100644 --- a/app/Http/Admin/Controllers/UserController.php +++ b/app/Http/Admin/Controllers/UserController.php @@ -94,6 +94,18 @@ class UserController extends Controller $this->view->setVar('admin_roles', $adminRoles); } + /** + * @Get("/{id:[0-9]+}/online", name="admin.user.online") + */ + public function onlineAction($id) + { + $userService = new UserService(); + + $pager = $userService->getOnlineLogs($id); + + $this->view->setVar('pager', $pager); + } + /** * @Post("/{id:[0-9]+}/update", name="admin.user.update") */ diff --git a/app/Http/Admin/Services/Answer.php b/app/Http/Admin/Services/Answer.php new file mode 100644 index 00000000..b1fd1263 --- /dev/null +++ b/app/Http/Admin/Services/Answer.php @@ -0,0 +1,295 @@ +getParams(); + + $params['deleted'] = $params['deleted'] ?? 0; + + $sort = $pagerQuery->getSort(); + $page = $pagerQuery->getPage(); + $limit = $pagerQuery->getLimit(); + + $answerRepo = new AnswerRepo(); + + $pager = $answerRepo->paginate($params, $sort, $page, $limit); + + return $this->handleAnswers($pager); + } + + public function getAnswer($id) + { + return $this->findOrFail($id); + } + + public function createAnswer() + { + $post = $this->request->getPost(); + + $user = $this->getLoginUser(); + + $validator = new AnswerValidator(); + + $question = $validator->checkQuestion($post['question_id']); + + $answer = new AnswerModel(); + + $answer->owner_id = $user->id; + $answer->question_id = $question->id; + $answer->published = AnswerModel::PUBLISH_APPROVED; + $answer->content = $validator->checkContent($post['content']); + + $answer->create(); + + $this->recountQuestionAnswers($question); + $this->recountUserAnswers($user); + $this->handleAnswerPostPoint($answer); + $this->handleQuestionAnsweredNotice($answer); + + return $answer; + } + + public function updateAnswer($id) + { + $answer = $this->findOrFail($id); + + $post = $this->request->getPost(); + + $validator = new AnswerValidator(); + + $data = []; + + if (isset($post['content'])) { + $data['content'] = $validator->checkContent($post['content']); + } + + if (isset($post['published'])) { + + $data['published'] = $validator->checkPublishStatus($post['published']); + + $question = $this->findQuestion($answer->question_id); + + $this->recountQuestionAnswers($question); + + $user = $this->findUser($answer->owner_id); + + $this->recountUserAnswers($user); + } + + $answer->update($data); + + return $answer; + } + + public function deleteAnswer($id) + { + $answer = $this->findOrFail($id); + + $answer->deleted = 1; + + $answer->update(); + + $question = $this->findQuestion($answer->question_id); + + $this->recountQuestionAnswers($question); + + $owner = $this->findUser($answer->owner_id); + + $this->recountUserAnswers($owner); + + return $answer; + } + + public function restoreAnswer($id) + { + $answer = $this->findOrFail($id); + + $answer->deleted = 0; + + $answer->update(); + + $question = $this->findQuestion($answer->question_id); + + $this->recountQuestionAnswers($question); + + $owner = $this->findUser($answer->owner_id); + + $this->recountUserAnswers($owner); + + return $answer; + } + + public function reviewAnswer($id) + { + $type = $this->request->getPost('type', ['trim', 'string']); + $reason = $this->request->getPost('reason', ['trim', 'string']); + + $answer = $this->findOrFail($id); + + $validator = new AnswerValidator(); + + if ($type == 'approve') { + $answer->published = AnswerModel::PUBLISH_APPROVED; + } elseif ($type == 'reject') { + $validator->checkRejectReason($reason); + $answer->published = AnswerModel::PUBLISH_REJECTED; + } + + $answer->update(); + + $question = $this->findQuestion($answer->question_id); + + $this->recountQuestionAnswers($question); + + $owner = $this->findUser($answer->owner_id); + + $this->recountUserAnswers($owner); + + $sender = $this->getLoginUser(); + + if ($type == 'approve') { + + $this->handleAnswerPostPoint($answer); + $this->handleAnswerApprovedNotice($answer, $sender); + + $this->eventsManager->fire('Answer:afterApprove', $this, $answer); + + } elseif ($type == 'reject') { + + $options = ReasonModel::answerRejectOptions(); + + if (array_key_exists($reason, $options)) { + $reason = $options[$reason]; + } + + $this->handleAnswerRejectedNotice($answer, $sender, $reason); + + $this->eventsManager->fire('Answer:afterReject', $this, $answer); + } + + return $answer; + } + + protected function findOrFail($id) + { + $validator = new AnswerValidator(); + + return $validator->checkAnswer($id); + } + + protected function findQuestion($id) + { + $questionRepo = new QuestionRepo(); + + return $questionRepo->findById($id); + } + + protected function findUser($id) + { + $userRepo = new UserRepo(); + + return $userRepo->findById($id); + } + + protected function handleAnswers($pager) + { + if ($pager->total_items > 0) { + + $builder = new AnswerListBuilder(); + + $items = $pager->items->toArray(); + + $pipeA = $builder->handleQuestions($items); + $pipeB = $builder->handleUsers($pipeA); + $pipeC = $builder->objects($pipeB); + + $pager->items = $pipeC; + } + + return $pager; + } + + protected function recountQuestionAnswers(QuestionModel $question) + { + $questionRepo = new QuestionRepo(); + + $answerCount = $questionRepo->countAnswers($question->id); + + $question->answer_count = $answerCount; + + $question->update(); + } + + protected function recountUserAnswers(UserModel $user) + { + $userRepo = new UserRepo(); + + $answerCount = $userRepo->countAnswers($user->id); + + $user->answer_count = $answerCount; + + $user->update(); + } + + protected function handleQuestionAnsweredNotice(AnswerModel $answer) + { + $notice = new QuestionAnsweredNotice(); + + $notice->handle($answer); + } + + protected function handleAnswerApprovedNotice(AnswerModel $answer, UserModel $sender) + { + $notice = new AnswerApprovedNotice(); + + $notice->handle($answer, $sender); + } + + protected function handleAnswerRejectedNotice(AnswerModel $answer, UserModel $sender, $reason) + { + $notice = new AnswerRejectedNotice(); + + $notice->handle($answer, $sender, $reason); + + } + + protected function handleAnswerPostPoint(AnswerModel $answer) + { + $service = new AnswerPostPointHistory(); + + $service->handle($answer); + } + +} diff --git a/app/Http/Admin/Services/Article.php b/app/Http/Admin/Services/Article.php index 98674a74..fa39df84 100644 --- a/app/Http/Admin/Services/Article.php +++ b/app/Http/Admin/Services/Article.php @@ -7,15 +7,14 @@ use App\Caches\Article as ArticleCache; use App\Library\Paginator\Query as PagerQuery; use App\Library\Utils\Word as WordUtil; use App\Models\Article as ArticleModel; -use App\Models\ArticleTag as ArticleTagModel; use App\Models\Category as CategoryModel; use App\Models\Reason as ReasonModel; use App\Models\User as UserModel; use App\Repos\Article as ArticleRepo; -use App\Repos\ArticleTag as ArticleTagRepo; use App\Repos\Category as CategoryRepo; use App\Repos\Tag as TagRepo; use App\Repos\User as UserRepo; +use App\Services\Logic\Article\ArticleDataTrait; use App\Services\Logic\Notice\System\ArticleApproved as ArticleApprovedNotice; use App\Services\Logic\Notice\System\ArticleRejected as ArticleRejectedNotice; use App\Services\Logic\Point\History\ArticlePost as ArticlePostPointHistory; @@ -25,20 +24,13 @@ use App\Validators\Article as ArticleValidator; class Article extends Service { - public function getArticleModel() - { - $article = new ArticleModel(); - - $article->afterFetch(); - - return $article; - } + use ArticleDataTrait; public function getXmTags($id) { $tagRepo = new TagRepo(); - $allTags = $tagRepo->findAll(['published' => 1], 'priority'); + $allTags = $tagRepo->findAll(['published' => 1]); if ($allTags->count() == 0) return []; @@ -86,7 +78,7 @@ class Article extends Service return ArticleModel::sourceTypes(); } - public function getRejectOptions() + public function getReasons() { return ReasonModel::articleRejectOptions(); } @@ -132,13 +124,14 @@ class Article extends Service $article = new ArticleModel(); + $article->published = ArticleModel::PUBLISH_APPROVED; $article->owner_id = $user->id; $article->category_id = $category->id; $article->title = $title; $article->create(); - $this->incrUserArticleCount($user); + $this->recountUserArticles($user); $this->eventsManager->fire('Article:afterCreate', $this, $article); @@ -164,14 +157,6 @@ class Article extends Service $data['title'] = $validator->checkTitle($post['title']); } - if (isset($post['cover'])) { - $data['cover'] = $validator->checkCover($post['cover']); - } - - if (isset($post['summary'])) { - $data['summary'] = $validator->checkSummary($post['summary']); - } - if (isset($post['content'])) { $data['content'] = $validator->checkContent($post['content']); $data['word_count'] = WordUtil::getWordCount($data['content']); @@ -184,8 +169,8 @@ class Article extends Service } } - if (isset($post['allow_comment'])) { - $data['allow_comment'] = $post['allow_comment']; + if (isset($post['closed'])) { + $data['closed'] = $validator->checkCloseStatus($post['closed']); } if (isset($post['private'])) { @@ -197,7 +182,12 @@ class Article extends Service } if (isset($post['published'])) { + $data['published'] = $validator->checkPublishStatus($post['published']); + + $owner = $this->findUser($article->owner_id); + + $this->recountUserArticles($owner); } if (isset($post['xm_tag_ids'])) { @@ -225,7 +215,7 @@ class Article extends Service $owner = $userRepo->findById($article->owner_id); - $this->decrUserArticleCount($owner); + $this->recountUserArticles($owner); $this->rebuildArticleIndex($article); @@ -246,7 +236,7 @@ class Article extends Service $owner = $userRepo->findById($article->owner_id); - $this->incrUserArticleCount($owner); + $this->recountUserArticles($owner); $this->rebuildArticleIndex($article); @@ -262,14 +252,25 @@ class Article extends Service $article = $this->findOrFail($id); + $validator = new ArticleValidator(); + if ($type == 'approve') { + $article->published = ArticleModel::PUBLISH_APPROVED; + } elseif ($type == 'reject') { + + $validator->checkRejectReason($reason); + $article->published = ArticleModel::PUBLISH_REJECTED; } $article->update(); + $owner = $this->findUser($article->owner_id); + + $this->recountUserArticles($owner); + $sender = $this->getLoginUser(); if ($type == 'approve') { @@ -309,59 +310,11 @@ class Article extends Service return $validator->checkArticle($id); } - protected function saveTags(ArticleModel $article, $tagIds) + protected function findUser($id) { - $originTagIds = []; + $userRepo = new UserRepo(); - /** - * 修改数据后,afterFetch设置的属性会失效,重新执行 - */ - $article->afterFetch(); - - if ($article->tags) { - $originTagIds = kg_array_column($article->tags, 'id'); - } - - $newTagIds = $tagIds ? explode(',', $tagIds) : []; - $addedTagIds = array_diff($newTagIds, $originTagIds); - - if ($addedTagIds) { - foreach ($addedTagIds as $tagId) { - $articleTag = new ArticleTagModel(); - $articleTag->article_id = $article->id; - $articleTag->tag_id = $tagId; - $articleTag->create(); - } - } - - $deletedTagIds = array_diff($originTagIds, $newTagIds); - - if ($deletedTagIds) { - $articleTagRepo = new ArticleTagRepo(); - foreach ($deletedTagIds as $tagId) { - $articleTag = $articleTagRepo->findArticleTag($article->id, $tagId); - if ($articleTag) { - $articleTag->delete(); - } - } - } - - $articleTags = []; - - if ($newTagIds) { - $tagRepo = new TagRepo(); - $tags = $tagRepo->findByIds($newTagIds); - if ($tags->count() > 0) { - $articleTags = []; - foreach ($tags as $tag) { - $articleTags[] = ['id' => $tag->id, 'name' => $tag->name]; - } - } - } - - $article->tags = $articleTags; - - $article->update(); + return $userRepo->findById($id); } protected function handleArticles($pager) @@ -383,21 +336,17 @@ class Article extends Service return $pager; } - protected function incrUserArticleCount(UserModel $user) + protected function recountUserArticles(UserModel $user) { - $user->article_count += 1; + $userRepo = new UserRepo(); + + $articleCount = $userRepo->countArticles($user->id); + + $user->article_count = $articleCount; $user->update(); } - protected function decrUserArticleCount(UserModel $user) - { - if ($user->article_count > 0) { - $user->article_count -= 1; - $user->update(); - } - } - protected function rebuildArticleCache(ArticleModel $article) { $cache = new ArticleCache(); diff --git a/app/Http/Admin/Services/AuthNode.php b/app/Http/Admin/Services/AuthNode.php index 1c7871e3..b62914ae 100644 --- a/app/Http/Admin/Services/AuthNode.php +++ b/app/Http/Admin/Services/AuthNode.php @@ -272,12 +272,6 @@ class AuthNode extends Service 'type' => 'button', 'route' => 'admin.article.edit', ], - [ - 'id' => '1-7-6', - 'title' => '文章分类', - 'type' => 'menu', - 'route' => 'admin.article.category', - ], [ 'id' => '1-7-5', 'title' => '删除文章', @@ -288,7 +282,7 @@ class AuthNode extends Service 'id' => '1-7-9', 'title' => '文章详情', 'type' => 'button', - 'route' => 'admin.article.review', + 'route' => 'admin.article.show', ], [ 'id' => '1-7-10', @@ -298,6 +292,104 @@ class AuthNode extends Service ], ], ], + [ + 'id' => '1-10', + 'title' => '问题管理', + 'type' => 'menu', + 'children' => [ + [ + 'id' => '1-10-1', + 'title' => '问题列表', + 'type' => 'menu', + 'route' => 'admin.question.list', + ], + [ + 'id' => '1-10-2', + 'title' => '搜索问题', + 'type' => 'menu', + 'route' => 'admin.question.search', + ], + [ + 'id' => '1-10-3', + 'title' => '添加问题', + 'type' => 'menu', + 'route' => 'admin.question.add', + ], + [ + 'id' => '1-10-4', + 'title' => '编辑问题', + 'type' => 'button', + 'route' => 'admin.question.edit', + ], + [ + 'id' => '1-10-5', + 'title' => '删除问题', + 'type' => 'button', + 'route' => 'admin.question.delete', + ], + [ + 'id' => '1-10-9', + 'title' => '问题详情', + 'type' => 'button', + 'route' => 'admin.question.show', + ], + [ + 'id' => '1-10-10', + 'title' => '审核问题', + 'type' => 'button', + 'route' => 'admin.question.review', + ], + ], + ], + [ + 'id' => '1-11', + 'title' => '回答管理', + 'type' => 'menu', + 'children' => [ + [ + 'id' => '1-11-1', + 'title' => '回答列表', + 'type' => 'menu', + 'route' => 'admin.answer.list', + ], + [ + 'id' => '1-11-2', + 'title' => '搜索回答', + 'type' => 'menu', + 'route' => 'admin.answer.search', + ], + [ + 'id' => '1-11-3', + 'title' => '添加答案', + 'type' => 'button', + 'route' => 'admin.answer.add', + ], + [ + 'id' => '1-11-4', + 'title' => '编辑回答', + 'type' => 'button', + 'route' => 'admin.answer.edit', + ], + [ + 'id' => '1-11-5', + 'title' => '删除回答', + 'type' => 'button', + 'route' => 'admin.answer.delete', + ], + [ + 'id' => '1-11-9', + 'title' => '回答详情', + 'type' => 'button', + 'route' => 'admin.answer.show', + ], + [ + 'id' => '1-11-10', + 'title' => '审核回答', + 'type' => 'button', + 'route' => 'admin.answer.review', + ], + ], + ], [ 'id' => '1-8', 'title' => '标签管理', @@ -387,6 +479,18 @@ class AuthNode extends Service 'type' => 'menu', 'route' => 'admin.mod.articles', ], + [ + 'id' => '2-10-2', + 'title' => '问题列表', + 'type' => 'menu', + 'route' => 'admin.mod.questions', + ], + [ + 'id' => '2-10-3', + 'title' => '回答列表', + 'type' => 'menu', + 'route' => 'admin.mod.answers', + ], ], ], [ @@ -542,6 +646,12 @@ class AuthNode extends Service 'type' => 'menu', 'route' => 'admin.slide.list', ], + [ + 'id' => '2-5-5', + 'title' => '搜索轮播', + 'type' => 'menu', + 'route' => 'admin.slide.search', + ], [ 'id' => '2-5-2', 'title' => '添加轮播', @@ -560,12 +670,6 @@ class AuthNode extends Service 'type' => 'button', 'route' => 'admin.slide.delete', ], - [ - 'id' => '2-5-5', - 'title' => '搜索轮播', - 'type' => 'menu', - 'route' => 'admin.slide.search', - ], ], ], [ @@ -851,7 +955,13 @@ class AuthNode extends Service 'title' => '编辑用户', 'type' => 'button', 'route' => 'admin.user.edit', - ] + ], + [ + 'id' => '4-1-5', + 'title' => '在线记录', + 'type' => 'button', + 'route' => 'admin.user.online', + ], ], ], [ diff --git a/app/Http/Admin/Services/Consult.php b/app/Http/Admin/Services/Consult.php index 667b99a7..cb93d9a5 100644 --- a/app/Http/Admin/Services/Consult.php +++ b/app/Http/Admin/Services/Consult.php @@ -4,7 +4,10 @@ namespace App\Http\Admin\Services; use App\Builders\ConsultList as ConsultListBuilder; use App\Library\Paginator\Query as PagerQuery; +use App\Models\Chapter as ChapterModel; use App\Models\Consult as ConsultModel; +use App\Models\Course as CourseModel; +use App\Repos\Chapter as ChapterRepo; use App\Repos\Consult as ConsultRepo; use App\Repos\Course as CourseRepo; use App\Services\Logic\Notice\ConsultReply as ConsultReplyNotice; @@ -74,6 +77,7 @@ class Consult extends Service if (isset($post['published'])) { $data['published'] = $validator->checkPublishStatus($post['published']); + $this->handleItemConsults($consult); } $consult->update($data); @@ -93,13 +97,7 @@ class Consult extends Service $consult->update(); - $courseRepo = new CourseRepo(); - - $course = $courseRepo->findById($consult->course_id); - - $course->consult_count -= 1; - - $course->update(); + $this->handleItemConsults($consult); } public function restoreConsult($id) @@ -110,13 +108,20 @@ class Consult extends Service $consult->update(); - $courseRepo = new CourseRepo(); + $this->handleItemConsults($consult); + } - $course = $courseRepo->findById($consult->course_id); + protected function handleItemConsults(ConsultModel $consult) + { + if ($consult->course_id > 0) { + $course = $this->findCourse($consult->course_id); + $this->recountCourseConsults($course); + } - $course->consult_count += 1; - - $course->update(); + if ($consult->chapter_id > 0) { + $chapter = $this->findChapter($consult->chapter_id); + $this->recountChapterConsults($chapter); + } } protected function handleReplyNotice(ConsultModel $consult) @@ -133,6 +138,42 @@ class Consult extends Service return $validator->checkConsult($id); } + protected function findCourse($id) + { + $courseRepo = new CourseRepo(); + + return $courseRepo->findById($id); + } + + protected function findChapter($id) + { + $chapterRepo = new ChapterRepo(); + + return $chapterRepo->findById($id); + } + + protected function recountCourseConsults(CourseModel $course) + { + $courseRepo = new CourseRepo(); + + $consultCount = $courseRepo->countConsults($course->id); + + $course->consult_count = $consultCount; + + $course->update(); + } + + protected function recountChapterConsults(ChapterModel $chapter) + { + $chapterRepo = new ChapterRepo(); + + $consultCount = $chapterRepo->countConsults($chapter->id); + + $chapter->consult_count = $consultCount; + + $chapter->update(); + } + protected function handleConsults($pager) { if ($pager->total_items > 0) { diff --git a/app/Http/Admin/Services/Moderation.php b/app/Http/Admin/Services/Moderation.php index 68b69cc0..fbf3cfa7 100644 --- a/app/Http/Admin/Services/Moderation.php +++ b/app/Http/Admin/Services/Moderation.php @@ -2,10 +2,16 @@ namespace App\Http\Admin\Services; +use App\Builders\AnswerList as AnswerListBuilder; use App\Builders\ArticleList as ArticleListBuilder; +use App\Builders\QuestionList as QuestionListBuilder; use App\Library\Paginator\Query as PagerQuery; +use App\Models\Answer as AnswerModel; use App\Models\Article as ArticleModel; +use App\Models\Question as QuestionModel; +use App\Repos\Answer as AnswerRepo; use App\Repos\Article as ArticleRepo; +use App\Repos\Question as QuestionRepo; class Moderation extends Service { @@ -30,6 +36,46 @@ class Moderation extends Service return $this->handleArticles($pager); } + public function getQuestions() + { + $pagerQuery = new PagerQuery(); + + $params = $pagerQuery->getParams(); + + $params['published'] = QuestionModel::PUBLISH_PENDING; + $params['deleted'] = 0; + + $sort = $pagerQuery->getSort(); + $page = $pagerQuery->getPage(); + $limit = $pagerQuery->getLimit(); + + $questionRepo = new QuestionRepo(); + + $pager = $questionRepo->paginate($params, $sort, $page, $limit); + + return $this->handleQuestions($pager); + } + + public function getAnswers() + { + $pagerQuery = new PagerQuery(); + + $params = $pagerQuery->getParams(); + + $params['published'] = AnswerModel::PUBLISH_PENDING; + $params['deleted'] = 0; + + $sort = $pagerQuery->getSort(); + $page = $pagerQuery->getPage(); + $limit = $pagerQuery->getLimit(); + + $answerRepo = new AnswerRepo(); + + $pager = $answerRepo->paginate($params, $sort, $page, $limit); + + return $this->handleAnswers($pager); + } + protected function handleArticles($pager) { if ($pager->total_items > 0) { @@ -49,4 +95,41 @@ class Moderation extends Service return $pager; } + protected function handleQuestions($pager) + { + if ($pager->total_items > 0) { + + $builder = new QuestionListBuilder(); + + $items = $pager->items->toArray(); + + $pipeA = $builder->handleQuestions($items); + $pipeB = $builder->handleCategories($pipeA); + $pipeC = $builder->handleUsers($pipeB); + $pipeD = $builder->objects($pipeC); + + $pager->items = $pipeD; + } + + return $pager; + } + + protected function handleAnswers($pager) + { + if ($pager->total_items > 0) { + + $builder = new AnswerListBuilder(); + + $items = $pager->items->toArray(); + + $pipeA = $builder->handleQuestions($items); + $pipeB = $builder->handleUsers($pipeA); + $pipeC = $builder->objects($pipeB); + + $pager->items = $pipeC; + } + + return $pager; + } + } diff --git a/app/Http/Admin/Services/Question.php b/app/Http/Admin/Services/Question.php new file mode 100644 index 00000000..eda8ffd3 --- /dev/null +++ b/app/Http/Admin/Services/Question.php @@ -0,0 +1,349 @@ +findAll(['published' => 1]); + + if ($allTags->count() == 0) return []; + + $questionTagIds = []; + + if ($id > 0) { + $question = $this->findOrFail($id); + if (!empty($question->tags)) { + $questionTagIds = kg_array_column($question->tags, 'id'); + } + } + + $list = []; + + foreach ($allTags as $tag) { + $selected = in_array($tag->id, $questionTagIds); + $list[] = [ + 'name' => $tag->name, + 'value' => $tag->id, + 'selected' => $selected, + ]; + } + + return $list; + } + + public function getCategories() + { + $categoryRepo = new CategoryRepo(); + + return $categoryRepo->findAll([ + 'type' => CategoryModel::TYPE_ARTICLE, + 'level' => 1, + 'published' => 1, + ]); + } + + public function getPublishTypes() + { + return QuestionModel::publishTypes(); + } + + public function getReasons() + { + return ReasonModel::questionRejectOptions(); + } + + public function getQuestions() + { + $pagerQuery = new PagerQuery(); + + $params = $pagerQuery->getParams(); + + if (!empty($params['xm_tag_ids'])) { + $params['tag_id'] = explode(',', $params['xm_tag_ids']); + } + + $params['deleted'] = $params['deleted'] ?? 0; + + $sort = $pagerQuery->getSort(); + $page = $pagerQuery->getPage(); + $limit = $pagerQuery->getLimit(); + + $questionRepo = new QuestionRepo(); + + $pager = $questionRepo->paginate($params, $sort, $page, $limit); + + return $this->handleQuestions($pager); + } + + public function getQuestion($id) + { + return $this->findOrFail($id); + } + + public function createQuestion() + { + $post = $this->request->getPost(); + + $user = $this->getLoginUser(); + + $validator = new QuestionValidator(); + + $title = $validator->checkTitle($post['title']); + + $question = new QuestionModel(); + + $question->published = QuestionModel::PUBLISH_APPROVED; + $question->owner_id = $user->id; + $question->title = $title; + + $question->create(); + + $this->recountUserQuestions($user); + + $this->eventsManager->fire('Question:afterCreate', $this, $question); + + return $question; + } + + public function updateQuestion($id) + { + $post = $this->request->getPost(); + + $question = $this->findOrFail($id); + + $validator = new QuestionValidator(); + + $data = []; + + if (isset($post['category_id'])) { + $category = $validator->checkCategory($post['category_id']); + $data['category_id'] = $category->id; + } + + if (isset($post['title'])) { + $data['title'] = $validator->checkTitle($post['title']); + } + + if (isset($post['content'])) { + $data['content'] = $validator->checkContent($post['content']); + } + + if (isset($post['anonymous'])) { + $data['anonymous'] = $validator->checkAnonymousStatus($post['anonymous']); + } + + if (isset($post['closed'])) { + $data['closed'] = $validator->checkCloseStatus($post['closed']); + } + + if (isset($post['published'])) { + + $data['published'] = $validator->checkPublishStatus($post['published']); + + $owner = $this->findUser($question->owner_id); + + $this->recountUserQuestions($owner); + } + + if (isset($post['xm_tag_ids'])) { + $this->saveTags($question, $post['xm_tag_ids']); + } + + $question->update($data); + + $this->rebuildQuestionIndex($question); + + $this->eventsManager->fire('Question:afterUpdate', $this, $question); + + return $question; + } + + public function deleteQuestion($id) + { + $question = $this->findOrFail($id); + + $question->deleted = 1; + + $question->update(); + + $userRepo = new UserRepo(); + + $owner = $userRepo->findById($question->owner_id); + + $this->recountUserQuestions($owner); + + $this->rebuildQuestionIndex($question); + + $this->eventsManager->fire('Question:afterDelete', $this, $question); + + return $question; + } + + public function restoreQuestion($id) + { + $question = $this->findOrFail($id); + + $question->deleted = 0; + + $question->update(); + + $userRepo = new UserRepo(); + + $owner = $userRepo->findById($question->owner_id); + + $this->recountUserQuestions($owner); + + $this->rebuildQuestionIndex($question); + + $this->eventsManager->fire('Question:afterRestore', $this, $question); + + return $question; + } + + public function reviewQuestion($id) + { + $type = $this->request->getPost('type', ['trim', 'string']); + $reason = $this->request->getPost('reason', ['trim', 'string']); + + $question = $this->findOrFail($id); + + $validator = new QuestionValidator(); + + if ($type == 'approve') { + $question->published = QuestionModel::PUBLISH_APPROVED; + } elseif ($type == 'reject') { + $validator->checkRejectReason($reason); + $question->published = QuestionModel::PUBLISH_REJECTED; + } + + $question->update(); + + $owner = $this->findUser($question->owner_id); + + $this->recountUserQuestions($owner); + + $sender = $this->getLoginUser(); + + if ($type == 'approve') { + + $this->rebuildQuestionIndex($question); + + $this->handlePostPoint($question); + + $notice = new QuestionApprovedNotice(); + + $notice->handle($question, $sender); + + $this->eventsManager->fire('Question:afterApprove', $this, $question); + + } elseif ($type == 'reject') { + + $options = ReasonModel::questionRejectOptions(); + + if (array_key_exists($reason, $options)) { + $reason = $options[$reason]; + } + + $notice = new QuestionRejectedNotice(); + + $notice->handle($question, $sender, $reason); + + $this->eventsManager->fire('Question:afterReject', $this, $question); + } + + return $question; + } + + protected function findOrFail($id) + { + $validator = new QuestionValidator(); + + return $validator->checkQuestion($id); + } + + protected function findUser($id) + { + $userRepo = new UserRepo(); + + return $userRepo->findById($id); + } + + protected function handleQuestions($pager) + { + if ($pager->total_items > 0) { + + $builder = new QuestionListBuilder(); + + $items = $pager->items->toArray(); + + $pipeA = $builder->handleQuestions($items); + $pipeB = $builder->handleCategories($pipeA); + $pipeC = $builder->handleUsers($pipeB); + $pipeD = $builder->objects($pipeC); + + $pager->items = $pipeD; + } + + return $pager; + } + + protected function recountUserQuestions(UserModel $user) + { + $userRepo = new UserRepo(); + + $questionCount = $userRepo->countQuestions($user->id); + + $user->question_count = $questionCount; + + $user->update(); + } + + protected function rebuildQuestionCache(QuestionModel $question) + { + $cache = new QuestionCache(); + + $cache->rebuild($question->id); + } + + protected function rebuildQuestionIndex(QuestionModel $question) + { + $sync = new QuestionIndexSync(); + + $sync->addItem($question->id); + } + + protected function handlePostPoint(QuestionModel $question) + { + if ($question->published != QuestionModel::PUBLISH_APPROVED) return; + + $service = new QuestionPostPointHistory(); + + $service->handle($question); + } + +} diff --git a/app/Http/Admin/Services/Review.php b/app/Http/Admin/Services/Review.php index 5ae43fb6..e04c41fb 100644 --- a/app/Http/Admin/Services/Review.php +++ b/app/Http/Admin/Services/Review.php @@ -4,6 +4,7 @@ namespace App\Http\Admin\Services; use App\Builders\ReviewList as ReviewListBuilder; use App\Library\Paginator\Query as PagerQuery; +use App\Models\Course as CourseModel; use App\Repos\Course as CourseRepo; use App\Repos\Review as ReviewRepo; use App\Services\CourseStat as CourseStatService; @@ -47,6 +48,8 @@ class Review extends Service { $review = $this->findOrFail($id); + $course = $this->findCourse($review->course_id); + $post = $this->request->getPost(); $validator = new ReviewValidator(); @@ -71,11 +74,12 @@ class Review extends Service if (isset($post['published'])) { $data['published'] = $validator->checkPublishStatus($post['published']); + $this->recountCourseReviews($course); } $review->update($data); - $this->updateCourseRating($review->course_id); + $this->updateCourseRating($course); return $review; } @@ -88,7 +92,9 @@ class Review extends Service $review->update(); - $this->decrCourseReviewCount($review->course_id); + $course = $this->findCourse($review->course_id); + + $this->recountCourseReviews($course); } public function restoreReview($id) @@ -99,7 +105,9 @@ class Review extends Service $review->update(); - $this->incrCourseReviewCount($review->course_id); + $course = $this->findCourse($review->course_id); + + $this->recountCourseReviews($course); } protected function findOrFail($id) @@ -109,33 +117,29 @@ class Review extends Service return $validator->checkReview($id); } - protected function incrCourseReviewCount($courseId) + protected function findCourse($id) { $courseRepo = new CourseRepo(); - $course = $courseRepo->findById($courseId); + return $courseRepo->findById($id); + } - $course->review_count -= 1; + protected function recountCourseReviews(CourseModel $course) + { + $courseRepo = new CourseRepo(); + + $reviewCount = $courseRepo->countReviews($course->id); + + $course->review_count = $reviewCount; $course->update(); } - protected function decrCourseReviewCount($courseId) - { - $courseRepo = new CourseRepo(); - - $course = $courseRepo->findById($courseId); - - $course->review_count += 1; - - $course->update(); - } - - protected function updateCourseRating($courseId) + protected function updateCourseRating(CourseModel $course) { $service = new CourseStatService(); - $service->updateRating($courseId); + $service->updateRating($course->id); } protected function handleReviews($pager) diff --git a/app/Http/Admin/Services/Tag.php b/app/Http/Admin/Services/Tag.php index b0a75ef5..584a8086 100644 --- a/app/Http/Admin/Services/Tag.php +++ b/app/Http/Admin/Services/Tag.php @@ -19,7 +19,7 @@ class Tag extends Service $params['deleted'] = $params['deleted'] ?? 0; - $sort = 'priority'; + $sort = $pagerQuery->getSort(); $page = $pagerQuery->getPage(); $limit = $pagerQuery->getLimit(); @@ -42,7 +42,7 @@ class Tag extends Service $tag = new TagModel(); $tag->name = $validator->checkName($post['name']); - $tag->published = $validator->checkPublishStatus($post['published']); + $tag->priority = $validator->checkPriority($post['priority']); $tag->create(); @@ -68,6 +68,10 @@ class Tag extends Service } } + if (isset($post['icon'])) { + $data['icon'] = $validator->checkIcon($post['icon']); + } + if (isset($post['priority'])) { $data['priority'] = $validator->checkPriority($post['priority']); } diff --git a/app/Http/Admin/Services/User.php b/app/Http/Admin/Services/User.php index 7ebf83c9..ac95ca4e 100644 --- a/app/Http/Admin/Services/User.php +++ b/app/Http/Admin/Services/User.php @@ -10,6 +10,7 @@ use App\Models\Account as AccountModel; use App\Models\ImUser as ImUserModel; use App\Models\User as UserModel; use App\Repos\Account as AccountRepo; +use App\Repos\Online as OnlineRepo; use App\Repos\Role as RoleRepo; use App\Repos\User as UserRepo; use App\Validators\Account as AccountValidator; @@ -30,6 +31,25 @@ class User extends Service return $roleRepo->findAll(['deleted' => 0]); } + public function getOnlineLogs($id) + { + $user = $this->findOrFail($id); + + $pageQuery = new PaginateQuery(); + + $params = $pageQuery->getParams(); + + $params['user_id'] = $user->id; + + $sort = $pageQuery->getSort(); + $page = $pageQuery->getPage(); + $limit = $pageQuery->getLimit(); + + $onlineRepo = new OnlineRepo(); + + return $onlineRepo->paginate($params, $sort, $page, $limit); + } + public function getUsers() { $pageQuery = new PaginateQuery(); diff --git a/app/Http/Admin/Views/answer/add.volt b/app/Http/Admin/Views/answer/add.volt new file mode 100644 index 00000000..4b6a5dcf --- /dev/null +++ b/app/Http/Admin/Views/answer/add.volt @@ -0,0 +1,41 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+
+ 回答问题 +
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + +
+
+ +{% endblock %} + +{% block link_css %} + + {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} + {{ js_include('admin/js/vditor.js') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/answer/edit.volt b/app/Http/Admin/Views/answer/edit.volt new file mode 100644 index 00000000..d11df81c --- /dev/null +++ b/app/Http/Admin/Views/answer/edit.volt @@ -0,0 +1,40 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+
+ 编辑答案 +
+
+
+ +
+
+
+
+
+ +
+
+
+ + + +
+
+ +{% endblock %} + +{% block link_css %} + + {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} + {{ js_include('admin/js/vditor.js') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/answer/list.volt b/app/Http/Admin/Views/answer/list.volt new file mode 100644 index 00000000..697a5f19 --- /dev/null +++ b/app/Http/Admin/Views/answer/list.volt @@ -0,0 +1,64 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {{ partial('macros/answer') }} + + + + + + + + + + + + + + + + + + + + {% for item in pager.items %} + {% set question_url = url({'for':'home.question.show','id':item.question.id}) %} + {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set review_url = url({'for':'admin.answer.review','id':item.id}) %} + {% set edit_url = url({'for':'admin.answer.edit','id':item.id}) %} + {% set delete_url = url({'for':'admin.answer.delete','id':item.id}) %} + {% set restore_url = url({'for':'admin.answer.restore','id':item.id}) %} + + + + + + + + {% endfor %} + +
信息评论点赞状态操作
+

问题:{{ item.question.title }}

+

回答:{{ item.summary }}

+

作者:{{ item.owner.name }} 创建:{{ date('Y-m-d',item.create_time) }}

+
{{ item.comment_count }}{{ item.like_count }}{{ publish_status(item.published) }} +
+ + +
+
+ + {{ partial('partials/pager') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/article/show.volt b/app/Http/Admin/Views/answer/review.volt similarity index 70% rename from app/Http/Admin/Views/article/show.volt rename to app/Http/Admin/Views/answer/review.volt index 106706e9..c94e5e6e 100644 --- a/app/Http/Admin/Views/article/show.volt +++ b/app/Http/Admin/Views/answer/review.volt @@ -2,24 +2,20 @@ {% block content %} - {% set list_url = url({'for':'admin.article.pending_list'}) %} +
+ 审核内容 +
-
-
- - 审核列表 - {{ article.title }} - -
+
+
{{ question.title }}
+
{{ answer.content }}
-
-
- -
-
{{ article.content|parse_markdown }}
-
-
+
+ 审核意见 +
+ +
@@ -33,7 +29,7 @@
diff --git a/app/Http/Admin/Views/comment/search.volt b/app/Http/Admin/Views/answer/search.volt similarity index 63% rename from app/Http/Admin/Views/comment/search.volt rename to app/Http/Admin/Views/answer/search.volt index b0c4a8b7..cd0e7b6f 100644 --- a/app/Http/Admin/Views/comment/search.volt +++ b/app/Http/Admin/Views/answer/search.volt @@ -2,20 +2,20 @@ {% block content %} - +
- 搜索评价 + 搜索回答
- +
- +
- +
- +
@@ -25,10 +25,18 @@
- +
- - + {% for value,title in publish_types %} + + {% endfor %} +
+
+
+ +
+ +
diff --git a/app/Http/Admin/Views/article/add.volt b/app/Http/Admin/Views/article/add.volt index 81e6d1e4..369bd8c8 100644 --- a/app/Http/Admin/Views/article/add.volt +++ b/app/Http/Admin/Views/article/add.volt @@ -6,17 +6,6 @@
添加文章
-
- -
- -
-
diff --git a/app/Http/Admin/Views/article/edit.volt b/app/Http/Admin/Views/article/edit.volt index 9cfa6e0b..179f055e 100644 --- a/app/Http/Admin/Views/article/edit.volt +++ b/app/Http/Admin/Views/article/edit.volt @@ -33,7 +33,6 @@ {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} {{ js_include('lib/xm-select.js') }} - {{ js_include('admin/js/cover.upload.js') }} {{ js_include('admin/js/vditor.js') }} {% endblock %} diff --git a/app/Http/Admin/Views/article/edit_basic.volt b/app/Http/Admin/Views/article/edit_basic.volt index 8857b583..ebf4ea31 100644 --- a/app/Http/Admin/Views/article/edit_basic.volt +++ b/app/Http/Admin/Views/article/edit_basic.volt @@ -1,33 +1,12 @@ {% set source_url_display = article.source_type == 1 ? 'display:none' : 'display:block' %} -
- -
- - -
-
- -
-
-
- -
- -
-
@@ -51,18 +30,10 @@
- +
- {% for value,title in publish_types %} - - {% endfor %} -
-
-
- -
- - + +
diff --git a/app/Http/Admin/Views/article/edit_desc.volt b/app/Http/Admin/Views/article/edit_desc.volt index 70123beb..185096f2 100644 --- a/app/Http/Admin/Views/article/edit_desc.volt +++ b/app/Http/Admin/Views/article/edit_desc.volt @@ -1,20 +1,12 @@
- -
+
- -
- -
-
-
- -
+
diff --git a/app/Http/Admin/Views/article/list.volt b/app/Http/Admin/Views/article/list.volt index 95eab222..b13ccdea 100644 --- a/app/Http/Admin/Views/article/list.volt +++ b/app/Http/Admin/Views/article/list.volt @@ -38,13 +38,13 @@ 文章 - 状态 - 浏览 评论 + 浏览 点赞 收藏 + 状态 推荐 - 评论 + 关闭 操作 @@ -52,6 +52,7 @@ {% for item in pager.items %} {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} {% set preview_url = url({'for':'home.article.show','id':item.id}) %} + {% set review_url = url({'for':'admin.article.review','id':item.id}) %} {% set edit_url = url({'for':'admin.article.edit','id':item.id}) %} {% set update_url = url({'for':'admin.article.update','id':item.id}) %} {% set delete_url = url({'for':'admin.article.delete','id':item.id}) %} @@ -74,18 +75,22 @@ 创建:{{ date('Y-m-d',item.create_time) }}

- {{ publish_status(item.published) }} {{ item.view_count }} {{ item.comment_count }} {{ item.like_count }} {{ item.favorite_count }} + {{ publish_status(item.published) }} - +
    -
  • 预览文章
  • + {% if item.published == 1 %} +
  • 审核文章
  • + {% elseif item.published == 2 %} +
  • 预览文章
  • + {% endif %}
  • 编辑文章
  • {% if item.deleted == 0 %}
  • 删除文章
  • @@ -142,16 +147,16 @@ }); }); - form.on('switch(comment)', function (data) { + form.on('switch(closed)', function (data) { var checked = $(this).is(':checked'); - var allowComment = checked ? 1 : 0; + var closed = checked ? 1 : 0; var url = $(this).data('url'); - var tips = allowComment === 1 ? '确定要开启评论?' : '确定要关闭评论?'; + var tips = closed === 1 ? '确定要关闭评论?' : '确定要开启评论?'; layer.confirm(tips, function () { $.ajax({ type: 'POST', url: url, - data: {allow_comment: allowComment}, + data: {closed: closed}, success: function (res) { layer.msg(res.msg, {icon: 1}); }, diff --git a/app/Http/Admin/Views/article/review.volt b/app/Http/Admin/Views/article/review.volt new file mode 100644 index 00000000..f24ca97d --- /dev/null +++ b/app/Http/Admin/Views/article/review.volt @@ -0,0 +1,85 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
    + 审核内容 +
    + +
    +
    {{ article.title }}
    +
    {{ article.content }}
    + {% if article.tags %} +
    + {% for item in article.tags %} + {{ item['name'] }} + {% endfor %} +
    + {% endif %} +
    + +
    + 审核意见 +
    + + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + + +{% endblock %} + +{% block link_css %} + + {{ css_link('home/css/markdown.css') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/article/search.volt b/app/Http/Admin/Views/article/search.volt index 69b89d9f..a21a904d 100644 --- a/app/Http/Admin/Views/article/search.volt +++ b/app/Http/Admin/Views/article/search.volt @@ -24,17 +24,6 @@
-
- -
- -
-
@@ -57,6 +46,20 @@ {% endfor %}
+
+ +
+ + +
+
+
+ +
+ + +
+
diff --git a/app/Http/Admin/Views/audit/list.volt b/app/Http/Admin/Views/audit/list.volt index b1b739d7..b40d81be 100644 --- a/app/Http/Admin/Views/audit/list.volt +++ b/app/Http/Admin/Views/audit/list.volt @@ -40,20 +40,15 @@ {% for item in pager.items %} - {% set list_by_id_url = url({'for':'admin.audit.list'},{'user_id':item.user_id}) %} - {% set list_by_ip_url = url({'for':'admin.audit.list'},{'user_ip':item.user_ip}) %} - {% set list_by_route_url = url({'for':'admin.audit.list'},{'req_route':item.req_route}) %} - {% set list_by_path_url = url({'for':'admin.audit.list'},{'req_path':item.req_path}) %} {% set show_url = url({'for':'admin.audit.show','id':item.id}) %} {{ item.user_id }} - {{ item.user_name }} + {{ item.user_name }} - {{ item.user_ip }} - 位置 + {{ item.user_ip }} - {{ item.req_route }} - {{ item.req_path }} + {{ item.req_route }} + {{ item.req_path }} {{ date('Y-m-d H:i:s',item.create_time) }} diff --git a/app/Http/Admin/Views/comment/edit.volt b/app/Http/Admin/Views/comment/edit.volt deleted file mode 100644 index 292772af..00000000 --- a/app/Http/Admin/Views/comment/edit.volt +++ /dev/null @@ -1,98 +0,0 @@ -{% extends 'templates/main.volt' %} - -{% block content %} - -
-
- 编辑评价 -
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
{{ review.content }}
-
-
-
- -
- - -
-
-
- -
- - -
-
-
- -{% endblock %} - -{% block inline_js %} - - - -{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/comment/list.volt b/app/Http/Admin/Views/comment/list.volt index 64071a58..539ebbd9 100644 --- a/app/Http/Admin/Views/comment/list.volt +++ b/app/Http/Admin/Views/comment/list.volt @@ -4,9 +4,9 @@ {{ partial('macros/common') }} - +
- + @@ -15,31 +15,23 @@ + - - + {% for item in pager.items %} + {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} {% set update_url = url({'for':'admin.comment.update','id':item.id}) %} {% set delete_url = url({'for':'admin.comment.delete','id':item.id}) %} {% set restore_url = url({'for':'admin.comment.restore','id':item.id}) %} - - - - + + + + - - - - diff --git a/app/Http/Admin/Views/tag/add.volt b/app/Http/Admin/Views/tag/add.volt index e3cb3cbf..b2d30288 100644 --- a/app/Http/Admin/Views/tag/add.volt +++ b/app/Http/Admin/Views/tag/add.volt @@ -12,12 +12,6 @@ -
- -
- -
-
diff --git a/app/Http/Admin/Views/tag/edit.volt b/app/Http/Admin/Views/tag/edit.volt index 1128c729..e39e3058 100644 --- a/app/Http/Admin/Views/tag/edit.volt +++ b/app/Http/Admin/Views/tag/edit.volt @@ -7,15 +7,19 @@ 编辑标签
- -
- + +
+ + +
+
+
- +
- +
@@ -34,4 +38,10 @@
+{% endblock %} + +{% block include_js %} + + {{ js_include('admin/js/icon.upload.js') }} + {% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/tag/list.volt b/app/Http/Admin/Views/tag/list.volt index 99785aaa..2ca61555 100644 --- a/app/Http/Admin/Views/tag/list.volt +++ b/app/Http/Admin/Views/tag/list.volt @@ -29,15 +29,17 @@
+ + + - @@ -49,10 +51,11 @@ {% set delete_url = url({'for':'admin.tag.delete','id':item.id}) %} + - - - + + + + @@ -42,7 +43,8 @@ - + + @@ -51,6 +53,7 @@ {% for item in pager.items %} {% set preview_url = url({'for':'home.user.show','id':item.id}) %} + {% set online_url = url({'for':'admin.user.online','id':item.id}) %} {% set edit_url = url({'for':'admin.user.edit','id':item.id}) %} - + + @@ -90,4 +95,29 @@ {{ partial('partials/pager') }} +{% endblock %} + +{% block inline_js %} + + + {% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/user/online.volt b/app/Http/Admin/Views/user/online.volt new file mode 100644 index 00000000..86f0af78 --- /dev/null +++ b/app/Http/Admin/Views/user/online.volt @@ -0,0 +1,42 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {{ partial('macros/common') }} + +
评论点赞 用户终端发布时间 操作
-

内容:{{ substr(item.content,0,30) }}

-

时间:{{ date('Y-m-d H:i',item.create_time) }},点赞:{{ item.like_count }}

-
-

昵称:{{ item.owner.name }}

-

编号:{{ item.owner.id }}

-
-

类型:{{ client_type(item.client_type) }}

-

地址:查看

-
{{ item.content }}{{ item.like_count }}{{ item.owner.name }}{{ date('Y-m-d',item.create_time) }} {% if item.deleted == 0 %} 删除 @@ -54,10 +46,4 @@ {{ partial('partials/pager') }} -{% endblock %} - -{% block include_js %} - - {{ js_include('admin/js/ip2region.js') }} - {% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/macros/answer.volt b/app/Http/Admin/Views/macros/answer.volt new file mode 100644 index 00000000..05de2d9f --- /dev/null +++ b/app/Http/Admin/Views/macros/answer.volt @@ -0,0 +1,11 @@ +{%- macro publish_status(type) %} + {% if type == 1 %} + 审核中 + {% elseif type == 2 %} + 已发布 + {% elseif type == 3 %} + 未通过 + {% else %} + 未知 + {% endif %} +{%- endmacro %} \ No newline at end of file diff --git a/app/Http/Admin/Views/macros/article.volt b/app/Http/Admin/Views/macros/article.volt index 9d31a831..c904f6ba 100644 --- a/app/Http/Admin/Views/macros/article.volt +++ b/app/Http/Admin/Views/macros/article.volt @@ -12,11 +12,11 @@ {%- macro source_info(type,url) %} {% if type == 1 %} - 原创 + 原创 {% elseif type == 2 %} - 转载 + 转载 {% elseif type == 3 %} - 翻译 + 翻译 {% else %} N/A {% endif %} diff --git a/app/Http/Admin/Views/macros/question.volt b/app/Http/Admin/Views/macros/question.volt new file mode 100644 index 00000000..6593e6ea --- /dev/null +++ b/app/Http/Admin/Views/macros/question.volt @@ -0,0 +1,18 @@ +{%- macro publish_status(type) %} + {% if type == 1 %} + 审核中 + {% elseif type == 2 %} + 已发布 + {% elseif type == 3 %} + 未通过 + {% else %} + 未知 + {% endif %} +{%- endmacro %} + +{%- macro tags_info(items) %} + {% for item in items %} + {% set comma = loop.last ? '' : ',' %} + {{ item.name ~ comma }} + {% endfor %} +{%- endmacro %} \ No newline at end of file diff --git a/app/Http/Admin/Views/moderation/answers.volt b/app/Http/Admin/Views/moderation/answers.volt new file mode 100644 index 00000000..ffeab7d9 --- /dev/null +++ b/app/Http/Admin/Views/moderation/answers.volt @@ -0,0 +1,55 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {{ partial('macros/answer') }} + +
+
+ + 回答审核 + +
+
+ + + + + + + + + + + + + + + + + + {% for item in pager.items %} + {% set question_url = url({'for':'home.question.show','id':item.question.id}) %} + {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set review_url = url({'for':'admin.answer.review','id':item.id}) %} + + + + + + + {% endfor %} + +
回答作者时间操作
+

问题:{{ item.question.title }}

+

回答:{{ substr(item.summary,0,32) }}

+
+

昵称:{{ item.owner.name }}

+

编号:{{ item.owner.id }}

+
{{ date('Y-m-d H:i',item.create_time) }} + 详情 +
+ + {{ partial('partials/pager') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/moderation/articles.volt b/app/Http/Admin/Views/moderation/articles.volt index 87a3229b..f9e1a631 100644 --- a/app/Http/Admin/Views/moderation/articles.volt +++ b/app/Http/Admin/Views/moderation/articles.volt @@ -14,8 +14,6 @@ - - @@ -25,8 +23,6 @@ - - @@ -34,14 +30,11 @@ {% for item in pager.items %} {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} - {% set show_url = url({'for':'admin.article.show','id':item.id}) %} + {% set review_url = url({'for':'admin.article.review','id':item.id}) %} - - - + {% endfor %} diff --git a/app/Http/Admin/Views/moderation/questions.volt b/app/Http/Admin/Views/moderation/questions.volt new file mode 100644 index 00000000..4d0a0a6e --- /dev/null +++ b/app/Http/Admin/Views/moderation/questions.volt @@ -0,0 +1,58 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {{ partial('macros/question') }} + +
+
+ + 问题审核 + +
+
+ +
文章 作者来源评论 时间 操作
-

标题:{{ item.title }}({{ item.id }})

+

标题:{{ item.title }} {{ source_info(item.source_type,item.source_url) }}

- {% if item.category.id is defined %} - 分类:{{ item.category.name }} - {% endif %} {% if item.tags %} 标签:{{ tags_info(item.tags) }} {% endif %} @@ -51,23 +44,9 @@

昵称:{{ item.owner.name }}

编号:{{ item.owner.id }}

{{ source_info(item.source_type,item.source_url) }} - {% if item.allow_comment == 1 %} - 开启 - {% else %} - 关闭 - {% endif %} - - {% if item.update_time > 0 %} - {{ date('Y-m-d H:i:s',item.update_time) }} - {% else %} - {{ date('Y-m-d H:i:s',item.create_time) }} - {% endif %} - {{ date('Y-m-d H:i',item.create_time) }} - 详情 + 详情
+ + + + + + + + + + + + + + + + {% for item in pager.items %} + {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set review_url = url({'for':'admin.question.review','id':item.id}) %} + + + + + + + {% endfor %} + +
问题作者时间操作
+

标题:{{ item.title }}

+

+ {% if item.tags %} + 标签:{{ tags_info(item.tags) }} + {% endif %} +

+
+

昵称:{{ item.owner.name }}

+

编号:{{ item.owner.id }}

+
{{ date('Y-m-d H:i',item.create_time) }} + 详情 +
+ + {{ partial('partials/pager') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/add.volt b/app/Http/Admin/Views/question/add.volt new file mode 100644 index 00000000..e6cd2a19 --- /dev/null +++ b/app/Http/Admin/Views/question/add.volt @@ -0,0 +1,24 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+
+ 添加问题 +
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +{% endblock %} diff --git a/app/Http/Admin/Views/question/edit.volt b/app/Http/Admin/Views/question/edit.volt new file mode 100644 index 00000000..c99a7fc0 --- /dev/null +++ b/app/Http/Admin/Views/question/edit.volt @@ -0,0 +1,61 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+ 编辑问题 +
+ +
+
    +
  • 基本信息
  • +
  • 内容详情
  • +
+
+
+ {{ partial('question/edit_basic') }} +
+
+ {{ partial('question/edit_desc') }} +
+
+
+ +{% endblock %} + +{% block link_css %} + + {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} + {{ js_include('lib/xm-select.js') }} + {{ js_include('admin/js/vditor.js') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/edit_basic.volt b/app/Http/Admin/Views/question/edit_basic.volt new file mode 100644 index 00000000..e94340d9 --- /dev/null +++ b/app/Http/Admin/Views/question/edit_basic.volt @@ -0,0 +1,35 @@ +
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
\ No newline at end of file diff --git a/app/Http/Admin/Views/question/edit_desc.volt b/app/Http/Admin/Views/question/edit_desc.volt new file mode 100644 index 00000000..b355bd47 --- /dev/null +++ b/app/Http/Admin/Views/question/edit_desc.volt @@ -0,0 +1,14 @@ +
+
+
+
+ +
+
+
+
+ + +
+
+
\ No newline at end of file diff --git a/app/Http/Admin/Views/question/list.volt b/app/Http/Admin/Views/question/list.volt new file mode 100644 index 00000000..cfabc02a --- /dev/null +++ b/app/Http/Admin/Views/question/list.volt @@ -0,0 +1,162 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {{ partial('macros/question') }} + + {% set add_url = url({'for':'admin.question.add'}) %} + {% set search_url = url({'for':'admin.question.search'}) %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for item in pager.items %} + {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set preview_url = url({'for':'home.question.show','id':item.id}) %} + {% set review_url = url({'for':'admin.question.review','id':item.id}) %} + {% set edit_url = url({'for':'admin.question.edit','id':item.id}) %} + {% set update_url = url({'for':'admin.question.update','id':item.id}) %} + {% set delete_url = url({'for':'admin.question.delete','id':item.id}) %} + {% set restore_url = url({'for':'admin.question.restore','id':item.id}) %} + {% set answer_add_url = url({'for':'admin.answer.add'},{'question_id':item.id}) %} + {% set answer_list_url = url({'for':'admin.answer.list'},{'question_id':item.id}) %} + + + + + + + + + + + {% endfor %} + +
问题回答浏览点赞收藏状态关闭操作
+

标题:{{ item.title }}({{ item.id }})

+

+ {% if item.category.id is defined %} + 分类:{{ item.category.name }} + {% endif %} + {% if item.tags %} + 标签:{{ tags_info(item.tags) }} + {% endif %} +

+

+ 作者:{{ item.owner.name }} + 创建:{{ date('Y-m-d',item.create_time) }} +

+
{{ item.answer_count }}{{ item.view_count }}{{ item.like_count }}{{ item.favorite_count }}{{ publish_status(item.published) }} +
+ + +
+
+ + {{ partial('partials/pager') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/review.volt b/app/Http/Admin/Views/question/review.volt new file mode 100644 index 00000000..e1491b8a --- /dev/null +++ b/app/Http/Admin/Views/question/review.volt @@ -0,0 +1,85 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+ 审核内容 +
+ +
+
{{ question.title }}
+
{{ question.content }}
+ {% if question.tags %} +
+ {% for item in question.tags %} + {{ item['name'] }} + {% endfor %} +
+ {% endif %} +
+ +
+ 审核意见 +
+ +
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ +{% endblock %} + +{% block link_css %} + + {{ css_link('home/css/markdown.css') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/search.volt b/app/Http/Admin/Views/question/search.volt new file mode 100644 index 00000000..75e6aca3 --- /dev/null +++ b/app/Http/Admin/Views/question/search.volt @@ -0,0 +1,93 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+
+ 搜索问题 +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+ {% for value,title in publish_types %} + + {% endfor %} +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +{% endblock %} + +{% block include_js %} + + {{ js_include('lib/xm-select.js') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/setting/storage.volt b/app/Http/Admin/Views/setting/storage.volt index 93d58fee..e0f7b07d 100644 --- a/app/Http/Admin/Views/setting/storage.volt +++ b/app/Http/Admin/Views/setting/storage.volt @@ -121,10 +121,6 @@
群组头像 public/static/admin/img/default/group_cover.png
文章封面public/static/admin/img/default/article_cover.png
课程封面 public/static/admin/img/default/course_cover.png
编号图标 名称关注数 创建时间 更新时间排序 发布 操作
{{ item.id }}{{ item.name }} {{ item.name }}{{ date('Y-m-d H:i',item.create_time) }}{{ date('Y-m-d H:i',item.update_time) }}{{ item.follow_count }}{{ date('Y-m-d',item.create_time) }}{{ date('Y-m-d',item.update_time) }} diff --git a/app/Http/Admin/Views/user/list.volt b/app/Http/Admin/Views/user/list.volt index a9ff78cd..76ea12ba 100644 --- a/app/Http/Admin/Views/user/list.volt +++ b/app/Http/Admin/Views/user/list.volt @@ -33,6 +33,7 @@
用户角色 课程 文章收藏提问回答 活跃时间 注册时间 操作
@@ -71,15 +74,17 @@ {{ item.course_count }} {{ item.article_count }}{{ item.favorite_count }}{{ item.question_count }}{{ item.answer_count }} {{ date('Y-m-d',item.active_time) }} {{ date('Y-m-d',item.create_time) }}
+ + + + + + + + + + + + + + + + {% for item in pager.items %} + + + + + + + {% endfor %} + +
终端类型终端地址活跃时间创建时间
{{ client_type(item.client_type) }}{{ item.client_ip }}{{ date('Y-m-d H:i',item.active_time) }}{{ date('Y-m-d H:i',item.create_time) }}
+ + {{ partial('partials/pager') }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('admin/js/ip2region.js') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Home/Controllers/AnswerController.php b/app/Http/Home/Controllers/AnswerController.php new file mode 100644 index 00000000..f51c77d6 --- /dev/null +++ b/app/Http/Home/Controllers/AnswerController.php @@ -0,0 +1,178 @@ +view->setRenderLevel(View::LEVEL_ACTION_VIEW); + } + + /** + * @Get("/add", name="home.answer.add") + */ + public function addAction() + { + $id = $this->request->getQuery('question_id', 'int', 0); + + $service = new QuestionService(); + + $question = $service->getQuestion($id); + + $this->seo->prependTitle('回答问题'); + + $this->view->setVar('question', $question); + } + + /** + * @Get("/{id:[0-9]+}/edit", name="home.answer.edit") + */ + public function editAction($id) + { + $service = new AnswerService(); + + $answer = $service->getAnswer($id); + + $service = new QuestionService(); + + $question = $service->getQuestion($answer->question_id); + + $this->seo->prependTitle('编辑回答'); + + $this->view->setVar('question', $question); + $this->view->setVar('answer', $answer); + } + + /** + * @Get("/{id:[0-9]+}", name="home.answer.show") + */ + public function showAction($id) + { + $service = new AnswerInfoService(); + + $answer = $service->handle($id); + + $this->view->setVar('answer', $answer); + } + + /** + * @Post("/create", name="home.answer.create") + */ + public function createAction() + { + $service = new AnswerCreateService(); + + $answer = $service->handle(); + + $location = $this->url->get([ + 'for' => 'home.question.show', + 'id' => $answer->question_id, + ]); + + $content = [ + 'location' => $location, + 'msg' => '创建回答成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/update", name="home.answer.update") + */ + public function updateAction($id) + { + $service = new AnswerUpdateService(); + + $answer = $service->handle($id); + + $location = $this->url->get([ + 'for' => 'home.question.show', + 'id' => $answer->question_id, + ]); + + $content = [ + 'location' => $location, + 'msg' => '更新回答成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/delete", name="home.answer.delete") + */ + public function deleteAction($id) + { + $service = new AnswerDeleteService(); + + $answer = $service->handle($id); + + $location = $this->url->get([ + 'for' => 'home.question.show', + 'id' => $answer->question_id, + ]); + + $content = [ + 'location' => $location, + 'msg' => '删除回答成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/like", name="home.answer.like") + */ + public function likeAction($id) + { + $service = new AnswerLikeService(); + + $data = $service->handle($id); + + $msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功'; + + return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); + } + + /** + * @Post("/{id:[0-9]+}/accept", name="home.answer.accept") + */ + public function acceptAction($id) + { + $service = new AnswerAcceptService(); + + $data = $service->handle($id); + + $msg = $data['action'] == 'do' ? '采纳成功' : '取消采纳成功'; + + return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); + } + + /** + * @Route("/{id:[0-9]+}/report", name="home.answer.report") + */ + public function reportAction($id) + { + + } + +} diff --git a/app/Http/Home/Controllers/ArticleController.php b/app/Http/Home/Controllers/ArticleController.php index ba081e4b..36adbfd8 100644 --- a/app/Http/Home/Controllers/ArticleController.php +++ b/app/Http/Home/Controllers/ArticleController.php @@ -4,13 +4,15 @@ namespace App\Http\Home\Controllers; use App\Http\Home\Services\Article as ArticleService; use App\Http\Home\Services\ArticleQuery as ArticleQueryService; +use App\Services\Logic\Article\ArticleCreate as ArticleCreateService; +use App\Services\Logic\Article\ArticleDelete as ArticleDeleteService; use App\Services\Logic\Article\ArticleFavorite as ArticleFavoriteService; use App\Services\Logic\Article\ArticleInfo as ArticleInfoService; use App\Services\Logic\Article\ArticleLike as ArticleLikeService; use App\Services\Logic\Article\ArticleList as ArticleListService; -use App\Services\Logic\Article\CommentList as ArticleCommentListService; +use App\Services\Logic\Article\ArticleUpdate as ArticleUpdateService; use App\Services\Logic\Article\HotAuthorList as HotAuthorListService; -use App\Services\Logic\Article\RelatedList as ArticleRelatedListService; +use App\Services\Logic\Article\RelatedArticleList as RelatedArticleListService; use Phalcon\Mvc\View; /** @@ -78,7 +80,7 @@ class ArticleController extends Controller $article = $service->getArticleModel(); $xmTags = $service->getXmTags(0); - $this->seo->prependTitle('撰写文章'); + $this->seo->prependTitle('写文章'); $this->view->pick('article/edit'); $this->view->setVar('source_types', $sourceTypes); @@ -123,6 +125,7 @@ class ArticleController extends Controller } $this->seo->prependTitle($article['title']); + $this->seo->setDescription($article['summary']); $this->view->setVar('article', $article); } @@ -132,7 +135,7 @@ class ArticleController extends Controller */ public function relatedAction($id) { - $service = new ArticleRelatedListService(); + $service = new RelatedArticleListService(); $articles = $service->handle($id); @@ -140,27 +143,14 @@ class ArticleController extends Controller $this->view->setVar('articles', $articles); } - /** - * @Get("/{id:[0-9]+}/comments", name="home.article.comments") - */ - public function commentsAction($id) - { - $service = new ArticleCommentListService(); - - $comments = $service->handle($id); - - $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); - $this->view->setVar('comments', $comments); - } - /** * @Post("/create", name="home.article.create") */ public function createAction() { - $service = new ArticleService(); + $service = new ArticleCreateService(); - $service->createArticle(); + $service->handle(); $location = $this->url->get(['for' => 'home.uc.articles']); @@ -177,9 +167,9 @@ class ArticleController extends Controller */ public function updateAction($id) { - $service = new ArticleService(); + $service = new ArticleUpdateService(); - $service->updateArticle($id); + $service->handle($id); $location = $this->url->get(['for' => 'home.uc.articles']); @@ -196,12 +186,14 @@ class ArticleController extends Controller */ public function deleteAction($id) { - $service = new ArticleService(); + $service = new ArticleDeleteService(); - $service->deleteArticle($id); + $service->handle($id); + + $location = $this->url->get(['for' => 'home.uc.articles']); $content = [ - 'location' => $this->request->getHTTPReferer(), + 'location' => $location, 'msg' => '删除文章成功', ]; @@ -236,5 +228,12 @@ class ArticleController extends Controller return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); } + /** + * @Route("/{id:[0-9]+}/report", name="home.article.report") + */ + public function reportAction($id) + { + + } } diff --git a/app/Http/Home/Controllers/ChapterController.php b/app/Http/Home/Controllers/ChapterController.php index f9230ad6..dcb97734 100644 --- a/app/Http/Home/Controllers/ChapterController.php +++ b/app/Http/Home/Controllers/ChapterController.php @@ -6,7 +6,6 @@ use App\Models\ChapterLive as LiveModel; use App\Models\Course as CourseModel; use App\Services\Logic\Chapter\ChapterInfo as ChapterInfoService; use App\Services\Logic\Chapter\ChapterLike as ChapterLikeService; -use App\Services\Logic\Chapter\DanmuList as ChapterDanmuListService; use App\Services\Logic\Chapter\Learning as ChapterLearningService; use App\Services\Logic\Chapter\ResourceList as ChapterResourceListService; use App\Services\Logic\Course\BasicInfo as CourseInfoService; @@ -30,18 +29,6 @@ class ChapterController extends Controller $this->view->setVar('items', $items); } - /** - * @Get("/{id:[0-9]+}/danmus", name="home.chapter.danmus") - */ - public function danmusAction($id) - { - $service = new ChapterDanmuListService(); - - $items = $service->handle($id); - - return $this->jsonSuccess(['items' => $items]); - } - /** * @Get("/{id:[0-9]+}", name="home.chapter.show") */ diff --git a/app/Http/Home/Controllers/CommentController.php b/app/Http/Home/Controllers/CommentController.php index 4d11ee38..9448e362 100644 --- a/app/Http/Home/Controllers/CommentController.php +++ b/app/Http/Home/Controllers/CommentController.php @@ -63,26 +63,6 @@ class CommentController extends Controller $this->view->setVar('comment', $comment); } - /** - * @Get("/add", name="home.comment.add") - */ - public function addAction() - { - - } - - /** - * @Get("/{id:[0-9]+}/reply", name="home.comment.reply") - */ - public function replyAction($id) - { - $service = new CommentInfoService(); - - $comment = $service->handle($id); - - $this->view->setVar('comment', $comment); - } - /** * @Post("/create", name="home.comment.create") */ @@ -100,9 +80,9 @@ class CommentController extends Controller } /** - * @Post("/{id:[0-9]+}/reply", name="home.comment.create_reply") + * @Post("/{id:[0-9]+}/reply", name="home.comment.reply") */ - public function createReplyAction($id) + public function replyAction($id) { $service = new CommentReplyService(); @@ -141,4 +121,12 @@ class CommentController extends Controller return $this->jsonSuccess(['msg' => '删除评论成功']); } + /** + * @Route("/{id:[0-9]+}/report", name="home.comment.report") + */ + public function reportAction($id) + { + + } + } diff --git a/app/Http/Home/Controllers/QuestionController.php b/app/Http/Home/Controllers/QuestionController.php new file mode 100644 index 00000000..a691a5ea --- /dev/null +++ b/app/Http/Home/Controllers/QuestionController.php @@ -0,0 +1,261 @@ +handle(); + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->pick('question/hot_questions'); + $this->view->setVar('questions', $questions); + } + + /** + * @Get("/top/answerers", name="home.question.top_answerers") + */ + public function topAnswerersAction() + { + $service = new TopAnswererListService(); + + $answerers = $service->handle(); + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->pick('question/top_answerers'); + $this->view->setVar('answerers', $answerers); + } + + /** + * @Get("/list", name="home.question.list") + */ + public function listAction() + { + $service = new QuestionQueryService(); + + $sorts = $service->handleSorts(); + $params = $service->getParams(); + + $this->seo->prependTitle('问答'); + + $this->view->setVar('sorts', $sorts); + $this->view->setVar('params', $params); + } + + /** + * @Get("/pager", name="home.question.pager") + */ + public function pagerAction() + { + $service = new QuestionListService(); + + $pager = $service->handle(); + + $pager->target = 'question-list'; + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/add", name="home.question.add") + */ + public function addAction() + { + $service = new QuestionService(); + + $question = $service->getQuestionModel(); + + $xmTags = $service->getXmTags(0); + + $this->seo->prependTitle('提问题'); + + $this->view->pick('question/edit'); + $this->view->setVar('question', $question); + $this->view->setVar('xm_tags', $xmTags); + } + + /** + * @Get("/{id:[0-9]+}/edit", name="home.question.edit") + */ + public function editAction($id) + { + $service = new QuestionService(); + + $question = $service->getQuestion($id); + + $xmTags = $service->getXmTags($id); + + $this->seo->prependTitle('编辑问题'); + + $this->view->setVar('question', $question); + $this->view->setVar('xm_tags', $xmTags); + } + + /** + * @Get("/{id:[0-9]+}", name="home.question.show") + */ + public function showAction($id) + { + $service = new QuestionInfoService(); + + $question = $service->handle($id); + + $this->seo->prependTitle($question['title']); + $this->seo->setDescription($question['summary']); + + $this->view->setVar('question', $question); + } + + /** + * @Get("/{id:[0-9]+}/answers", name="home.question.answers") + */ + public function answersAction($id) + { + $service = new QuestionService(); + + $question = $service->getQuestion($id); + + $service = new AnswerListService(); + + $pager = $service->handle($id); + + $pager->target = 'answer-list'; + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->setVar('question', $question); + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/{id:[0-9]+}/related", name="home.question.related") + */ + public function relatedAction($id) + { + $service = new RelatedQuestionListService(); + + $questions = $service->handle($id); + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->setVar('questions', $questions); + } + + /** + * @Post("/create", name="home.question.create") + */ + public function createAction() + { + $service = new QuestionCreateService(); + + $service->handle(); + + $location = $this->url->get(['for' => 'home.uc.questions']); + + $content = [ + 'location' => $location, + 'msg' => '创建问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/update", name="home.question.update") + */ + public function updateAction($id) + { + $service = new QuestionUpdateService(); + + $service->handle($id); + + $location = $this->url->get(['for' => 'home.uc.questions']); + + $content = [ + 'location' => $location, + 'msg' => '更新问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/delete", name="home.question.delete") + */ + public function deleteAction($id) + { + $service = new QuestionDeleteService(); + + $service->handle($id); + + $location = $this->url->get(['for' => 'home.uc.questions']); + + $content = [ + 'location' => $location, + 'msg' => '删除问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/favorite", name="home.question.favorite") + */ + public function favoriteAction($id) + { + $service = new QuestionFavoriteService(); + + $data = $service->handle($id); + + $msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功'; + + return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); + } + + /** + * @Post("/{id:[0-9]+}/like", name="home.question.like") + */ + public function likeAction($id) + { + $service = new QuestionLikeService(); + + $data = $service->handle($id); + + $msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功'; + + return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); + } + + /** + * @Route("/{id:[0-9]+}/report", name="home.question.report") + */ + public function reportAction($id) + { + + } + +} diff --git a/app/Http/Home/Controllers/SearchController.php b/app/Http/Home/Controllers/SearchController.php index ba3507ef..2e2927af 100644 --- a/app/Http/Home/Controllers/SearchController.php +++ b/app/Http/Home/Controllers/SearchController.php @@ -5,6 +5,7 @@ namespace App\Http\Home\Controllers; use App\Services\Logic\Search\Article as ArticleSearchService; use App\Services\Logic\Search\Course as CourseSearchService; use App\Services\Logic\Search\Group as GroupSearchService; +use App\Services\Logic\Search\Question as QuestionSearchService; use App\Services\Logic\Search\User as UserSearchService; /** @@ -42,7 +43,7 @@ class SearchController extends Controller /** * @param string $type - * @return ArticleSearchService|CourseSearchService|GroupSearchService|UserSearchService + * @return ArticleSearchService|QuestionSearchService|CourseSearchService|GroupSearchService|UserSearchService */ protected function getSearchService($type) { @@ -50,6 +51,9 @@ class SearchController extends Controller case 'article': $service = new ArticleSearchService(); break; + case 'question': + $service = new QuestionSearchService(); + break; case 'group': $service = new GroupSearchService(); break; diff --git a/app/Http/Home/Controllers/TagController.php b/app/Http/Home/Controllers/TagController.php new file mode 100644 index 00000000..262b8614 --- /dev/null +++ b/app/Http/Home/Controllers/TagController.php @@ -0,0 +1,70 @@ +seo->prependTitle('标签'); + } + + /** + * @Get("/list/pager", name="home.tag.list_pager") + */ + public function listPagerAction() + { + $service = new TagListService(); + + $pager = $service->handle(); + + $pager->target = 'all-tag-list'; + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->pick('tag/list_pager'); + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/my/pager", name="home.tag.my_pager") + */ + public function myPagerAction() + { + $service = new FollowListService(); + + $pager = $service->handle(); + + $pager->target = 'my-tag-list'; + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->pick('tag/my_pager'); + $this->view->setVar('pager', $pager); + } + + /** + * @Post("/{id:[0-9]+}/follow", name="home.tag.follow") + */ + public function followAction($id) + { + $service = new TagFollowService(); + + $data = $service->handle($id); + + $msg = $data['action'] == 'do' ? '关注成功' : '取消关注成功'; + + return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); + } + +} diff --git a/app/Http/Home/Controllers/UserConsoleController.php b/app/Http/Home/Controllers/UserConsoleController.php index c38d6209..1f215146 100644 --- a/app/Http/Home/Controllers/UserConsoleController.php +++ b/app/Http/Home/Controllers/UserConsoleController.php @@ -5,6 +5,7 @@ namespace App\Http\Home\Controllers; use App\Repos\WeChatSubscribe as WeChatSubscribeRepo; use App\Services\Logic\Account\OAuthProvider as OAuthProviderService; use App\Services\Logic\User\Console\AccountInfo as AccountInfoService; +use App\Services\Logic\User\Console\AnswerList as AnswerListService; use App\Services\Logic\User\Console\ArticleList as ArticleListService; use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService; use App\Services\Logic\User\Console\ConnectList as ConnectListService; @@ -24,6 +25,7 @@ use App\Services\Logic\User\Console\PointHistory as PointHistoryService; use App\Services\Logic\User\Console\PointRedeemList as PointRedeemListService; use App\Services\Logic\User\Console\ProfileInfo as ProfileInfoService; use App\Services\Logic\User\Console\ProfileUpdate as ProfileUpdateService; +use App\Services\Logic\User\Console\QuestionList as QuestionListService; use App\Services\Logic\User\Console\RefundList as RefundListService; use App\Services\Logic\User\Console\ReviewList as ReviewListService; use Phalcon\Mvc\Dispatcher; @@ -154,6 +156,32 @@ class UserConsoleController extends Controller $this->view->setVar('pager', $pager); } + /** + * @Get("/questions", name="home.uc.questions") + */ + public function questionsAction() + { + $service = new QuestionListService(); + + $pager = $service->handle(); + + $this->view->pick('user/console/questions'); + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/answers", name="home.uc.answers") + */ + public function answersAction() + { + $service = new AnswerListService(); + + $pager = $service->handle(); + + $this->view->pick('user/console/answers'); + $this->view->setVar('pager', $pager); + } + /** * @Get("/favorites", name="home.uc.favorites") */ diff --git a/app/Http/Home/Controllers/WidgetController.php b/app/Http/Home/Controllers/WidgetController.php new file mode 100644 index 00000000..b129692d --- /dev/null +++ b/app/Http/Home/Controllers/WidgetController.php @@ -0,0 +1,58 @@ +handle(); + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->pick('widget/my_tags'); + $this->view->setVar('tags', $pager->items); + } + + /** + * @Get("/hot/questions", name="home.widget.hot_questions") + */ + public function hotQuestionsAction() + { + $service = new HotQuestionListService(); + + $questions = $service->handle(); + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->pick('widget/hot_questions'); + $this->view->setVar('questions', $questions); + } + + /** + * @Get("/top/answerers", name="home.widget.top_answerers") + */ + public function topAnswerersAction() + { + $service = new TopAnswererListService(); + + $answerers = $service->handle(); + + $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); + $this->view->pick('widget/top_answerers'); + $this->view->setVar('answerers', $answerers); + } + +} diff --git a/app/Http/Home/Services/Answer.php b/app/Http/Home/Services/Answer.php new file mode 100644 index 00000000..099a72da --- /dev/null +++ b/app/Http/Home/Services/Answer.php @@ -0,0 +1,24 @@ +checkAnswer($id); + } + +} diff --git a/app/Http/Home/Services/Article.php b/app/Http/Home/Services/Article.php index ac172aa2..420de389 100644 --- a/app/Http/Home/Services/Article.php +++ b/app/Http/Home/Services/Article.php @@ -2,143 +2,76 @@ namespace App\Http\Home\Services; -use App\Http\Admin\Services\Article as ArticleService; -use App\Library\Utils\Word as WordUtil; use App\Models\Article as ArticleModel; -use App\Traits\Client as ClientTrait; -use App\Validators\Article as ArticleValidator; +use App\Models\Category as CategoryModel; +use App\Repos\Category as CategoryRepo; +use App\Repos\Tag as TagRepo; +use App\Services\Logic\ArticleTrait; -class Article extends ArticleService +class Article extends Service { - use ClientTrait; + use ArticleTrait; - public function createArticle() + public function getArticleModel() { - $post = $this->request->getPost(); - - $user = $this->getLoginUser(); - $article = new ArticleModel(); - $data = $this->handlePostData($post); - - $data['client_type'] = $this->getClientType(); - $data['client_ip'] = $this->getClientIp(); - $data['owner_id'] = $user->id; - - $article->create($data); - - if (isset($post['xm_tag_ids'])) { - $this->saveTags($article, $post['xm_tag_ids']); - } - - $this->incrUserArticleCount($user); - - $this->eventsManager->fire('Article:afterCreate', $this, $article); + $article->afterFetch(); return $article; } - public function updateArticle($id) + public function getXmTags($id) { - $post = $this->request->getPost(); + $tagRepo = new TagRepo(); - $article = $this->findOrFail($id); + $allTags = $tagRepo->findAll(['published' => 1], 'priority'); - $data = $this->handlePostData($post); + if ($allTags->count() == 0) return []; - $data['client_type'] = $this->getClientType(); - $data['client_ip'] = $this->getClientIp(); + $articleTagIds = []; - if ($article->published == ArticleModel::PUBLISH_REJECTED) { - $data['published'] = ArticleModel::PUBLISH_PENDING; - } - - /** - * 当通过审核后,禁止修改部分文章属性 - */ - if ($article->published == ArticleModel::PUBLISH_APPROVED) { - unset( - $data['title'], - $data['content'], - $data['cover'], - $data['source_type'], - $data['source_url'], - $data['category_id'], - $post['xm_tag_ids'], - ); - } - - $article->update($data); - - if (isset($post['xm_tag_ids'])) { - $this->saveTags($article, $post['xm_tag_ids']); - } - - $this->eventsManager->fire('Article:afterUpdate', $this, $article); - - return $article; - } - - public function deleteArticle($id) - { - $article = $this->findOrFail($id); - - $user = $this->getLoginUser(); - - $validator = new ArticleValidator(); - - $validator->checkOwner($user->id, $article->owner_id); - - $article->deleted = 1; - - $article->update(); - - $this->decrUserArticleCount($user); - - $this->rebuildArticleIndex($article); - - $this->eventsManager->fire('Article:afterDelete', $this, $article); - - return $article; - } - - protected function handlePostData($post) - { - $data = []; - - $validator = new ArticleValidator(); - - $data['title'] = $validator->checkTitle($post['title']); - $data['content'] = $validator->checkContent($post['content']); - $data['word_count'] = WordUtil::getWordCount($data['content']); - - if (isset($post['category_id'])) { - $category = $validator->checkCategory($post['category_id']); - $data['category_id'] = $category->id; - } - - if (isset($post['cover'])) { - $data['cover'] = $validator->checkCover($post['cover']); - } - - if (isset($post['source_type'])) { - $data['source_type'] = $validator->checkSourceType($post['source_type']); - if ($post['source_type'] != ArticleModel::SOURCE_ORIGIN) { - $data['source_url'] = $validator->checkSourceUrl($post['source_url']); + if ($id > 0) { + $article = $this->checkArticle($id); + if (!empty($article->tags)) { + $articleTagIds = kg_array_column($article->tags, 'id'); } } - if (isset($post['allow_comment'])) { - $data['allow_comment'] = $validator->checkAllowCommentStatus($post['allow_comment']); + $list = []; + + foreach ($allTags as $tag) { + $selected = in_array($tag->id, $articleTagIds); + $list[] = [ + 'name' => $tag->name, + 'value' => $tag->id, + 'selected' => $selected, + ]; } - if (isset($post['private'])) { - $data['private'] = $validator->checkPrivateStatus($post['private']); - } + return $list; + } - return $data; + public function getCategories() + { + $categoryRepo = new CategoryRepo(); + + return $categoryRepo->findAll([ + 'type' => CategoryModel::TYPE_ARTICLE, + 'level' => 1, + 'published' => 1, + ]); + } + + public function getSourceTypes() + { + return ArticleModel::sourceTypes(); + } + + public function getArticle($id) + { + return $this->checkArticle($id); } } diff --git a/app/Http/Home/Services/Question.php b/app/Http/Home/Services/Question.php new file mode 100644 index 00000000..c8af59cc --- /dev/null +++ b/app/Http/Home/Services/Question.php @@ -0,0 +1,73 @@ +afterFetch(); + + return $question; + } + + public function getCategories() + { + $categoryRepo = new CategoryRepo(); + + return $categoryRepo->findAll([ + 'type' => CategoryModel::TYPE_ARTICLE, + 'level' => 1, + 'published' => 1, + ]); + } + + public function getXmTags($id) + { + $tagRepo = new TagRepo(); + + $allTags = $tagRepo->findAll(['published' => 1], 'priority'); + + if ($allTags->count() == 0) return []; + + $questionTagIds = []; + + if ($id > 0) { + $question = $this->checkQuestion($id); + if (!empty($question->tags)) { + $questionTagIds = kg_array_column($question->tags, 'id'); + } + } + + $list = []; + + foreach ($allTags as $tag) { + $selected = in_array($tag->id, $questionTagIds); + $list[] = [ + 'name' => $tag->name, + 'value' => $tag->id, + 'selected' => $selected, + ]; + } + + return $list; + } + + public function getQuestion($id) + { + return $this->checkQuestion($id); + } + +} diff --git a/app/Http/Home/Services/QuestionQuery.php b/app/Http/Home/Services/QuestionQuery.php new file mode 100644 index 00000000..19f854d3 --- /dev/null +++ b/app/Http/Home/Services/QuestionQuery.php @@ -0,0 +1,64 @@ +baseUrl = $this->url->get(['for' => 'home.question.list']); + } + + public function handleSorts() + { + $params = $this->getParams(); + + $result = []; + + $sorts = QuestionModel::sortTypes(); + + foreach ($sorts as $key => $value) { + $params['sort'] = $key; + $result[] = [ + 'id' => $key, + 'name' => $value, + 'url' => $this->baseUrl . $this->buildParams($params), + ]; + } + + return $result; + } + + public function getParams() + { + $query = $this->request->getQuery(); + + $params = []; + + $validator = new QuestionQueryValidator(); + + if (isset($query['tag_id'])) { + $validator->checkTag($query['tag_id']); + $params['tag_id'] = $query['tag_id']; + } + + if (isset($query['sort'])) { + $validator->checkSort($query['sort']); + $params['sort'] = $query['sort']; + } + + return $params; + } + + protected function buildParams($params) + { + return $params ? '?' . http_build_query($params) : ''; + } + +} diff --git a/app/Http/Home/Views/answer/add.volt b/app/Http/Home/Views/answer/add.volt new file mode 100644 index 00000000..085ae598 --- /dev/null +++ b/app/Http/Home/Views/answer/add.volt @@ -0,0 +1,76 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ + +
+
+
+
+
+ +
+ +{% endblock %} + +{% block link_css %} + + {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} + {{ js_include('home/js/answer.js') }} + {{ js_include('home/js/vditor.js') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Home/Views/answer/edit.volt b/app/Http/Home/Views/answer/edit.volt new file mode 100644 index 00000000..bbed9ada --- /dev/null +++ b/app/Http/Home/Views/answer/edit.volt @@ -0,0 +1,49 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +{% endblock %} + +{% block link_css %} + + {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} + {{ js_include('home/js/answer.js') }} + {{ js_include('home/js/vditor.js') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Home/Views/answer/tips.volt b/app/Http/Home/Views/answer/tips.volt new file mode 100644 index 00000000..cc8ba34e --- /dev/null +++ b/app/Http/Home/Views/answer/tips.volt @@ -0,0 +1,20 @@ +{% extends 'templates/layer.volt' %} + +{% block content %} + +
+

适合作为回答的

+
    +
  • 经过验证的有效解决办法
  • +
  • 自己的经验指引,对解决问题有帮助
  • +
  • 遵循 Markdown 语法排版,表达语义正确
  • +
+

不该作为回答的

+
    +
  • 询问内容细节或回复楼层
  • +
  • 与题目无关的内容
  • +
  • “赞” “顶” “同问” 等毫无意义的内容
  • +
+
+ +{% endblock %} \ No newline at end of file diff --git a/app/Http/Home/Views/article/comment.volt b/app/Http/Home/Views/article/comment.volt index 91731085..6d9b24ee 100644 --- a/app/Http/Home/Views/article/comment.volt +++ b/app/Http/Home/Views/article/comment.volt @@ -9,7 +9,7 @@ diff --git a/app/Http/Home/Views/article/edit.volt b/app/Http/Home/Views/article/edit.volt index 5cfe7fcd..f88d3a5f 100644 --- a/app/Http/Home/Views/article/edit.volt +++ b/app/Http/Home/Views/article/edit.volt @@ -2,7 +2,7 @@ {% block content %} - {% set title = article.id > 0 ? '编辑文章' : '撰写文章' %} + {% set title = article.id > 0 ? '编辑文章' : '写文章' %} {% set action_url = article.id > 0 ? url({'for':'home.article.update','id':article.id}) : url({'for':'home.article.create'}) %} {% set source_url_display = article.source_type == 1 ? 'display:none' : 'display:block' %} @@ -30,8 +30,7 @@
- - +
@@ -41,25 +40,6 @@
基本信息
-
- -
- - -
- -
-
- -
- -
-
@@ -87,10 +67,10 @@
- +
- - + +
diff --git a/app/Http/Home/Views/article/hot_authors.volt b/app/Http/Home/Views/article/hot_authors.volt index ec92de94..8cfd91fa 100644 --- a/app/Http/Home/Views/article/hot_authors.volt +++ b/app/Http/Home/Views/article/hot_authors.volt @@ -1,4 +1,4 @@ -{% if authors %} +{% if authors|length > 0 %}
推荐作者
diff --git a/app/Http/Home/Views/article/list.volt b/app/Http/Home/Views/article/list.volt index c3e60e54..e540ac25 100644 --- a/app/Http/Home/Views/article/list.volt +++ b/app/Http/Home/Views/article/list.volt @@ -2,10 +2,10 @@ {% block content %} - {% set category_val = request.get('category_id','int','all') %} {% set sort_val = request.get('sort','trim','latest') %} {% set pager_url = url({'for':'home.article.pager'}, params) %} - {% set hot_author_url = url({'for':'home.article.hot_authors'}) %} + {% set hot_authors_url = url({'for':'home.article.hot_authors'}) %} + {% set my_tags_url = url({'for':'home.widget.my_tags'},{'type':'article'}) %} diff --git a/app/Http/Home/Views/article/sticky.volt b/app/Http/Home/Views/article/sticky.volt index 50be63ab..5e3cef4f 100644 --- a/app/Http/Home/Views/article/sticky.volt +++ b/app/Http/Home/Views/article/sticky.volt @@ -20,7 +20,7 @@
- +
{{ article.favorite_count }}
diff --git a/app/Http/Home/Views/chapter/comment.volt b/app/Http/Home/Views/chapter/comment.volt index 35a0f786..9cff8d86 100644 --- a/app/Http/Home/Views/chapter/comment.volt +++ b/app/Http/Home/Views/chapter/comment.volt @@ -8,7 +8,7 @@ diff --git a/app/Http/Home/Views/comment/list.volt b/app/Http/Home/Views/comment/list.volt index 15f853bb..f4c86d44 100644 --- a/app/Http/Home/Views/comment/list.volt +++ b/app/Http/Home/Views/comment/list.volt @@ -1,12 +1,13 @@ {% if pager.total_pages > 0 %} {% for item in pager.items %} {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set report_url = url({'for':'home.comment.report','id':item.id}) %} {% set like_url = url({'for':'home.comment.like','id':item.id}) %} {% set delete_url = url({'for':'home.comment.delete','id':item.id}) %} - {% set reply_create_url = url({'for':'home.comment.create_reply','id':item.id}) %} + {% set reply_url = url({'for':'home.comment.reply','id':item.id}) %} {% set reply_list_url = url({'for':'home.comment.replies','id':item.id},{'limit':5}) %}
-