1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-19 14:46:14 +08:00

阶段提交

This commit is contained in:
koogua 2021-05-12 20:41:24 +08:00
parent 661034f3c1
commit 863505dcca
84 changed files with 1695 additions and 373 deletions

View File

@ -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)
### 更新

View File

@ -3,6 +3,7 @@
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Answer as AnswerService;
use App\Http\Admin\Services\Question as QuestionService;
/**
* @RoutePrefix("/admin/answer")
@ -15,7 +16,11 @@ class AnswerController extends Controller
*/
public function searchAction()
{
$answerService = new AnswerService();
$publishTypes = $answerService->getPublishTypes();
$this->view->setVar('publish_types', $publishTypes);
}
/**
@ -30,6 +35,78 @@ class AnswerController extends Controller
$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")
*/
@ -39,7 +116,16 @@ class AnswerController extends Controller
$answerService->updateAnswer($id);
$content = ['msg' => '更新回答成功'];
$location = $this->request->getPost('referer');
if (empty($location)) {
$location = $this->url->get(['for' => 'admin.answer.list']);
}
$content = [
'location' => $location,
'msg' => '更新回答成功',
];
return $this->jsonSuccess($content);
}
@ -78,4 +164,38 @@ class AnswerController extends Controller
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);
}
}

View File

@ -189,10 +189,10 @@ class ArticleController extends Controller
return $this->jsonSuccess($content);
}
$rejectOptions = $articleService->getRejectOptions();
$reasons = $articleService->getReasons();
$article = $articleService->getArticle($id);
$this->view->setVar('reject_options', $rejectOptions);
$this->view->setVar('reasons', $reasons);
$this->view->setVar('article', $article);
}

View File

@ -34,4 +34,16 @@ class ModerationController extends Controller
$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);
}
}

View File

@ -185,10 +185,10 @@ class QuestionController extends Controller
return $this->jsonSuccess($content);
}
$rejectOptions = $questionService->getRejectOptions();
$reasons = $questionService->getReasons();
$question = $questionService->getQuestion($id);
$this->view->setVar('reject_options', $rejectOptions);
$this->view->setVar('reasons', $reasons);
$this->view->setVar('question', $question);
}

View File

@ -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")
*/

View File

@ -4,12 +4,32 @@ namespace App\Http\Admin\Services;
use App\Builders\AnswerList as AnswerListBuilder;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Answer as AnswerModel;
use App\Models\Question as QuestionModel;
use App\Models\Reason as ReasonModel;
use App\Models\User as UserModel;
use App\Repos\Answer as AnswerRepo;
use App\Repos\Question as QuestionRepo;
use App\Repos\User as UserRepo;
use App\Services\Logic\Notice\System\AnswerApproved as AnswerApprovedNotice;
use App\Services\Logic\Notice\System\AnswerRejected as AnswerRejectedNotice;
use App\Services\Logic\Notice\System\QuestionAnswered as QuestionAnsweredNotice;
use App\Services\Logic\Point\History\AnswerPost as AnswerPostPointHistory;
use App\Validators\Answer as AnswerValidator;
class Answer extends Service
{
public function getPublishTypes()
{
return AnswerModel::publishTypes();
}
public function getReasons()
{
return ReasonModel::answerRejectOptions();
}
public function getAnswers()
{
$pagerQuery = new PagerQuery();
@ -34,6 +54,33 @@ class Answer extends Service
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);
@ -49,7 +96,16 @@ class Answer extends Service
}
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);
@ -59,24 +115,91 @@ class Answer extends Service
public function deleteAnswer($id)
{
$page = $this->findOrFail($id);
$answer = $this->findOrFail($id);
$page->deleted = 1;
$answer->deleted = 1;
$page->update();
$answer->update();
return $page;
$question = $this->findQuestion($answer->question_id);
$this->recountQuestionAnswers($question);
$owner = $this->findUser($answer->owner_id);
$this->recountUserAnswers($owner);
return $answer;
}
public function restoreAnswer($id)
{
$page = $this->findOrFail($id);
$answer = $this->findOrFail($id);
$page->deleted = 0;
$answer->deleted = 0;
$page->update();
$answer->update();
return $page;
$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)
@ -86,13 +209,29 @@ class Answer extends Service
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();
$pipeA = $pager->items->toArray();
$items = $pager->items->toArray();
$pipeA = $builder->handleQuestions($items);
$pipeB = $builder->handleUsers($pipeA);
$pipeC = $builder->objects($pipeB);
@ -102,4 +241,55 @@ class Answer extends Service
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);
}
}

View File

@ -30,7 +30,7 @@ class Article extends Service
{
$tagRepo = new TagRepo();
$allTags = $tagRepo->findAll(['published' => 1], 'priority');
$allTags = $tagRepo->findAll(['published' => 1]);
if ($allTags->count() == 0) return [];
@ -78,7 +78,7 @@ class Article extends Service
return ArticleModel::sourceTypes();
}
public function getRejectOptions()
public function getReasons()
{
return ReasonModel::articleRejectOptions();
}
@ -124,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);
@ -168,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'])) {
@ -181,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'])) {
@ -209,7 +215,7 @@ class Article extends Service
$owner = $userRepo->findById($article->owner_id);
$this->decrUserArticleCount($owner);
$this->recountUserArticles($owner);
$this->rebuildArticleIndex($article);
@ -230,7 +236,7 @@ class Article extends Service
$owner = $userRepo->findById($article->owner_id);
$this->incrUserArticleCount($owner);
$this->recountUserArticles($owner);
$this->rebuildArticleIndex($article);
@ -249,14 +255,22 @@ class Article extends Service
$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') {
@ -296,6 +310,13 @@ class Article extends Service
return $validator->checkArticle($id);
}
protected function findUser($id)
{
$userRepo = new UserRepo();
return $userRepo->findById($id);
}
protected function handleArticles($pager)
{
if ($pager->total_items > 0) {
@ -315,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();

View File

@ -294,7 +294,7 @@ class AuthNode extends Service
],
[
'id' => '1-10',
'title' => '问管理',
'title' => '问管理',
'type' => 'menu',
'children' => [
[
@ -341,6 +341,55 @@ class AuthNode extends Service
],
],
],
[
'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' => '标签管理',
@ -436,6 +485,12 @@ class AuthNode extends Service
'type' => 'menu',
'route' => 'admin.mod.questions',
],
[
'id' => '2-10-3',
'title' => '回答列表',
'type' => 'menu',
'route' => 'admin.mod.answers',
],
],
],
[
@ -900,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',
],
],
],
[

View File

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

View File

@ -2,11 +2,14 @@
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;
@ -53,6 +56,26 @@ class Moderation extends Service
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) {
@ -91,4 +114,22 @@ class Moderation extends Service
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;
}
}

View File

@ -29,7 +29,7 @@ class Question extends Service
{
$tagRepo = new TagRepo();
$allTags = $tagRepo->findAll(['published' => 1], 'priority');
$allTags = $tagRepo->findAll(['published' => 1]);
if ($allTags->count() == 0) return [];
@ -72,7 +72,7 @@ class Question extends Service
return QuestionModel::publishTypes();
}
public function getRejectOptions()
public function getReasons()
{
return ReasonModel::questionRejectOptions();
}
@ -117,12 +117,13 @@ class Question extends Service
$question = new QuestionModel();
$question->published = QuestionModel::PUBLISH_APPROVED;
$question->owner_id = $user->id;
$question->title = $title;
$question->create();
$this->incrUserQuestionCount($user);
$this->recountUserQuestions($user);
$this->eventsManager->fire('Question:afterCreate', $this, $question);
@ -161,7 +162,12 @@ class Question extends Service
}
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'])) {
@ -189,7 +195,7 @@ class Question extends Service
$owner = $userRepo->findById($question->owner_id);
$this->decrUserQuestionCount($owner);
$this->recountUserQuestions($owner);
$this->rebuildQuestionIndex($question);
@ -210,7 +216,7 @@ class Question extends Service
$owner = $userRepo->findById($question->owner_id);
$this->incrUserQuestionCount($owner);
$this->recountUserQuestions($owner);
$this->rebuildQuestionIndex($question);
@ -237,6 +243,10 @@ class Question extends Service
$question->update();
$owner = $this->findUser($question->owner_id);
$this->recountUserQuestions($owner);
$sender = $this->getLoginUser();
if ($type == 'approve') {
@ -276,6 +286,13 @@ class Question extends Service
return $validator->checkQuestion($id);
}
protected function findUser($id)
{
$userRepo = new UserRepo();
return $userRepo->findById($id);
}
protected function handleQuestions($pager)
{
if ($pager->total_items > 0) {
@ -295,21 +312,17 @@ class Question extends Service
return $pager;
}
protected function incrUserQuestionCount(UserModel $user)
protected function recountUserQuestions(UserModel $user)
{
$user->question_count += 1;
$userRepo = new UserRepo();
$questionCount = $userRepo->countQuestions($user->id);
$user->question_count = $questionCount;
$user->update();
}
protected function decrUserQuestionCount(UserModel $user)
{
if ($user->question_count > 0) {
$user->question_count -= 1;
$user->update();
}
}
protected function rebuildQuestionCache(QuestionModel $question)
{
$cache = new QuestionCache();

View File

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

View File

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

View File

@ -0,0 +1,41 @@
{% extends 'templates/main.volt' %}
{% block content %}
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.answer.create'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>回答问题</legend>
</fieldset>
<div class="layui-form-item">
<div class="layui-input-block" style="margin:0;">
<input class="layui-input" type="text" name="title" value="{{ question.title }}" readonly="readonly">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin:0;">
<div id="vditor"></div>
<textarea name="content" class="layui-hide" id="vditor-textarea"></textarea>
</div>
</div>
<div class="layui-input-block kg-center" style="margin:0;">
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="referer" value="{{ referer }}">
<input type="hidden" name="question_id" value="{{ question.id }}">
</div>
</form>
{% 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 %}

View File

@ -0,0 +1,40 @@
{% extends 'templates/main.volt' %}
{% block content %}
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.answer.update','id':answer.id}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>编辑答案</legend>
</fieldset>
<div class="layui-form-item">
<div class="layui-input-block" style="margin:0;">
<input class="layui-input" type="text" name="title" value="{{ question.title }}" readonly="readonly">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block" style="margin:0;">
<div id="vditor"></div>
<textarea name="content" class="layui-hide" id="vditor-textarea">{{ answer.content }}</textarea>
</div>
</div>
<div class="layui-input-block kg-center" style="margin:0;">
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="referer" value="{{ referer }}">
</div>
</form>
{% 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 %}

View File

@ -0,0 +1,64 @@
{% extends 'templates/main.volt' %}
{% block content %}
{{ partial('macros/answer') }}
<table class="layui-table kg-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>信息</th>
<th>评论</th>
<th>点赞</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% 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}) %}
<tr>
<td>
<P>问题:<a href="{{ question_url }}" target="_blank">{{ item.question.title }}</a></P>
<p>回答:{{ item.summary }}</p>
<p>作者:<a href="{{ owner_url }}" target="_blank">{{ item.owner.name }}</a> 创建:{{ date('Y-m-d',item.create_time) }}</p>
</td>
<td>{{ item.comment_count }}</td>
<td>{{ item.like_count }}</td>
<td>{{ publish_status(item.published) }}</td>
<td class="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
{% if item.published == 1 %}
<li><a href="{{ review_url }}">审核回答</a></li>
{% endif %}
<li><a href="{{ edit_url }}">编辑回答</a></li>
{% if item.deleted == 0 %}
<a href="javascript:" class="kg-delete" data-url="{{ delete_url }}">删除回答</a>
{% else %}
<a href="javascript:" class="kg-restore" data-url="{{ restore_url }}">还原回答</a>
{% endif %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endblock %}

View File

@ -0,0 +1,78 @@
{% extends 'templates/main.volt' %}
{% block content %}
<fieldset class="layui-elem-field layui-field-title">
<legend>审核内容</legend>
</fieldset>
<div class="kg-mod-preview">
<div class="title">{{ question.title }}</div>
<div class="content markdown-body">{{ answer.content }}</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>审核意见</legend>
</fieldset>
<form class="layui-form kg-form kg-review-form" method="POST" action="{{ url({'for':'admin.answer.review','id':answer.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">审核</label>
<div class="layui-input-block">
<input type="radio" name="type" value="approve" title="通过" lay-filter="review">
<input type="radio" name="type" value="reject" title="拒绝" lay-filter="review">
</div>
</div>
<div id="reason-block" style="display:none;">
<div class="layui-form-item">
<label class="layui-form-label">理由</label>
<div class="layui-input-block">
<select name="reason">
<option value="">请选择</option>
{% for value,name in reasons %}
<option value="{{ value }}">{{ name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button id="kg-submit" class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{% endblock %}
{% block link_css %}
{{ css_link('home/css/markdown.css') }}
{% endblock %}
{% block inline_js %}
<script>
layui.use(['jquery', 'form'], function () {
var $ = layui.jquery;
var form = layui.form;
form.on('radio(review)', function (data) {
var block = $('#reason-block');
if (data.value === 'approve') {
block.hide();
} else {
block.show();
}
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,58 @@
{% extends 'templates/main.volt' %}
{% block content %}
<form class="layui-form kg-form" method="GET" action="{{ url({'for':'admin.answer.list'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>搜索回答</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">回答编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="id" placeholder="回答编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">问题编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="question_id" placeholder="问题编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="owner_id" placeholder="用户编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发布状态</label>
<div class="layui-input-block">
{% for value,title in publish_types %}
<input type="radio" name="published" value="{{ value }}" title="{{ title }}">
{% endfor %}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">匿名</label>
<div class="layui-input-block">
<input type="radio" name="anonymous" value="1" title="是">
<input type="radio" name="anonymous" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">删除</label>
<div class="layui-input-block">
<input type="radio" name="deleted" value="1" title="是">
<input type="radio" name="deleted" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{% endblock %}

View File

@ -30,10 +30,10 @@
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">允许评论</label>
<label class="layui-form-label">关闭评论</label>
<div class="layui-input-block">
<input type="radio" name="allow_comment" value="1" title="是" {% if article.allow_comment == 1 %}checked="checked"{% endif %}>
<input type="radio" name="allow_comment" value="0" title="否" {% if article.allow_comment == 0 %}checked="checked"{% endif %}>
<input type="radio" name="closed" value="1" title="是" {% if article.closed == 1 %}checked="checked"{% endif %}>
<input type="radio" name="closed" value="0" title="否" {% if article.closed == 0 %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">

View File

@ -38,13 +38,13 @@
<thead>
<tr>
<th>文章</th>
<th>状态</th>
<th>浏览</th>
<th>评论</th>
<th>浏览</th>
<th>点赞</th>
<th>收藏</th>
<th>状态</th>
<th>推荐</th>
<th>评论</th>
<th>关闭</th>
<th>操作</th>
</tr>
</thead>
@ -75,13 +75,13 @@
<span>创建:{{ date('Y-m-d',item.create_time) }}</span>
</p>
</td>
<td>{{ publish_status(item.published) }}</td>
<td>{{ item.view_count }}</td>
<td>{{ item.comment_count }}</td>
<td>{{ item.like_count }}</td>
<td>{{ item.favorite_count }}</td>
<td>{{ publish_status(item.published) }}</td>
<td><input type="checkbox" name="featured" value="1" lay-skin="switch" lay-text="是|否" lay-filter="featured" data-url="{{ update_url }}" {% if item.featured == 1 %}checked="checked"{% endif %}></td>
<td><input type="checkbox" name="comment" value="1" lay-skin="switch" lay-text="开|关" lay-filter="comment" data-url="{{ update_url }}" {% if item.allow_comment == 1 %}checked="checked"{% endif %}></td>
<td><input type="checkbox" name="comment" value="1" lay-skin="switch" lay-text="是|否" lay-filter="closed" data-url="{{ update_url }}" {% if item.closed == 1 %}checked="checked"{% endif %}></td>
<td class="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
@ -147,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});
},

View File

@ -36,7 +36,7 @@
<div class="layui-input-block">
<select name="reason">
<option value="">请选择</option>
{% for value,name in reject_options %}
{% for value,name in reasons %}
<option value="{{ value }}">{{ name }}</option>
{% endfor %}
</select>

View File

@ -46,6 +46,20 @@
{% endfor %}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">允许评论</label>
<div class="layui-input-block">
<input type="radio" name="closed" value="0" title="是">
<input type="radio" name="closed" value="1" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">仅我可见</label>
<div class="layui-input-block">
<input type="radio" name="private" value="1" title="是">
<input type="radio" name="private" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">推荐</label>
<div class="layui-input-block">

View File

@ -4,9 +4,9 @@
{{ partial('macros/common') }}
<table class="layui-table kg-table layui-form">
<table class="layui-table kg-table layui-form" lay-size="lg">
<colgroup>
<col>
<col width="50%">
<col>
<col>
<col>
@ -15,31 +15,23 @@
<thead>
<tr>
<th>评论</th>
<th>点赞</th>
<th>用户</th>
<th>终端</th>
<th>发布</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% 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}) %}
<tr>
<td>
<p>内容:<a href="javascript:" title="{{ item.content }}">{{ substr(item.content,0,30) }}</a></p>
<p>时间:{{ date('Y-m-d H:i',item.create_time) }},点赞:{{ item.like_count }}</p>
</td>
<td>
<p>昵称:{{ item.owner.name }}</p>
<p>编号:{{ item.owner.id }}</p>
</td>
<td>
<p>类型:{{ client_type(item.client_type) }}</p>
<p>地址:<a href="javascript:" class="kg-ip2region" data-ip="{{ item.client_ip }}">查看</a></p>
</td>
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="published" data-url="{{ update_url }}" {% if item.published == 1 %}checked="checked"{% endif %}></td>
<td>{{ item.content }}</td>
<td>{{ item.like_count }}</td>
<td><a href="{{ owner_url }}" target="_blank">{{ item.owner.name }}</a></td>
<td>{{ date('Y-m-d',item.create_time) }}</td>
<td class="center">
{% if item.deleted == 0 %}
<a href="javascript:" class="layui-badge layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</a>
@ -54,10 +46,4 @@
{{ partial('partials/pager') }}
{% endblock %}
{% block include_js %}
{{ js_include('admin/js/ip2region.js') }}
{% endblock %}

View File

@ -0,0 +1,11 @@
{%- macro publish_status(type) %}
{% if type == 1 %}
审核中
{% elseif type == 2 %}
已发布
{% elseif type == 3 %}
未通过
{% else %}
未知
{% endif %}
{%- endmacro %}

View File

@ -12,11 +12,11 @@
{%- macro source_info(type,url) %}
{% if type == 1 %}
原创
<span class="layui-badge">原创</span>
{% elseif type == 2 %}
<a href="{{ url }}" target="_blank">转载</a>
<a class="layui-badge layui-bg-blue" href="{{ url }}" target="_blank">转载</a>
{% elseif type == 3 %}
翻译
<span class="layui-badge layui-bg-gray">翻译</span>
{% else %}
N/A
{% endif %}

View File

@ -0,0 +1,55 @@
{% extends 'templates/main.volt' %}
{% block content %}
{{ partial('macros/answer') }}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>回答审核</cite></a>
</span>
</div>
</div>
<table class="layui-table kg-table layui-form">
<colgroup>
<col width="50%">
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>回答</th>
<th>作者</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% 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}) %}
<tr>
<td>
<P>问题:<a href="{{ question_url }}" target="_blank">{{ item.question.title }}</a></P>
<p class="layui-elip">回答:{{ substr(item.summary,0,32) }}</p>
</td>
<td>
<p>昵称:<a href="{{ owner_url }}" target="_blank">{{ item.owner.name }}</a></p>
<p>编号:{{ item.owner.id }}</p>
</td>
<td>{{ date('Y-m-d H:i',item.create_time) }}</td>
<td class="center">
<a href="{{ review_url }}" class="layui-btn layui-btn-sm">详情</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endblock %}

View File

@ -2,7 +2,6 @@
{% block content %}
{{ partial('macros/common') }}
{{ partial('macros/article') }}
<div class="kg-nav">
@ -18,14 +17,12 @@
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>文章</th>
<th>作者</th>
<th>终端</th>
<th>时间</th>
<th>操作</th>
</tr>
@ -36,9 +33,8 @@
{% set review_url = url({'for':'admin.article.review','id':item.id}) %}
<tr>
<td>
<p>标题:{{ item.title }}</p>
<p>标题:{{ item.title }} {{ source_info(item.source_type,item.source_url) }}</p>
<p class="meta">
<span>来源:{{ source_info(item.source_type,item.source_url) }}</span>
{% if item.tags %}
<span>标签:{{ tags_info(item.tags) }}</span>
{% endif %}
@ -48,17 +44,7 @@
<p>昵称:<a href="{{ owner_url }}" target="_blank">{{ item.owner.name }}</a></p>
<p>编号:{{ item.owner.id }}</p>
</td>
<td>
<p>类型:{{ client_type(item.client_type) }}</p>
<p>地址:<a href="javascript:" class="kg-ip2region" title="查看位置" data-ip="{{ item.client_ip }}">{{ item.client_ip }}</a></p>
</td>
<td>
{% 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 %}
</td>
<td>{{ date('Y-m-d H:i',item.create_time) }}</td>
<td class="center">
<a href="{{ review_url }}" class="layui-btn layui-btn-sm">详情</a>
</td>
@ -69,10 +55,4 @@
{{ partial('partials/pager') }}
{% endblock %}
{% block include_js %}
{{ js_include('admin/js/ip2region.js') }}
{% endblock %}

View File

@ -2,7 +2,6 @@
{% block content %}
{{ partial('macros/common') }}
{{ partial('macros/question') }}
<div class="kg-nav">
@ -18,14 +17,12 @@
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>问题</th>
<th>作者</th>
<th>终端</th>
<th>时间</th>
<th>操作</th>
</tr>
@ -47,17 +44,7 @@
<p>昵称:<a href="{{ owner_url }}" target="_blank">{{ item.owner.name }}</a></p>
<p>编号:{{ item.owner.id }}</p>
</td>
<td>
<p>类型:{{ client_type(item.client_type) }}</p>
<p>地址:<a href="javascript:" class="kg-ip2region" title="查看位置" data-ip="{{ item.client_ip }}">{{ item.client_ip }}</a></p>
</td>
<td>
{% 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 %}
</td>
<td>{{ date('Y-m-d H:i',item.create_time) }}</td>
<td class="center">
<a href="{{ review_url }}" class="layui-btn layui-btn-sm">详情</a>
</td>
@ -68,10 +55,4 @@
{{ partial('partials/pager') }}
{% endblock %}
{% block include_js %}
{{ js_include('admin/js/ip2region.js') }}
{% endblock %}

View File

@ -37,12 +37,12 @@
<thead>
<tr>
<th>问题</th>
<th>状态</th>
<th>浏览</th>
<th>回答</th>
<th>浏览</th>
<th>点赞</th>
<th>收藏</th>
<th>讨论</th>
<th>状态</th>
<th>关闭</th>
<th>操作</th>
</tr>
</thead>
@ -55,7 +55,8 @@
{% 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_url = url({'for':'admin.answer.list'},{'item_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}) %}
<tr>
<td>
<p>标题:<a href="{{ edit_url }}">{{ item.title }}</a>{{ item.id }}</p>
@ -72,12 +73,12 @@
<span>创建:{{ date('Y-m-d',item.create_time) }}</span>
</p>
</td>
<td>{{ publish_status(item.published) }}</td>
<td>{{ item.view_count }}</td>
<td>{{ item.answer_count }}</td>
<td>{{ item.view_count }}</td>
<td>{{ item.like_count }}</td>
<td>{{ item.favorite_count }}</td>
<td><input type="checkbox" name="closed" value="1" lay-skin="switch" lay-text="开|关" lay-filter="discuss" data-url="{{ update_url }}" {% if item.closed == 0 %}checked="checked"{% endif %}></td>
<td>{{ publish_status(item.published) }}</td>
<td><input type="checkbox" name="closed" value="1" lay-skin="switch" lay-text="是|否" lay-filter="closed" data-url="{{ update_url }}" {% if item.closed == 1 %}checked="checked"{% endif %}></td>
<td class="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
@ -87,6 +88,7 @@
{% elseif item.published == 2 %}
<li><a href="{{ preview_url }}" target="_blank">预览问题</a></li>
{% endif %}
<li><a href="{{ answer_add_url }}">回答问题</a></li>
<li><a href="{{ edit_url }}">编辑问题</a></li>
{% if item.deleted == 0 %}
<li><a href="javascript:" class="kg-delete" data-url="{{ delete_url }}">删除问题</a></li>
@ -94,7 +96,7 @@
<li><a href="javascript:" class="kg-restore" data-url="{{ restore_url }}">还原问题</a></li>
{% endif %}
<hr>
<li><a href="javascript:" class="kg-answer" data-url="{{ answer_url }}">回答管理</a></li>
<li><a href="{{ answer_list_url }}">回答管理</a></li>
</ul>
</div>
</td>
@ -117,11 +119,11 @@
var form = layui.form;
var layer = layui.layer;
form.on('switch(discuss)', function (data) {
form.on('switch(closed)', function (data) {
var checked = $(this).is(':checked');
var allowDiscuss = checked ? 1 : 0;
var closed = checked ? 1 : 0;
var url = $(this).data('url');
var tips = allowDiscuss === 1 ? '确定要开启讨论?' : '确定要关闭讨论?';
var tips = closed === 1 ? '确定要关闭讨论?' : '确定要开启讨论?';
layer.confirm(tips, function () {
$.ajax({
type: 'POST',

View File

@ -36,7 +36,7 @@
<div class="layui-input-block">
<select name="reason">
<option value="">请选择</option>
{% for value,name in reject_options %}
{% for value,name in reasons %}
<option value="{{ value }}">{{ name }}</option>
{% endfor %}
</select>

View File

@ -33,6 +33,7 @@
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
@ -42,7 +43,8 @@
<th>用户角色</th>
<th>课程</th>
<th>文章</th>
<th>收藏</th>
<th>提问</th>
<th>回答</th>
<th>活跃时间</th>
<th>注册时间</th>
<th>操作</th>
@ -51,6 +53,7 @@
<tbody>
{% 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}) %}
<tr>
<td class="center">
@ -71,15 +74,17 @@
</td>
<td>{{ item.course_count }}</td>
<td>{{ item.article_count }}</td>
<td>{{ item.favorite_count }}</td>
<td>{{ item.question_count }}</td>
<td>{{ item.answer_count }}</td>
<td>{{ date('Y-m-d',item.active_time) }}</td>
<td>{{ date('Y-m-d',item.create_time) }}</td>
<td class="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
<li><a href="{{ preview_url }}" target="_blank">预览</a></li>
<li><a href="{{ edit_url }}">编辑</a></li>
<li><a href="{{ preview_url }}" target="_blank">用户主页</a></li>
<li><a href="javascript:" class="kg-online" data-url="{{ online_url }}">在线记录</a></li>
<li><a href="{{ edit_url }}">编辑用户</a></li>
</ul>
</div>
</td>
@ -90,4 +95,29 @@
{{ partial('partials/pager') }}
{% endblock %}
{% block inline_js %}
<script>
layui.define(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
$('.kg-online').on('click', function () {
var url = $(this).data('url');
layer.open({
type: 2,
title: '在线记录',
area: ['800px', '600px'],
content: url
});
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,42 @@
{% extends 'templates/main.volt' %}
{% block content %}
{{ partial('macros/common') }}
<table class="layui-table kg-table">
<colgroup>
<col>
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th>终端类型</th>
<th>终端地址</th>
<th>活跃时间</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
<tr>
<td>{{ client_type(item.client_type) }}</td>
<td><a href="javascript:" title="查看位置" class="layui-badge layui-bg-gray kg-ip2region">{{ item.client_ip }}</a></td>
<td>{{ date('Y-m-d H:i',item.active_time) }}</td>
<td>{{ date('Y-m-d H:i',item.create_time) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endblock %}
{% block include_js %}
{{ js_include('admin/js/ip2region.js') }}
{% endblock %}

View File

@ -10,6 +10,7 @@ use App\Services\Logic\Answer\AnswerDelete as AnswerDeleteService;
use App\Services\Logic\Answer\AnswerInfo as AnswerInfoService;
use App\Services\Logic\Answer\AnswerLike as AnswerLikeService;
use App\Services\Logic\Answer\AnswerUpdate as AnswerUpdateService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/answer")
@ -17,6 +18,14 @@ use App\Services\Logic\Answer\AnswerUpdate as AnswerUpdateService;
class AnswerController extends Controller
{
/**
* @Get("/tips", name="home.answer.tips")
*/
public function tipsAction()
{
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
}
/**
* @Get("/add", name="home.answer.add")
*/
@ -28,6 +37,8 @@ class AnswerController extends Controller
$question = $service->getQuestion($id);
$this->seo->prependTitle('回答问题');
$this->view->setVar('question', $question);
}
@ -44,6 +55,8 @@ class AnswerController extends Controller
$question = $service->getQuestion($answer->question_id);
$this->seo->prependTitle('编辑回答');
$this->view->setVar('question', $question);
$this->view->setVar('answer', $answer);
}

View File

@ -33,6 +33,8 @@
</div>
</div>
<div id="tips" data-url="{{ url({'for':'home.answer.tips'}) }}"></div>
{% endblock %}
{% block link_css %}
@ -47,4 +49,28 @@
{{ js_include('home/js/answer.js') }}
{{ js_include('home/js/vditor.js') }}
{% endblock %}
{% block inline_js %}
<script>
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var $tips = $('#tips');
if ($tips.length > 0) {
layer.open({
type: 2,
title: '答题指南',
content: $tips.data('url'),
area: ['600px', '320px'],
});
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<div class="answer-tips">
<h3 class="suggest-text">适合作为回答的</h3>
<ul class="suggest-list">
<li><i class="layui-icon layui-icon-ok-circle"></i> 经过验证的有效解决办法</li>
<li><i class="layui-icon layui-icon-ok-circle"></i> 自己的经验指引,对解决问题有帮助</li>
<li><i class="layui-icon layui-icon-ok-circle"></i> 遵循 Markdown 语法排版,表达语义正确</li>
</ul>
<h3 class="not-suggest-text">不该作为回答的</h3>
<ul class="not-suggest-list">
<li><i class="layui-icon layui-icon-close-fill"></i> 询问内容细节或回复楼层</li>
<li><i class="layui-icon layui-icon-close-fill"></i> 与题目无关的内容</li>
<li><i class="layui-icon layui-icon-close-fill"></i> “赞” “顶” “同问” 等毫无意义的内容</li>
</ul>
</div>
{% endblock %}

View File

@ -67,10 +67,10 @@
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">允许评论</label>
<label class="layui-form-label">关闭评论</label>
<div class="layui-input-block">
<input type="radio" name="allow_comment" value="1" title="是" {% if article.allow_comment == 1 %}checked="checked"{% endif %}>
<input type="radio" name="allow_comment" value="0" title="否" {% if article.allow_comment == 0 %}checked="checked"{% endif %}>
<input type="radio" name="closed" value="1" title="是" {% if article.closed == 1 %}checked="checked"{% endif %}>
<input type="radio" name="closed" value="0" title="否" {% if article.closed == 0 %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">

View File

@ -65,7 +65,7 @@
{% endif %}
</div>
<div id="comment-anchor"></div>
{% if article.allow_comment == 1 %}
{% if article.closed == 0 %}
<div class="article-comment wrap">
{{ partial('article/comment') }}
</div>

View File

@ -16,7 +16,7 @@
<p>评价内容:{{ info.review.content }}</p>
{% elseif type == 184 %}
{% set article_url = url({'for':'home.article.show','id':info.article.id}) %}
<p>你的文章 <a href="{{ article_url }}" target="_blank">{{ info.article.title }}</a> 通过了审核</p>
<p>你的文章 <a href="{{ article_url }}" target="_blank">{{ info.article.title }}</a> 审核已通过</p>
{% elseif type == 185 %}
{% set article_url = url({'for':'home.article.show','id':info.article.id}) %}
<p>你的文章 <a href="{{ article_url }}" target="_blank">{{ info.article.title }}</a> 审核未通过</p>
@ -33,7 +33,7 @@
<p>{{ sender.name }} 喜欢了你的文章 <a href="{{ article_url }}" target="_blank">{{ info.article.title }}</a></p>
{% elseif type == 204 %}
{% set question_url = url({'for':'home.question.show','id':info.question.id}) %}
<p>你的提问 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a> 通过了审核</p>
<p>你的提问 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a> 审核已通过</p>
{% elseif type == 205 %}
{% set question_url = url({'for':'home.question.show','id':info.question.id}) %}
<p>你的提问 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a> 审核未通过</p>
@ -48,12 +48,18 @@
{% elseif type == 209 %}
{% set question_url = url({'for':'home.question.show','id':info.question.id}) %}
<p>{{ sender.name }} 喜欢了你的问题 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a></p>
{% elseif type == 209 %}
{% elseif type == 224 %}
{% set question_url = url({'for':'home.question.show','id':info.question.id}) %}
<p>{{ sender.name }} 喜欢了你的问题 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a></p>
<p>你对问题 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a> 的回答,审核已通过</p>
<p>回答内容:{{ info.answer.summary }}</p>
{% elseif type == 225 %}
{% set question_url = url({'for':'home.question.show','id':info.question.id}) %}
<p>你对问题 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a> 的回答,审核未通过</p>
<p>回答内容:{{ info.answer.summary }}</p>
{% elseif type == 228 %}
{% set question_url = url({'for':'home.question.show','id':info.question.id}) %}
<p>{{ sender.name }} 喜欢了你对问题 <a href="{{ question_url }}" target="_blank">{{ info.question.title }}</a> 的回答</p>
<p>回答内容:{{ info.answer.summary }}</p>
{% elseif type == 506 %}
<p>{{ sender.name }} 回复了你的评论:{{ info.comment.content }}</p>
<p>回复内容:{{ info.reply.content }}</p>

View File

@ -59,21 +59,21 @@
{% endif %}
</div>
<div id="answer-anchor"></div>
<div class="answer-tips">
{{ partial('question/answer_tips') }}
<div class="answer-wrap wrap center">
{% if question.me.answered == 0 %}
<button class="layui-btn layui-btn-fluid btn-answer" data-url="{{ answer_add_url }}">回答问题</button>
{% endif %}
</div>
{% if question.answer_count > 0 %}
<div class="layout-content">
<div class="content-wrap wrap">
<div class="layui-tab layui-tab-brief search-tab" lay-filter="answer">
<ul class="layui-tab-title">
<li class="layui-this" data-url="{{ answer_list_url }}?sort=popular">热门回答</li>
<li data-url="{{ answer_list_url }}?sort=latest">最新回答</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<div id="answer-list" data-url="{{ answer_list_url }}?sort=popular"></div>
</div>
<div class="answer-wrap wrap">
<div class="layui-tab layui-tab-brief search-tab">
<ul class="layui-tab-title">
<li class="layui-this" data-url="{{ answer_list_url }}?sort=popular">热门回答</li>
<li data-url="{{ answer_list_url }}?sort=latest">最新回答</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<div id="answer-list" data-url="{{ answer_list_url }}?sort=popular"></div>
</div>
</div>
</div>
@ -99,11 +99,6 @@
</div>
</div>
</div>
{% if question.me.answered == 0 %}
<div class="sidebar wrap">
<button class="layui-btn layui-btn-fluid btn-answer" data-url="{{ answer_add_url }}">回答问题</button>
</div>
{% endif %}
<div class="sidebar" id="sidebar-related" data-url="{{ question_related_url }}"></div>
</div>
</div>

View File

@ -11,7 +11,7 @@ class AppInfo
protected $link = 'https://koogua.com';
protected $version = '1.3.3';
protected $version = '1.3.4';
public function __get($name)
{

View File

@ -144,11 +144,11 @@ class Article extends Model
public $deleted = 0;
/**
* 允许评论
* 关闭标识
*
* @var int
*/
public $allow_comment = 1;
public $closed = 0;
/**
* 文字数

View File

@ -41,4 +41,20 @@ class Reason
];
}
public static function answerRejectOptions()
{
return [
101 => '答非所问',
102 => '内容质量差',
103 => '内容不实',
104 => '低俗色情',
105 => '广告软文',
106 => '恶意对比',
107 => '涉嫌歧视,恶意抹黑',
108 => '配图引起不适',
109 => '内容涉嫌违法',
110 => '其它问题',
];
}
}

View File

@ -90,13 +90,16 @@ class Answer extends Repository
public function countAnswers()
{
return (int)AnswerModel::count(['conditions' => 'deleted = 0']);
return (int)AnswerModel::count([
'conditions' => 'published = :published: AND deleted = 0',
'bind' => ['published' => AnswerModel::PUBLISH_APPROVED],
]);
}
public function countLikes($answerId)
{
return (int)AnswerLikeModel::count([
'conditions' => 'answer_id = :answer_id:',
'conditions' => 'answer_id = :answer_id: AND deleted = 0',
'bind' => ['answer_id' => $answerId],
]);
}

View File

@ -64,6 +64,10 @@ class Article extends Repository
$builder->andWhere('published = :published:', ['published' => $where['published']]);
}
if (isset($where['closed'])) {
$builder->andWhere('closed = :closed:', ['closed' => $where['closed']]);
}
if (isset($where['deleted'])) {
$builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
}
@ -79,9 +83,6 @@ class Article extends Repository
case 'popular':
$orderBy = 'score DESC';
break;
case 'comment':
$orderBy = 'comment_count DESC';
break;
default:
$orderBy = 'id DESC';
break;
@ -146,15 +147,15 @@ class Article extends Repository
public function countComments($articleId)
{
return (int)CommentModel::count([
'conditions' => 'item_id = ?1 AND item_type = ?2 AND deleted = 0',
'bind' => [1 => $articleId, 2 => CommentModel::ITEM_ARTICLE],
'conditions' => 'item_id = ?1 AND item_type = ?2 AND published = ?3 AND deleted = 0',
'bind' => [1 => $articleId, 2 => CommentModel::ITEM_ARTICLE, 3 => CommentModel::PUBLISH_APPROVED],
]);
}
public function countLikes($articleId)
{
return (int)ArticleLikeModel::count([
'conditions' => 'article_id = :article_id:',
'conditions' => 'article_id = :article_id: AND deleted = 0',
'bind' => ['article_id' => $articleId],
]);
}
@ -162,7 +163,7 @@ class Article extends Repository
public function countFavorites($articleId)
{
return (int)ArticleFavoriteModel::count([
'conditions' => 'article_id = :article_id:',
'conditions' => 'article_id = :article_id: AND deleted = 0',
'bind' => ['article_id' => $articleId],
]);
}

View File

@ -10,6 +10,7 @@ use App\Models\ChapterRead as ChapterReadModel;
use App\Models\ChapterUser as ChapterUserModel;
use App\Models\ChapterVod as ChapterVodModel;
use App\Models\Comment as CommentModel;
use App\Models\Consult as ConsultModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
@ -161,6 +162,14 @@ class Chapter extends Repository
]);
}
public function countConsults($chapterId)
{
return (int)ConsultModel::count([
'conditions' => 'chapter_id = :chapter_id: AND published = 1 AND deleted = 0',
'bind' => ['chapter_id' => $chapterId],
]);
}
public function countUsers($chapterId)
{
return (int)ChapterUserModel::count([

View File

@ -294,7 +294,9 @@ class Course extends Repository
public function countCourses()
{
return (int)CourseModel::count(['conditions' => 'deleted = 0']);
return (int)CourseModel::count([
'conditions' => 'published = 1 AND deleted = 0',
]);
}
public function countLessons($courseId)
@ -324,7 +326,7 @@ class Course extends Repository
public function countConsults($courseId)
{
return (int)ConsultModel::count([
'conditions' => 'course_id = :course_id: AND published = 1',
'conditions' => 'course_id = :course_id: AND published = 1 AND deleted = 0',
'bind' => ['course_id' => $courseId],
]);
}
@ -332,7 +334,7 @@ class Course extends Repository
public function countReviews($courseId)
{
return (int)ReviewModel::count([
'conditions' => 'course_id = :course_id: AND published = 1',
'conditions' => 'course_id = :course_id: AND published = 1 AND deleted = 0',
'bind' => ['course_id' => $courseId],
]);
}
@ -340,7 +342,7 @@ class Course extends Repository
public function countFavorites($courseId)
{
return (int)CourseFavoriteModel::count([
'conditions' => 'course_id = :course_id:',
'conditions' => 'course_id = :course_id: AND deleted = 0',
'bind' => ['course_id' => $courseId],
]);
}

View File

@ -2,6 +2,7 @@
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Models\Online as OnlineModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
@ -9,6 +10,38 @@ use Phalcon\Mvc\Model\ResultsetInterface;
class Online extends Repository
{
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder();
$builder->from(OnlineModel::class);
$builder->where('1 = 1');
if (!empty($where['user_id'])) {
$builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
}
switch ($sort) {
case 'oldest':
$orderBy = 'id ASC';
break;
default:
$orderBy = 'id DESC';
break;
}
$builder->orderBy($orderBy);
$pager = new PagerQueryBuilder([
'builder' => $builder,
'page' => $page,
'limit' => $limit,
]);
return $pager->paginate();
}
/**
* @param int $userId
* @param string $activeDate

View File

@ -147,29 +147,32 @@ class Question extends Repository
public function countQuestions()
{
return (int)QuestionModel::count(['conditions' => 'deleted = 0']);
return (int)QuestionModel::count([
'conditions' => 'published = :published: AND deleted = 0',
'bind' => ['published' => QuestionModel::PUBLISH_APPROVED],
]);
}
public function countAnswers($questionId)
{
return (int)AnswerModel::count([
'conditions' => 'question_id = :question_id: AND deleted = 0',
'bind' => ['question_id' => $questionId],
'conditions' => 'question_id = ?1 AND published = ?2 AND deleted = 0',
'bind' => [1 => $questionId, 2 => AnswerModel::PUBLISH_APPROVED],
]);
}
public function countComments($questionId)
{
return (int)CommentModel::count([
'conditions' => 'item_id = ?1 AND item_type = ?2 AND deleted = 0',
'bind' => [1 => $questionId, 2 => CommentModel::ITEM_QUESTION],
'conditions' => 'item_id = ?1 AND item_type = ?2 AND published = ?3 AND deleted = 0',
'bind' => [1 => $questionId, 2 => CommentModel::ITEM_QUESTION, 3 => CommentModel::PUBLISH_APPROVED],
]);
}
public function countFavorites($questionId)
{
return (int)QuestionFavoriteModel::count([
'conditions' => 'question_id = :question_id:',
'conditions' => 'question_id = :question_id: AND deleted = 0',
'bind' => ['question_id' => $questionId],
]);
}
@ -177,7 +180,7 @@ class Question extends Repository
public function countLikes($questionId)
{
return (int)QuestionLikeModel::count([
'conditions' => 'question_id = :question_id:',
'conditions' => 'question_id = :question_id: AND deleted = 0',
'bind' => ['question_id' => $questionId],
]);
}

View File

@ -178,31 +178,31 @@ class User extends Repository
public function countArticles($userId)
{
return (int)ArticleModel::count([
'conditions' => 'owner_id = :owner_id: AND published = 1',
'bind' => ['owner_id' => $userId],
'conditions' => 'owner_id = ?1 AND published = ?2',
'bind' => [1 => $userId, 2 => ArticleModel::PUBLISH_APPROVED],
]);
}
public function countQuestions($userId)
{
return (int)QuestionModel::count([
'conditions' => 'owner_id = :owner_id: AND published = 1',
'bind' => ['owner_id' => $userId],
'conditions' => 'owner_id = ?1 AND published = ?2',
'bind' => [1 => $userId, 2 => QuestionModel::PUBLISH_APPROVED],
]);
}
public function countAnswers($userId)
{
return (int)AnswerModel::count([
'conditions' => 'owner_id = :owner_id: AND published = 1',
'bind' => ['owner_id' => $userId],
'conditions' => 'owner_id = ?1 AND published = ?2',
'bind' => [1 => $userId, 2 => AnswerModel::PUBLISH_APPROVED],
]);
}
public function countCourseFavorites($userId)
{
return (int)CourseFavoriteModel::count([
'conditions' => 'user_id = :user_id:',
'conditions' => 'user_id = :user_id: AND deleted = 0',
'bind' => ['user_id' => $userId],
]);
}
@ -210,7 +210,7 @@ class User extends Repository
public function countArticleFavorites($userId)
{
return (int)ArticleFavoriteModel::count([
'conditions' => 'user_id = :user_id:',
'conditions' => 'user_id = :user_id: AND deleted = 0',
'bind' => ['user_id' => $userId],
]);
}

View File

@ -5,11 +5,14 @@ namespace App\Services\Logic\Answer;
use App\Models\Answer as AnswerModel;
use App\Models\Question as QuestionModel;
use App\Models\User as UserModel;
use App\Repos\Question as QuestionRepo;
use App\Repos\User as UserRepo;
use App\Services\Logic\AnswerTrait;
use App\Services\Logic\Notice\System\QuestionAnswered as QuestionAnsweredNotice;
use App\Services\Logic\Point\History\AnswerPost as AnswerPostPointHistory;
use App\Services\Logic\QuestionTrait;
use App\Services\Logic\Service as LogicService;
use App\Services\Sync\QuestionScore as QuestionScoreSync;
use App\Traits\Client as ClientTrait;
use App\Validators\Answer as AnswerValidator;
@ -28,16 +31,13 @@ class AnswerCreate extends LogicService
$user = $this->getLoginUser();
$answer = new AnswerModel();
$validator = new AnswerValidator();
$validator->checkIfAllowAnswer($question, $user);
/**
* @todo 引入自动审核机制
*/
$answer->published = AnswerModel::PUBLISH_APPROVED;
$answer = new AnswerModel();
$answer->published = $this->getPublishStatus($user);
$answer->content = $validator->checkContent($post['content']);
$answer->client_type = $this->getClientType();
@ -47,48 +47,74 @@ class AnswerCreate extends LogicService
$answer->create();
$question->last_answer_id = $answer->id;
$question->last_replier_id = $answer->owner_id;
$question->last_reply_time = $answer->create_time;
$this->recountQuestionAnswers($question);
$this->recountUserAnswers($user);
$question->update();
if ($answer->published == AnswerModel::PUBLISH_APPROVED) {
$this->incrUserAnswerCount($user);
$question->last_answer_id = $answer->id;
$question->last_replier_id = $answer->owner_id;
$question->last_reply_time = $answer->create_time;
$this->incrQuestionAnswerCount($question);
$question->update();
$this->handleAnswerPoint($answer);
$this->handleAnswerNotice($answer);
$this->syncQuestionScore($question);
$this->handleAnswerPostPoint($answer);
$this->handleQuestionAnsweredNotice($answer);
}
$this->eventsManager->fire('Answer:afterCreate', $this, $answer);
return $answer;
}
protected function incrQuestionAnswerCount(QuestionModel $question)
protected function getPublishStatus(UserModel $user)
{
$question->answer_count += 1;
return $user->answer_count > 2 ? AnswerModel::PUBLISH_APPROVED : AnswerModel::PUBLISH_PENDING;
}
protected function recountQuestionAnswers(QuestionModel $question)
{
$questionRepo = new QuestionRepo();
$answerCount = $questionRepo->countAnswers($question->id);
$question->answer_count = $answerCount;
$question->update();
}
protected function incrUserAnswerCount(UserModel $user)
protected function recountUserAnswers(UserModel $user)
{
$user->answer_count += 1;
$userRepo = new UserRepo();
$answerCount = $userRepo->countAnswers($user->id);
$user->answer_count = $answerCount;
$user->update();
}
protected function handleAnswerNotice(AnswerModel $answer)
protected function syncQuestionScore(QuestionModel $question)
{
$sync = new QuestionScoreSync();
$sync->addItem($question->id);
}
protected function handleQuestionAnsweredNotice(AnswerModel $answer)
{
if ($answer->published != AnswerModel::PUBLISH_APPROVED) return;
$notice = new QuestionAnsweredNotice();
$notice->handle($answer);
}
protected function handleAnswerPoint(AnswerModel $answer)
protected function handleAnswerPostPoint(AnswerModel $answer)
{
if ($answer->published != AnswerModel::PUBLISH_APPROVED) return;
$service = new AnswerPostPointHistory();
$service->handle($answer);

View File

@ -4,13 +4,14 @@ namespace App\Services\Logic\Answer;
use App\Models\Question as QuestionModel;
use App\Models\User as UserModel;
use App\Repos\Question as QuestionRepo;
use App\Repos\User as UserRepo;
use App\Services\Logic\AnswerTrait;
use App\Services\Logic\Question\QuestionScore as QuestionScoreService;
use App\Services\Logic\QuestionTrait;
use App\Services\Logic\Service as LogicService;
use App\Services\Sync\QuestionScore as QuestionScoreSync;
use App\Validators\Answer as AnswerValidator;
class AnswerDelete extends LogicService
{
@ -29,40 +30,47 @@ class AnswerDelete extends LogicService
$validator->checkOwner($user->id, $answer->owner_id);
$validator->checkIfAllowDelete($answer);
$answer->deleted = 1;
$answer->update();
$this->decrQuestionAnswerCount($question);
$this->updateQuestionScore($question);
$this->recountQuestionAnswers($question);
$this->recountUserAnswers($user);
$this->eventsManager->fire('Answer:afterDelete', $this, $answer);
return $answer;
}
protected function decrUserAnswerCount(UserModel $user)
protected function recountQuestionAnswers(QuestionModel $question)
{
if ($user->answer_count > 0) {
$user->answer_count -= 1;
$user->update();
}
$questionRepo = new QuestionRepo();
$answerCount = $questionRepo->countAnswers($question->id);
$question->answer_count = $answerCount;
$question->update();
}
protected function decrQuestionAnswerCount(QuestionModel $question)
protected function recountUserAnswers(UserModel $user)
{
if ($question->answer_count > 0) {
$question->answer_count -= 1;
$question->update();
}
$userRepo = new UserRepo();
$answerCount = $userRepo->countAnswers($user->id);
$user->answer_count = $answerCount;
$user->update();
}
protected function updateQuestionScore(QuestionModel $question)
protected function syncQuestionScore(QuestionModel $question)
{
$service = new QuestionScoreService();
$sync = new QuestionScoreSync();
$service->handle($question);
$sync->addItem($question->id);
}
}

View File

@ -23,13 +23,13 @@ class AnswerUpdate extends LogicService
$answer = $this->checkAnswer($id);
$question = $this->checkQuestion($answer->question_id);
$user = $this->getLoginUser();
$validator = new AnswerValidator();
$validator->checkIfAllowEdit($answer, $user);
$validator->checkOwner($user->id, $answer->owner_id);
$validator->checkIfAllowEdit($answer);
$answer->content = $validator->checkContent($post['content']);
$answer->client_type = $this->getClientType();
@ -37,8 +37,6 @@ class AnswerUpdate extends LogicService
$answer->update();
$this->syncQuestionScore($question);
$this->eventsManager->fire('Answer:afterUpdate', $this, $answer);
return $answer;

View File

@ -4,6 +4,7 @@ namespace App\Services\Logic\Article;
use App\Models\Article as ArticleModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Logic\Service as LogicService;
class ArticleCreate extends LogicService
@ -21,6 +22,7 @@ class ArticleCreate extends LogicService
$data = $this->handlePostData($post);
$data['published'] = $this->getPublishStatus($user);
$data['owner_id'] = $user->id;
$article->create($data);
@ -29,16 +31,25 @@ class ArticleCreate extends LogicService
$this->saveTags($article, $post['xm_tag_ids']);
}
$this->incrUserArticleCount($user);
$this->recountUserArticles($user);
$this->eventsManager->fire('Article:afterCreate', $this, $article);
return $article;
}
protected function incrUserArticleCount(UserModel $user)
protected function getPublishStatus(UserModel $user)
{
$user->article_count += 1;
return $user->article_count > 100 ? ArticleModel::PUBLISH_APPROVED : ArticleModel::PUBLISH_PENDING;
}
protected function recountUserArticles(UserModel $user)
{
$userRepo = new UserRepo();
$articleCount = $userRepo->countArticles($user->id);
$user->article_count = $articleCount;
$user->update();
}

View File

@ -37,8 +37,8 @@ trait ArticleDataTrait
}
}
if (isset($post['allow_comment'])) {
$data['allow_comment'] = $validator->checkAllowCommentStatus($post['allow_comment']);
if (isset($post['closed'])) {
$data['closed'] = $validator->checkCloseStatus($post['closed']);
}
if (isset($post['private'])) {

View File

@ -4,6 +4,7 @@ namespace App\Services\Logic\Article;
use App\Models\Article as ArticleModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Logic\ArticleTrait;
use App\Services\Logic\Service as LogicService;
use App\Services\Sync\ArticleIndex as ArticleIndexSync;
@ -28,19 +29,22 @@ class ArticleDelete extends LogicService
$article->update();
$this->decrUserArticleCount($user);
$this->recountUserArticles($user);
$this->rebuildArticleIndex($article);
$this->eventsManager->fire('Article:afterDelete', $this, $article);
}
protected function decrUserArticleCount(UserModel $user)
protected function recountUserArticles(UserModel $user)
{
if ($user->article_count > 0) {
$user->article_count -= 1;
$user->update();
}
$userRepo = new UserRepo();
$articleCount = $userRepo->countArticles($user->id);
$user->article_count = $articleCount;
$user->update();
}
protected function rebuildArticleIndex(ArticleModel $article)

View File

@ -51,7 +51,7 @@ class ArticleInfo extends LogicService
'owner' => $owner,
'me' => $me,
'private' => $article->private,
'allow_comment' => $article->allow_comment,
'closed' => $article->closed,
'source_type' => $article->source_type,
'source_url' => $article->source_url,
'word_count' => $article->word_count,

View File

@ -82,7 +82,7 @@ class ArticleList extends LogicService
'owner' => $owner,
'private' => $article['private'],
'published' => $article['published'],
'allow_comment' => $article['allow_comment'],
'closed' => $article['closed'],
'view_count' => $article['view_count'],
'like_count' => $article['like_count'],
'comment_count' => $article['comment_count'],

View File

@ -6,6 +6,8 @@ use App\Models\Chapter as ChapterModel;
use App\Models\Consult as ConsultModel;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Services\Logic\ChapterTrait;
use App\Services\Logic\CourseTrait;
use App\Services\Logic\Notice\DingTalk\ConsultCreate as ConsultCreateNotice;
@ -76,10 +78,8 @@ class ConsultCreate extends LogicService
$consult->create();
$this->incrCourseConsultCount($course);
$this->recountCourseConsults($course);
$this->incrUserDailyConsultCount($user);
$this->handleConsultCreateNotice($consult);
$this->eventsManager->fire('Consult:afterCreate', $this, $consult);
@ -116,12 +116,9 @@ class ConsultCreate extends LogicService
$consult->create();
$this->incrCourseConsultCount($course);
$this->incrChapterConsultCount($chapter);
$this->recountCourseConsults($course);
$this->recountChapterConsults($chapter);
$this->incrUserDailyConsultCount($user);
$this->handleConsultCreateNotice($consult);
$this->eventsManager->fire('Consult:afterCreate', $this, $consult);
@ -146,16 +143,24 @@ class ConsultCreate extends LogicService
return $priority;
}
protected function incrCourseConsultCount(CourseModel $course)
protected function recountCourseConsults(CourseModel $course)
{
$course->consult_count += 1;
$courseRepo = new CourseRepo();
$consultCount = $courseRepo->countConsults($course->id);
$course->consult_count = $consultCount;
$course->update();
}
protected function incrChapterConsultCount(ChapterModel $chapter)
protected function recountChapterConsults(ChapterModel $chapter)
{
$chapter->consult_count += 1;
$chapterRepo = new ChapterRepo();
$consultCount = $chapterRepo->countConsults($chapter->id);
$chapter->consult_count = $consultCount;
$chapter->update();
}

View File

@ -4,6 +4,8 @@ namespace App\Services\Logic\Consult;
use App\Models\Chapter as ChapterModel;
use App\Models\Course as CourseModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Services\Logic\ChapterTrait;
use App\Services\Logic\ConsultTrait;
use App\Services\Logic\CourseTrait;
@ -35,33 +37,39 @@ class ConsultDelete extends LogicService
$course = $this->checkCourse($consult->course_id);
$this->decrCourseConsultCount($course);
$this->recountCourseConsults($course);
}
if ($consult->chapter_id > 0) {
$chapter = $this->checkChapter($consult->chapter_id);
$this->decrChapterConsultCount($chapter);
$this->recountChapterConsults($chapter);
}
$this->eventsManager->fire('Consult:afterDelete', $this, $consult);
}
protected function decrCourseConsultCount(CourseModel $course)
protected function recountCourseConsults(CourseModel $course)
{
if ($course->consult_count > 0) {
$course->consult_count -= 1;
$course->update();
}
$courseRepo = new CourseRepo();
$consultCount = $courseRepo->countConsults($course->id);
$course->consult_count = $consultCount;
$course->update();
}
protected function decrChapterConsultCount(ChapterModel $chapter)
protected function recountChapterConsults(ChapterModel $chapter)
{
if ($chapter->consult_count > 0) {
$chapter->consult_count -= 1;
$chapter->update();
}
$chapterRepo = new ChapterRepo();
$consultCount = $chapterRepo->countConsults($chapter->id);
$chapter->consult_count = $consultCount;
$chapter->update();
}
}

View File

@ -13,6 +13,8 @@ class AnswerAccepted extends LogicService
public function handle(AnswerModel $answer, UserModel $sender)
{
$answerSummary = kg_substr($answer->summary, 0, 32);
$questionRepo = new QuestionRepo();
$question = $questionRepo->findById($answer->question_id);
@ -25,6 +27,7 @@ class AnswerAccepted extends LogicService
$notification->event_type = NotificationModel::TYPE_ANSWER_ACCEPTED;
$notification->event_info = [
'question' => ['id' => $question->id, 'title' => $question->title],
'answer' => ['id' => $answer->id, 'summary' => $answerSummary],
];
$notification->create();

View File

@ -0,0 +1,41 @@
<?php
namespace App\Services\Logic\Notice\System;
use App\Models\Answer as AnswerModel;
use App\Models\Notification as NotificationModel;
use App\Models\User as UserModel;
use App\Repos\Question as QuestionRepo;
use App\Services\Logic\Service as LogicService;
class AnswerApproved extends LogicService
{
public function handle(AnswerModel $answer, UserModel $sender)
{
$answerSummary = kg_substr($answer->summary, 0, 32);
$question = $this->findQuestion($answer->question_id);
$notification = new NotificationModel();
$notification->sender_id = $sender->id;
$notification->receiver_id = $answer->owner_id;
$notification->event_id = $answer->id;
$notification->event_type = NotificationModel::TYPE_ANSWER_APPROVED;
$notification->event_info = [
'question' => ['id' => $question->id, 'title' => $question->title],
'answer' => ['id' => $answer->id, 'summary' => $answerSummary],
];
$notification->create();
}
protected function findQuestion($id)
{
$questionRepo = new QuestionRepo();
return $questionRepo->findById($id);
}
}

View File

@ -13,6 +13,8 @@ class AnswerLiked extends LogicService
public function handle(AnswerModel $answer, UserModel $sender)
{
$answerSummary = kg_substr($answer->summary, 0, 32);
$questionRepo = new QuestionRepo();
$question = $questionRepo->findById($answer->question_id);
@ -25,6 +27,7 @@ class AnswerLiked extends LogicService
$notification->event_type = NotificationModel::TYPE_ANSWER_LIKED;
$notification->event_info = [
'question' => ['id' => $question->id, 'title' => $question->title],
'answer' => ['id' => $answer->id, 'summary' => $answerSummary],
];
$notification->create();

View File

@ -0,0 +1,42 @@
<?php
namespace App\Services\Logic\Notice\System;
use App\Models\Answer as AnswerModel;
use App\Models\Notification as NotificationModel;
use App\Models\User as UserModel;
use App\Repos\Question as QuestionRepo;
use App\Services\Logic\Service as LogicService;
class AnswerRejected extends LogicService
{
public function handle(AnswerModel $answer, UserModel $sender, $reason)
{
$answerSummary = kg_substr($answer->summary, 0, 32);
$question = $this->findQuestion($answer->question_id);
$notification = new NotificationModel();
$notification->sender_id = $sender->id;
$notification->receiver_id = $answer->owner_id;
$notification->event_id = $answer->id;
$notification->event_type = NotificationModel::TYPE_ANSWER_REJECTED;
$notification->event_info = [
'question' => ['id' => $question->id, 'title' => $question->title],
'answer' => ['id' => $answer->id, 'summary' => $answerSummary],
'reason' => $reason,
];
$notification->create();
}
protected function findQuestion($id)
{
$questionRepo = new QuestionRepo();
return $questionRepo->findById($id);
}
}

View File

@ -13,7 +13,7 @@ class ArticleCommented extends LogicService
public function handle(CommentModel $comment)
{
$comment->content = kg_substr($comment->content, 0, 32);
$commentContent = kg_substr($comment->content, 0, 32);
$article = $this->findArticle($comment->item_id);
@ -25,7 +25,7 @@ class ArticleCommented extends LogicService
$notification->event_type = NotificationModel::TYPE_ARTICLE_COMMENTED;
$notification->event_info = [
'article' => ['id' => $article->id, 'title' => $article->title],
'comment' => ['id' => $comment->id, 'content' => $comment->content],
'comment' => ['id' => $comment->id, 'content' => $commentContent],
];
$notification->create();

View File

@ -12,7 +12,7 @@ class CommentLiked extends LogicService
public function handle(CommentModel $comment, UserModel $sender)
{
$comment->content = kg_substr($comment->content, 0, 32);
$commentContent = kg_substr($comment->content, 0, 32);
$notification = new NotificationModel();
@ -22,7 +22,7 @@ class CommentLiked extends LogicService
$notification->event_type = NotificationModel::TYPE_COMMENT_LIKED;
$notification->event_info = [
'sender' => ['id' => $sender->id, 'name' => $sender->name],
'comment' => ['id' => $comment->id, 'content' => $comment->content],
'comment' => ['id' => $comment->id, 'content' => $commentContent],
];
$notification->create();

View File

@ -13,11 +13,11 @@ class CommentReplied extends LogicService
public function handle(CommentModel $reply)
{
$reply->content = kg_substr($reply->content, 0, 32);
$replyContent = kg_substr($reply->content, 0, 32);
$comment = $this->findComment($reply->parent_id);
$comment->content = kg_substr($comment->content, 0, 32);
$commentContent = kg_substr($comment->content, 0, 32);
$notification = new NotificationModel();
@ -26,8 +26,8 @@ class CommentReplied extends LogicService
$notification->event_id = $reply->id;
$notification->event_type = NotificationModel::TYPE_COMMENT_REPLIED;
$notification->event_info = [
'comment' => ['id' => $comment->id, 'content' => $comment->content],
'reply' => ['id' => $reply->id, 'content' => $reply->content],
'comment' => ['id' => $comment->id, 'content' => $commentContent],
'reply' => ['id' => $reply->id, 'content' => $replyContent],
];
$notification->create();

View File

@ -13,7 +13,7 @@ class ConsultLiked extends LogicService
public function handle(ConsultModel $consult, UserModel $sender)
{
$consult->question = kg_substr($consult->question, 0, 32);
$consultQuestion = kg_substr($consult->question, 0, 32);
$course = $this->findCourse($consult->course_id);
@ -25,7 +25,7 @@ class ConsultLiked extends LogicService
$notification->event_type = NotificationModel::TYPE_CONSULT_LIKED;
$notification->event_info = [
'course' => ['id' => $course->id, 'title' => $course->title],
'consult' => ['id' => $consult->id, 'question' => $consult->question],
'consult' => ['id' => $consult->id, 'question' => $consultQuestion],
];
$notification->create();

View File

@ -13,6 +13,8 @@ class QuestionAnswered extends LogicService
public function handle(AnswerModel $answer)
{
$answerSummary = kg_substr($answer->summary, 0, 32);
$question = $this->findQuestion($answer->question_id);
$notification = new NotificationModel();
@ -23,7 +25,7 @@ class QuestionAnswered extends LogicService
$notification->event_type = NotificationModel::TYPE_QUESTION_ANSWERED;
$notification->event_info = [
'question' => ['id' => $question->id, 'title' => $question->title],
'answer' => ['id' => $answer->id, 'summary' => $answer->summary],
'answer' => ['id' => $answer->id, 'summary' => $answerSummary],
];
$notification->create();

View File

@ -13,7 +13,7 @@ class ReviewLiked extends LogicService
public function handle(ReviewModel $review, UserModel $sender)
{
$review->content = kg_substr($review->content, 0, 32);
$reviewContent = kg_substr($review->content, 0, 32);
$course = $this->findCourse($review->course_id);
@ -25,7 +25,7 @@ class ReviewLiked extends LogicService
$notification->event_type = NotificationModel::TYPE_REVIEW_LIKED;
$notification->event_info = [
'course' => ['id' => $course->id, 'title' => $course->title],
'review' => ['id' => $review->id, 'content' => $review->content],
'review' => ['id' => $review->id, 'content' => $reviewContent],
];
$notification->create();

View File

@ -4,6 +4,7 @@ namespace App\Services\Logic\Question;
use App\Models\Question as QuestionModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Logic\Service as LogicService;
class QuestionCreate extends LogicService
@ -29,16 +30,25 @@ class QuestionCreate extends LogicService
$this->saveTags($question, $post['xm_tag_ids']);
}
$this->incrUserQuestionCount($user);
$this->recountUserQuestions($user);
$this->eventsManager->fire('Question:afterCreate', $this, $question);
return $question;
}
protected function incrUserQuestionCount(UserModel $user)
protected function getPublishStatus(UserModel $user)
{
$user->question_count += 1;
return $user->question_count > 3 ? QuestionModel::PUBLISH_APPROVED : QuestionModel::PUBLISH_PENDING;
}
protected function recountUserQuestions(UserModel $user)
{
$userRepo = new UserRepo();
$questionCount = $userRepo->countQuestions($user->id);
$user->question_count = $questionCount;
$user->update();
}

View File

@ -4,6 +4,7 @@ namespace App\Services\Logic\Question;
use App\Models\Question as QuestionModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Logic\QuestionTrait;
use App\Services\Logic\Service as LogicService;
use App\Services\Sync\QuestionIndex as QuestionIndexSync;
@ -30,7 +31,7 @@ class QuestionDelete extends LogicService
$question->update();
$this->decrUserQuestionCount($user);
$this->recountUserQuestions($user);
$this->rebuildQuestionIndex($question);
@ -39,12 +40,15 @@ class QuestionDelete extends LogicService
return $question;
}
protected function decrUserQuestionCount(UserModel $user)
protected function recountUserQuestions(UserModel $user)
{
if ($user->question_count > 0) {
$user->question_count -= 1;
$user->update();
}
$userRepo = new UserRepo();
$questionCount = $userRepo->countQuestions($user->id);
$user->question_count = $questionCount;
$user->update();
}
protected function rebuildQuestionIndex(QuestionModel $question)

View File

@ -5,6 +5,7 @@ namespace App\Services\Logic\Review;
use App\Models\Course as CourseModel;
use App\Models\CourseUser as CourseUserModel;
use App\Models\Review as ReviewModel;
use App\Repos\Course as CourseRepo;
use App\Services\CourseStat as CourseStatService;
use App\Services\Logic\CourseTrait;
use App\Services\Logic\Point\History\CourseReview as CourseReviewPointHistory;
@ -48,17 +49,15 @@ class ReviewCreate extends LogicService
$data['rating1'] = $validator->checkRating($post['rating1']);
$data['rating2'] = $validator->checkRating($post['rating2']);
$data['rating3'] = $validator->checkRating($post['rating3']);
$data['published'] = 1;
$review = new ReviewModel();
$review->create($data);
$this->updateCourseUserReview($courseUser);
$this->incrCourseReviewCount($course);
$this->recountCourseReviews($course);
$this->updateCourseRating($course);
$this->handleReviewPoint($review);
$this->eventsManager->fire('Review:afterCreate', $this, $review);
@ -73,13 +72,6 @@ class ReviewCreate extends LogicService
$courseUser->update();
}
protected function incrCourseReviewCount(CourseModel $course)
{
$course->review_count += 1;
$course->update();
}
protected function updateCourseRating(CourseModel $course)
{
$service = new CourseStatService();
@ -87,6 +79,17 @@ class ReviewCreate extends LogicService
$service->updateRating($course->id);
}
protected function recountCourseReviews(CourseModel $course)
{
$courseRepo = new CourseRepo();
$reviewCount = $courseRepo->countReviews($course->id);
$course->review_count = $reviewCount;
$course->update();
}
protected function handleReviewPoint(ReviewModel $review)
{
$service = new CourseReviewPointHistory();

View File

@ -3,6 +3,7 @@
namespace App\Services\Logic\Review;
use App\Models\Course as CourseModel;
use App\Repos\Course as CourseRepo;
use App\Services\CourseStat as CourseStatService;
use App\Services\Logic\CourseTrait;
use App\Services\Logic\ReviewTrait;
@ -31,19 +32,21 @@ class ReviewDelete extends LogicService
$review->update();
$this->decrCourseReviewCount($course);
$this->recountCourseReviews($course);
$this->updateCourseRating($course);
$this->eventsManager->fire('Review:afterDelete', $this, $review);
}
protected function decrCourseReviewCount(CourseModel $course)
protected function recountCourseReviews(CourseModel $course)
{
if ($course->review_count > 0) {
$course->review_count -= 1;
$course->update();
}
$courseRepo = new CourseRepo();
$reviewCount = $courseRepo->countReviews($course->id);
$course->review_count = $reviewCount;
$course->update();
}
protected function updateCourseRating(CourseModel $course)

View File

@ -6,6 +6,7 @@ use App\Caches\MaxAnswerId as MaxAnswerIdCache;
use App\Exceptions\BadRequest as BadRequestException;
use App\Models\Answer as AnswerModel;
use App\Models\Question as QuestionModel;
use App\Models\Reason as ReasonModel;
use App\Models\User as UserModel;
use App\Repos\Answer as AnswerRepo;
use App\Repos\Question as QuestionRepo;
@ -41,6 +42,13 @@ class Answer extends Validator
}
}
public function checkQuestion($id)
{
$validator = new Question();
return $validator->checkQuestion($id);
}
public function checkContent($content)
{
$value = $this->filter->sanitize($content, ['trim', 'string']);
@ -67,6 +75,13 @@ class Answer extends Validator
return $status;
}
public function checkRejectReason($reason)
{
if (!array_key_exists($reason, ReasonModel::answerRejectOptions())) {
throw new BadRequestException('answer.invalid_reject_reason');
}
}
public function checkIfAllowAnswer(QuestionModel $question, UserModel $user)
{
$allowed = true;
@ -88,13 +103,25 @@ class Answer extends Validator
}
}
public function checkIfAllowEdit(AnswerModel $answer, UserModel $user)
public function checkIfAllowEdit(AnswerModel $answer)
{
$this->checkOwner($user->id, $answer->owner_id);
if ($answer->accepted == 1) {
throw new BadRequestException('answer.edit_not_allowed');
}
if (time() - $answer->create_time > 3600) {
$case1 = $answer->published == AnswerModel::PUBLISH_APPROVED;
$case2 = time() - $answer->create_time > 3600;
if ($case1 && $case2) {
throw new BadRequestException('answer.edit_not_allowed');
}
}
public function checkIfAllowDelete(AnswerModel $answer)
{
if ($answer->accepted == 1) {
throw new BadRequestException('answer.delete_not_allowed');
}
}
}

View File

@ -149,10 +149,10 @@ class Article extends Validator
return $status;
}
public function checkAllowCommentStatus($status)
public function checkCloseStatus($status)
{
if (!in_array($status, [0, 1])) {
throw new BadRequestException('article.invalid_allow_comment_status');
throw new BadRequestException('article.invalid_close_status');
}
return $status;

View File

@ -120,7 +120,7 @@ $error['article.invalid_source_url'] = '无效的来源网址';
$error['article.invalid_feature_status'] = '无效的推荐状态';
$error['article.invalid_publish_status'] = '无效的发布状态';
$error['article.invalid_private_status'] = '无效的私有状态';
$error['article.invalid_allow_comment_status'] = '无效的允许评论状态';
$error['article.invalid_close_status'] = '无效的关闭状态';
$error['article.invalid_reject_reason'] = '无效的拒绝理由';
/**

View File

@ -23,6 +23,7 @@ final class V20210430023157 extends AbstractMigration
$this->handleRoleRoutes();
$this->handleQuestionNav();
$this->handleArticleCover();
$this->handleArticleClosed();
}
public function down()
@ -405,13 +406,29 @@ final class V20210430023157 extends AbstractMigration
'comment' => '用户编号',
'after' => 'question_id',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '删除标识',
'after' => 'user_id',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '创建时间',
'after' => 'user_id',
'after' => 'deleted',
])
->addColumn('update_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '更新时间',
'after' => 'create_time',
])
->addIndex(['user_id'], [
'name' => 'user_id',
@ -458,13 +475,29 @@ final class V20210430023157 extends AbstractMigration
'comment' => '标签编号',
'after' => 'question_id',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '删除标识',
'after' => 'user_id',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '创建时间',
'after' => 'user_id',
'after' => 'deleted',
])
->addColumn('update_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '更新时间',
'after' => 'create_time',
])
->addIndex(['question_id', 'user_id'], [
'name' => 'question_user',
@ -667,13 +700,29 @@ final class V20210430023157 extends AbstractMigration
'comment' => '标签编号',
'after' => 'answer_id',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '删除标识',
'after' => 'user_id',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '创建时间',
'after' => 'user_id',
'after' => 'deleted',
])
->addColumn('update_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '更新时间',
'after' => 'create_time',
])
->addIndex(['answer_id', 'user_id'], [
'name' => 'answer_user',
@ -824,6 +873,7 @@ final class V20210430023157 extends AbstractMigration
protected function modifyArticleTable()
{
$this->table('kg_article')
->renameColumn('allow_comment', 'closed')
->addColumn('report_count', 'integer', [
'null' => false,
'default' => '0',
@ -891,6 +941,15 @@ final class V20210430023157 extends AbstractMigration
->execute();
}
protected function handleArticleClosed()
{
$this->getQueryBuilder()
->update('kg_article')
->set('closed', 0)
->where(['closed' => 1])
->execute();
}
protected function handleQuestionNav()
{
$data = [

View File

@ -574,11 +574,11 @@
display: flex;
justify-content: space-between;
margin-bottom: 20px;
color: #999;
color: #666;
}
.article-info .meta a {
color: #999;
color: #666;
}
.article-info .meta .left span {
@ -612,6 +612,8 @@
.answer-tips h3 {
margin-bottom: 5px;
font-weight: normal;
font-size: inherit;
}
.answer-tips ul {

View File

@ -1,7 +1,6 @@
layui.use(['jquery', 'layer', 'helper'], function () {
layui.use(['jquery', 'helper'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var helper = layui.helper;
$('body').on('click', '.answer-report', function () {

View File

@ -80,4 +80,10 @@ layui.use(['jquery', 'helper'], function () {
});
});
$('.icon-reply').on('click', function () {
$('html').animate({
scrollTop: $('#comment-anchor').offset().top
}, 500);
});
});

View File

@ -58,4 +58,10 @@ layui.use(['jquery', 'helper'], function () {
});
});
$('.icon-reply').on('click', function () {
$('html').animate({
scrollTop: $('#comment-anchor').offset().top
}, 500);
});
});

View File

@ -83,12 +83,6 @@ layui.use(['jquery', 'form', 'layer', 'helper'], function () {
return false;
});
$('.icon-comment').on('click', function () {
$('html').animate({
scrollTop: $('#comment-anchor').offset().top
}, 500);
});
$('#btn-cancel-comment').on('click', function () {
$('#comment-footer').hide();
});

View File

@ -91,4 +91,10 @@ layui.use(['jquery', 'helper'], function () {
});
});
$('.icon-reply').on('click', function () {
$('html').animate({
scrollTop: $('#answer-anchor').offset().top
}, 500);
});
});