mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-07-16 05:12:19 +08:00
v1.3.4阶段提交
This commit is contained in:
parent
73a2e21cd6
commit
caa34d2675
70
app/Builders/AnswerList.php
Normal file
70
app/Builders/AnswerList.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use App\Repos\Question as QuestionRepo;
|
||||
use App\Repos\User as UserRepo;
|
||||
|
||||
class AnswerList extends Builder
|
||||
{
|
||||
|
||||
public function handleQuestions(array $answers)
|
||||
{
|
||||
$questions = $this->getQuestions($answers);
|
||||
|
||||
foreach ($answers as $key => $answer) {
|
||||
$answers[$key]['question'] = $questions[$answer['question_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $answers;
|
||||
}
|
||||
|
||||
public function handleUsers(array $answers)
|
||||
{
|
||||
$users = $this->getUsers($answers);
|
||||
|
||||
foreach ($answers as $key => $answer) {
|
||||
$answers[$key]['owner'] = $users[$answer['owner_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $answers;
|
||||
}
|
||||
|
||||
public function getQuestions(array $answers)
|
||||
{
|
||||
$ids = kg_array_column($answers, 'question_id');
|
||||
|
||||
$questionRepo = new QuestionRepo();
|
||||
|
||||
$questions = $questionRepo->findByIds($ids, ['id', 'title']);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($questions->toArray() as $question) {
|
||||
$result[$question['id']] = $question;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getUsers(array $answers)
|
||||
{
|
||||
$ids = kg_array_column($answers, 'owner_id');
|
||||
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
|
||||
|
||||
$baseUrl = kg_cos_url();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($users->toArray() as $user) {
|
||||
$user['avatar'] = $baseUrl . $user['avatar'];
|
||||
$result[$user['id']] = $user;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
53
app/Builders/QuestionList.php
Normal file
53
app/Builders/QuestionList.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use App\Repos\User as UserRepo;
|
||||
|
||||
class QuestionList extends Builder
|
||||
{
|
||||
|
||||
public function handleQuestions(array $questions)
|
||||
{
|
||||
foreach ($questions as $key => $question) {
|
||||
$questions[$key]['tags'] = json_decode($question['tags'], true);
|
||||
}
|
||||
|
||||
return $questions;
|
||||
}
|
||||
|
||||
public function handleUsers(array $questions)
|
||||
{
|
||||
$users = $this->getUsers($questions);
|
||||
|
||||
foreach ($questions as $key => $question) {
|
||||
$questions[$key]['owner'] = $users[$question['owner_id']] ?? new \stdClass();
|
||||
$questions[$key]['last_replier'] = $users[$question['last_replier_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $questions;
|
||||
}
|
||||
|
||||
public function getUsers($questions)
|
||||
{
|
||||
$ownerIds = kg_array_column($questions, 'owner_id');
|
||||
$lastReplierIds = kg_array_column($questions, 'last_replier_id');
|
||||
$ids = array_merge($ownerIds, $lastReplierIds);
|
||||
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
|
||||
|
||||
$baseUrl = kg_cos_url();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($users->toArray() as $user) {
|
||||
$user['avatar'] = $baseUrl . $user['avatar'];
|
||||
$result[$user['id']] = $user;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
75
app/Builders/TagFollowList.php
Normal file
75
app/Builders/TagFollowList.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use App\Repos\Tag as TagRepo;
|
||||
use App\Repos\User as UserRepo;
|
||||
|
||||
class TagFollowList extends Builder
|
||||
{
|
||||
|
||||
public function handleTags(array $relations)
|
||||
{
|
||||
$tags = $this->getTags($relations);
|
||||
|
||||
foreach ($relations as $key => $value) {
|
||||
$relations[$key]['tag'] = $tags[$value['tag_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $relations;
|
||||
}
|
||||
|
||||
public function handleUsers(array $relations)
|
||||
{
|
||||
$users = $this->getUsers($relations);
|
||||
|
||||
foreach ($relations as $key => $value) {
|
||||
$relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $relations;
|
||||
}
|
||||
|
||||
public function getTags(array $relations)
|
||||
{
|
||||
$ids = kg_array_column($relations, 'tag_id');
|
||||
|
||||
$tagRepo = new TagRepo();
|
||||
|
||||
$columns = ['id', 'name', 'alias', 'icon', 'follow_count'];
|
||||
|
||||
$tags = $tagRepo->findByIds($ids, $columns);
|
||||
|
||||
$baseUrl = kg_cos_url();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($tags->toArray() as $tag) {
|
||||
$tag['icon'] = $baseUrl . $tag['icon'];
|
||||
$result[$tag['id']] = $tag;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getUsers(array $relations)
|
||||
{
|
||||
$ids = kg_array_column($relations, 'user_id');
|
||||
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
|
||||
|
||||
$baseUrl = kg_cos_url();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($users->toArray() as $user) {
|
||||
$user['avatar'] = $baseUrl . $user['avatar'];
|
||||
$result[$user['id']] = $user;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
114
app/Caches/HotQuestionList.php
Normal file
114
app/Caches/HotQuestionList.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class HotQuestionList extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
$tomorrow = strtotime('tomorrow');
|
||||
|
||||
return $tomorrow - time();
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'hot_question_list';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$questions = $this->findWeeklyHotQuestions();
|
||||
|
||||
if ($questions->count() > 0) {
|
||||
return $this->handleQuestions($questions);
|
||||
}
|
||||
|
||||
$questions = $this->findMonthlyHotQuestions();
|
||||
|
||||
if ($questions->count() > 0) {
|
||||
return $this->handleQuestions($questions);
|
||||
}
|
||||
|
||||
$questions = $this->findYearlyHotQuestions();
|
||||
|
||||
if ($questions->count() > 0) {
|
||||
return $this->handleQuestions($questions);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuestionModel[] $questions
|
||||
* @return array
|
||||
*/
|
||||
protected function handleQuestions($questions)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$result[] = [
|
||||
'id' => $question->id,
|
||||
'title' => $question->title,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return ResultsetInterface|Resultset|QuestionModel[]
|
||||
*/
|
||||
protected function findWeeklyHotQuestions($limit = 10)
|
||||
{
|
||||
$createTime = strtotime('monday this week');
|
||||
|
||||
return $this->findHotQuestions($createTime, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return ResultsetInterface|Resultset|QuestionModel[]
|
||||
*/
|
||||
protected function findMonthlyHotQuestions($limit = 10)
|
||||
{
|
||||
$createTime = strtotime(date('Y-m-01'));
|
||||
|
||||
return $this->findHotQuestions($createTime, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return ResultsetInterface|Resultset|QuestionModel[]
|
||||
*/
|
||||
protected function findYearlyHotQuestions($limit = 10)
|
||||
{
|
||||
$createTime = strtotime(date('Y-01-01'));
|
||||
|
||||
return $this->findHotQuestions($createTime, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $createTime
|
||||
* @param int $limit
|
||||
* @return ResultsetInterface|Resultset|QuestionModel[]
|
||||
*/
|
||||
protected function findHotQuestions($createTime, $limit = 10)
|
||||
{
|
||||
return QuestionModel::query()
|
||||
->where('create_time > :create_time:', ['create_time' => $createTime])
|
||||
->orderBy('score DESC')
|
||||
->limit($limit)
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
29
app/Caches/MaxAnswerId.php
Normal file
29
app/Caches/MaxAnswerId.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Answer as AnswerModel;
|
||||
|
||||
class MaxAnswerId extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 365 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'max_answer_id';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$answer = AnswerModel::findFirst(['order' => 'id DESC']);
|
||||
|
||||
return $answer->id ?? 0;
|
||||
}
|
||||
|
||||
}
|
29
app/Caches/MaxQuestionId.php
Normal file
29
app/Caches/MaxQuestionId.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
|
||||
class MaxQuestionId extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 365 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'max_question_id';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$question = QuestionModel::findFirst(['order' => 'id DESC']);
|
||||
|
||||
return $question->id ?? 0;
|
||||
}
|
||||
|
||||
}
|
31
app/Caches/Question.php
Normal file
31
app/Caches/Question.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Repos\Question as QuestionRepo;
|
||||
|
||||
class Question extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return "question:{$id}";
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$questionRepo = new QuestionRepo();
|
||||
|
||||
$question = $questionRepo->findById($id);
|
||||
|
||||
return $question ?: null;
|
||||
}
|
||||
|
||||
}
|
@ -5,11 +5,9 @@ namespace App\Caches;
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
|
||||
class ArticleRelatedList extends Cache
|
||||
class TaggedArticleList extends Cache
|
||||
{
|
||||
|
||||
protected $articleId;
|
||||
|
||||
protected $limit = 5;
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
@ -21,25 +19,15 @@ class ArticleRelatedList extends Cache
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return "article_related_list:{$id}";
|
||||
return "tagged_article_list:{$id}";
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$this->articleId = $id;
|
||||
|
||||
$articleRepo = new ArticleRepo();
|
||||
|
||||
$article = $articleRepo->findById($id);
|
||||
|
||||
if (empty($article->tags)) return [];
|
||||
|
||||
$tagIds = kg_array_column($article->tags, 'id');
|
||||
|
||||
$randKey = array_rand($tagIds);
|
||||
|
||||
$where = [
|
||||
'tag_id' => $tagIds[$randKey],
|
||||
'tag_id' => $id,
|
||||
'published' => ArticleModel::PUBLISH_APPROVED,
|
||||
];
|
||||
|
||||
@ -61,7 +49,7 @@ class ArticleRelatedList extends Cache
|
||||
$count = 0;
|
||||
|
||||
foreach ($articles as $article) {
|
||||
if ($article->id != $this->articleId && $count < $this->limit) {
|
||||
if ($count < $this->limit) {
|
||||
$result[] = [
|
||||
'id' => $article->id,
|
||||
'title' => $article->title,
|
69
app/Caches/TaggedQuestionList.php
Normal file
69
app/Caches/TaggedQuestionList.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
use App\Repos\Question as QuestionRepo;
|
||||
|
||||
class TaggedQuestionList extends Cache
|
||||
{
|
||||
|
||||
protected $limit = 5;
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return "tagged_question_list:{$id}";
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
|
||||
$questionRepo = new QuestionRepo();
|
||||
|
||||
$where = [
|
||||
'tag_id' => $id,
|
||||
'published' => QuestionModel::PUBLISH_APPROVED,
|
||||
];
|
||||
|
||||
$pager = $questionRepo->paginate($where);
|
||||
|
||||
if ($pager->total_items == 0) return [];
|
||||
|
||||
return $this->handleContent($pager->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuestionModel[] $questions
|
||||
* @return array
|
||||
*/
|
||||
public function handleContent($questions)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($questions as $question) {
|
||||
if ($count < $this->limit) {
|
||||
$result[] = [
|
||||
'id' => $question->id,
|
||||
'title' => $question->title,
|
||||
'view_count' => $question->view_count,
|
||||
'like_count' => $question->like_count,
|
||||
'answer_count' => $question->answer_count,
|
||||
'favorite_count' => $question->favorite_count,
|
||||
];
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
103
app/Caches/TopAnswererList.php
Normal file
103
app/Caches/TopAnswererList.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Models\ArticleLike as ArticleLikeModel;
|
||||
use App\Repos\User as UserRepo;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class TopAnswererList extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'question_top_answerer_list';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$rankings = $this->findWeeklyAuthorRankings();
|
||||
|
||||
if ($rankings->count() > 0) {
|
||||
$userIds = kg_array_column($rankings->toArray(), 'author_id');
|
||||
return $this->handleUsers($userIds);
|
||||
}
|
||||
|
||||
$randOwners = $this->findRandArticleOwners();
|
||||
|
||||
if ($randOwners->count() > 0) {
|
||||
$userIds = kg_array_column($randOwners->toArray(), 'owner_id');
|
||||
return $this->handleUsers($userIds);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function handleUsers($userIds)
|
||||
{
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
$users = $userRepo->findByIds($userIds);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
$result[] = [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'avatar' => $user->avatar,
|
||||
'title' => $user->title,
|
||||
'about' => $user->about,
|
||||
'vip' => $user->vip,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return ResultsetInterface|Resultset
|
||||
*/
|
||||
protected function findRandArticleOwners($limit = 10)
|
||||
{
|
||||
return ArticleModel::query()
|
||||
->columns(['owner_id'])
|
||||
->orderBy('RAND()')
|
||||
->limit($limit)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return ResultsetInterface|Resultset
|
||||
*/
|
||||
protected function findWeeklyAuthorRankings($limit = 10)
|
||||
{
|
||||
$createTime = strtotime('monday this week');
|
||||
|
||||
$columns = [
|
||||
'author_id' => 'a.owner_id',
|
||||
'like_count' => 'count(al.user_id)',
|
||||
];
|
||||
|
||||
return $this->modelsManager->createBuilder()
|
||||
->columns($columns)
|
||||
->addFrom(ArticleLikeModel::class, 'al')
|
||||
->join(ArticleModel::class, 'al.article_id = a.id', 'a')
|
||||
->where('al.create_time > :create_time:', ['create_time' => $createTime])
|
||||
->groupBy('author_id')
|
||||
->orderBy('like_count DESC')
|
||||
->limit($limit)->getQuery()->execute();
|
||||
}
|
||||
|
||||
}
|
@ -118,7 +118,7 @@ class ArticleIndexTask extends Task
|
||||
protected function findArticles()
|
||||
{
|
||||
return ArticleModel::query()
|
||||
->where('published = 1')
|
||||
->where('published = :published:', ['published' => ArticleModel::PUBLISH_APPROVED])
|
||||
->execute();
|
||||
}
|
||||
|
||||
|
125
app/Console/Tasks/QuestionIndexTask.php
Normal file
125
app/Console/Tasks/QuestionIndexTask.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
use App\Services\Search\QuestionDocument;
|
||||
use App\Services\Search\QuestionSearcher;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class QuestionIndexTask extends Task
|
||||
{
|
||||
|
||||
/**
|
||||
* 搜索测试
|
||||
*
|
||||
* @command: php console.php question_index search {query}
|
||||
* @param array $params
|
||||
* @throws \XSException
|
||||
*/
|
||||
public function searchAction($params)
|
||||
{
|
||||
$query = $params[0] ?? null;
|
||||
|
||||
if (!$query) {
|
||||
exit('please special a query word' . PHP_EOL);
|
||||
}
|
||||
|
||||
$result = $this->searchQuestions($query);
|
||||
|
||||
var_export($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空索引
|
||||
*
|
||||
* @command: php console.php question_index clean
|
||||
*/
|
||||
public function cleanAction()
|
||||
{
|
||||
$this->cleanQuestionIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建索引
|
||||
*
|
||||
* @command: php console.php question_index rebuild
|
||||
*/
|
||||
public function rebuildAction()
|
||||
{
|
||||
$this->rebuildQuestionIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空索引
|
||||
*/
|
||||
protected function cleanQuestionIndex()
|
||||
{
|
||||
$handler = new QuestionSearcher();
|
||||
|
||||
$index = $handler->getXS()->getIndex();
|
||||
|
||||
echo '------ start clean question index ------' . PHP_EOL;
|
||||
|
||||
$index->clean();
|
||||
|
||||
echo '------ end clean question index ------' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建索引
|
||||
*/
|
||||
protected function rebuildQuestionIndex()
|
||||
{
|
||||
$questions = $this->findQuestions();
|
||||
|
||||
if ($questions->count() == 0) return;
|
||||
|
||||
$handler = new QuestionSearcher();
|
||||
|
||||
$documenter = new QuestionDocument();
|
||||
|
||||
$index = $handler->getXS()->getIndex();
|
||||
|
||||
echo '------ start rebuild question index ------' . PHP_EOL;
|
||||
|
||||
$index->beginRebuild();
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$document = $documenter->setDocument($question);
|
||||
$index->add($document);
|
||||
}
|
||||
|
||||
$index->endRebuild();
|
||||
|
||||
echo '------ end rebuild question index ------' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索文章
|
||||
*
|
||||
* @param string $query
|
||||
* @return array
|
||||
* @throws \XSException
|
||||
*/
|
||||
protected function searchQuestions($query)
|
||||
{
|
||||
$handler = new QuestionSearcher();
|
||||
|
||||
return $handler->search($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找文章
|
||||
*
|
||||
* @return ResultsetInterface|Resultset|QuestionModel[]
|
||||
*/
|
||||
protected function findQuestions()
|
||||
{
|
||||
return QuestionModel::query()
|
||||
->where('published = :published:', ['published' => QuestionModel::PUBLISH_APPROVED])
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
use App\Services\Search\ArticleDocument;
|
||||
use App\Services\Search\ArticleSearcher;
|
||||
@ -38,7 +39,7 @@ class SyncArticleIndexTask extends Task
|
||||
|
||||
$doc = $document->setDocument($article);
|
||||
|
||||
if ($article->published == 1) {
|
||||
if ($article->published == ArticleModel::PUBLISH_APPROVED) {
|
||||
$index->update($doc);
|
||||
} else {
|
||||
$index->del($article->id);
|
||||
|
44
app/Console/Tasks/SyncArticleScoreTask.php
Normal file
44
app/Console/Tasks/SyncArticleScoreTask.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
use App\Services\Sync\ArticleScore as ArticleScoreSync;
|
||||
use App\Services\Utils\ArticleScore as ArticleScoreService;
|
||||
|
||||
class SyncArticleScoreTask extends Task
|
||||
{
|
||||
|
||||
public function mainAction()
|
||||
{
|
||||
$redis = $this->getRedis();
|
||||
|
||||
$key = $this->getSyncKey();
|
||||
|
||||
$articleIds = $redis->sRandMember($key, 1000);
|
||||
|
||||
if (!$articleIds) return;
|
||||
|
||||
$articleRepo = new ArticleRepo();
|
||||
|
||||
$articles = $articleRepo->findByIds($articleIds);
|
||||
|
||||
if ($articles->count() == 0) return;
|
||||
|
||||
$service = new ArticleScoreService();
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$service->handle($article);
|
||||
}
|
||||
|
||||
$redis->sRem($key, ...$articleIds);
|
||||
}
|
||||
|
||||
protected function getSyncKey()
|
||||
{
|
||||
$sync = new ArticleScoreSync();
|
||||
|
||||
return $sync->getSyncKey();
|
||||
}
|
||||
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Repos\Course as CourseRepo;
|
||||
use App\Services\CourseStat as CourseStatService;
|
||||
use App\Services\Sync\CourseScore as CourseScoreSync;
|
||||
use App\Services\Utils\CourseScore as CourseScoreService;
|
||||
|
||||
class SyncCourseScoreTask extends Task
|
||||
{
|
||||
@ -25,10 +25,10 @@ class SyncCourseScoreTask extends Task
|
||||
|
||||
if ($courses->count() == 0) return;
|
||||
|
||||
$statService = new CourseStatService();
|
||||
$service = new CourseScoreService();
|
||||
|
||||
foreach ($courses as $course) {
|
||||
$statService->updateScore($course->id);
|
||||
$service->handle($course);
|
||||
}
|
||||
|
||||
$redis->sRem($key, ...$courseIds);
|
||||
|
61
app/Console/Tasks/SyncQuestionIndexTask.php
Normal file
61
app/Console/Tasks/SyncQuestionIndexTask.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
use App\Repos\Question as QuestionRepo;
|
||||
use App\Services\Search\QuestionDocument;
|
||||
use App\Services\Search\QuestionSearcher;
|
||||
use App\Services\Sync\QuestionIndex as QuestionIndexSync;
|
||||
|
||||
class SyncQuestionIndexTask extends Task
|
||||
{
|
||||
|
||||
public function mainAction()
|
||||
{
|
||||
$redis = $this->getRedis();
|
||||
|
||||
$key = $this->getSyncKey();
|
||||
|
||||
$questionIds = $redis->sRandMember($key, 1000);
|
||||
|
||||
if (!$questionIds) return;
|
||||
|
||||
$questionRepo = new QuestionRepo();
|
||||
|
||||
$questions = $questionRepo->findByIds($questionIds);
|
||||
|
||||
if ($questions->count() == 0) return;
|
||||
|
||||
$document = new QuestionDocument();
|
||||
|
||||
$handler = new QuestionSearcher();
|
||||
|
||||
$index = $handler->getXS()->getIndex();
|
||||
|
||||
$index->openBuffer();
|
||||
|
||||
foreach ($questions as $question) {
|
||||
|
||||
$doc = $document->setDocument($question);
|
||||
|
||||
if ($question->published == QuestionModel::PUBLISH_APPROVED) {
|
||||
$index->update($doc);
|
||||
} else {
|
||||
$index->del($question->id);
|
||||
}
|
||||
}
|
||||
|
||||
$index->closeBuffer();
|
||||
|
||||
$redis->sRem($key, ...$questionIds);
|
||||
}
|
||||
|
||||
protected function getSyncKey()
|
||||
{
|
||||
$sync = new QuestionIndexSync();
|
||||
|
||||
return $sync->getSyncKey();
|
||||
}
|
||||
|
||||
}
|
44
app/Console/Tasks/SyncQuestionScoreTask.php
Normal file
44
app/Console/Tasks/SyncQuestionScoreTask.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Repos\Question as QuestionRepo;
|
||||
use App\Services\Sync\QuestionScore as QuestionScoreSync;
|
||||
use App\Services\Utils\QuestionScore as QuestionScoreService;
|
||||
|
||||
class SyncQuestionScoreTask extends Task
|
||||
{
|
||||
|
||||
public function mainAction()
|
||||
{
|
||||
$redis = $this->getRedis();
|
||||
|
||||
$key = $this->getSyncKey();
|
||||
|
||||
$questionIds = $redis->sRandMember($key, 1000);
|
||||
|
||||
if (!$questionIds) return;
|
||||
|
||||
$questionRepo = new QuestionRepo();
|
||||
|
||||
$questions = $questionRepo->findByIds($questionIds);
|
||||
|
||||
if ($questions->count() == 0) return;
|
||||
|
||||
$service = new QuestionScoreService();
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$service->handle($question);
|
||||
}
|
||||
|
||||
$redis->sRem($key, ...$questionIds);
|
||||
}
|
||||
|
||||
protected function getSyncKey()
|
||||
{
|
||||
$sync = new QuestionScoreSync();
|
||||
|
||||
return $sync->getSyncKey();
|
||||
}
|
||||
|
||||
}
|
@ -53,6 +53,27 @@ class UploadController extends Controller
|
||||
return $this->jsonSuccess(['data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/icon/img", name="admin.upload.icon_img")
|
||||
*/
|
||||
public function uploadIconImageAction()
|
||||
{
|
||||
$service = new StorageService();
|
||||
|
||||
$file = $service->uploadIconImage();
|
||||
|
||||
if (!$file) {
|
||||
return $this->jsonError(['msg' => '上传文件失败']);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'src' => $service->getImageUrl($file->path),
|
||||
'title' => $file->name,
|
||||
];
|
||||
|
||||
return $this->jsonSuccess(['data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/cover/img", name="admin.upload.cover_img")
|
||||
*/
|
||||
|
@ -542,6 +542,12 @@ class AuthNode extends Service
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.slide.list',
|
||||
],
|
||||
[
|
||||
'id' => '2-5-5',
|
||||
'title' => '搜索轮播',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.slide.search',
|
||||
],
|
||||
[
|
||||
'id' => '2-5-2',
|
||||
'title' => '添加轮播',
|
||||
@ -560,12 +566,6 @@ class AuthNode extends Service
|
||||
'type' => 'button',
|
||||
'route' => 'admin.slide.delete',
|
||||
],
|
||||
[
|
||||
'id' => '2-5-5',
|
||||
'title' => '搜索轮播',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.slide.search',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
|
@ -19,7 +19,7 @@ class Tag extends Service
|
||||
|
||||
$params['deleted'] = $params['deleted'] ?? 0;
|
||||
|
||||
$sort = 'priority';
|
||||
$sort = $pagerQuery->getSort();
|
||||
$page = $pagerQuery->getPage();
|
||||
$limit = $pagerQuery->getLimit();
|
||||
|
||||
@ -43,7 +43,6 @@ class Tag extends Service
|
||||
|
||||
$tag->name = $validator->checkName($post['name']);
|
||||
$tag->priority = $validator->checkPriority($post['priority']);
|
||||
$tag->published = $validator->checkPublishStatus($post['published']);
|
||||
|
||||
$tag->create();
|
||||
|
||||
@ -69,6 +68,10 @@ class Tag extends Service
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($post['icon'])) {
|
||||
$data['icon'] = $validator->checkIcon($post['icon']);
|
||||
}
|
||||
|
||||
if (isset($post['priority'])) {
|
||||
$data['priority'] = $validator->checkPriority($post['priority']);
|
||||
}
|
||||
|
@ -12,19 +12,6 @@
|
||||
<input class="layui-input" type="text" name="name" lay-verify="required">
|
||||
</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="priority" lay-verify="number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">发布</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="published" value="1" title="是" checked="checked" >
|
||||
<input type="radio" name="published" value="0" title="否" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
|
@ -7,15 +7,19 @@
|
||||
<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="name" value="{{ tag2.name }}" lay-verify="required">
|
||||
<label class="layui-form-label">图标</label>
|
||||
<div class="layui-input-inline" style="width: 80px;">
|
||||
<img id="img-icon" class="kg-icon" src="{{ tag2.icon }}">
|
||||
<input type="hidden" name="icon" value="{{ tag2.icon }}">
|
||||
</div>
|
||||
<div class="layui-input-inline" style="padding-top:15px;">
|
||||
<button id="change-icon" class="layui-btn layui-btn-sm" type="button">更换</button>
|
||||
</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 class="layui-input" type="text" name="priority" value="{{ tag2.priority }}" lay-verify="number">
|
||||
<input class="layui-input" type="text" name="name" value="{{ tag2.name }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
@ -35,3 +39,9 @@
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('admin/js/icon.upload.js') }}
|
||||
|
||||
{% endblock %}
|
@ -29,15 +29,17 @@
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col width="12%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>编号</th>
|
||||
<th>图标</th>
|
||||
<th>名称</th>
|
||||
<th>关注数</th>
|
||||
<th>创建时间</th>
|
||||
<th>更新时间</th>
|
||||
<th>排序</th>
|
||||
<th>发布</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
@ -49,10 +51,11 @@
|
||||
{% set delete_url = url({'for':'admin.tag.delete','id':item.id}) %}
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td><img class="kg-icon" src="{{ item.icon }}" alt="{{ item.name }}"></td>
|
||||
<td><a href="{{ edit_url }}">{{ item.name }}</a></td>
|
||||
<td>{{ date('Y-m-d H:i',item.create_time) }}</td>
|
||||
<td>{{ date('Y-m-d H:i',item.update_time) }}</td>
|
||||
<td class="center"><input class="layui-input kg-priority" type="text" name="priority" title="数值越小排序越靠前" value="{{ item.priority }}" data-url="{{ update_url }}"></td>
|
||||
<td>{{ item.follow_count }}</td>
|
||||
<td>{{ date('Y-m-d',item.create_time) }}</td>
|
||||
<td>{{ date('Y-m-d',item.update_time) }}</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 class="center">
|
||||
|
126
app/Http/Home/Controllers/AnswerController.php
Normal file
126
app/Http/Home/Controllers/AnswerController.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Http\Home\Services\Answer as AnswerService;
|
||||
use App\Services\Logic\Answer\AnswerAccept as AnswerAcceptService;
|
||||
use App\Services\Logic\Answer\AnswerCreate as AnswerCreateService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/answer")
|
||||
*/
|
||||
class AnswerController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/add", name="home.answer.add")
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/edit", name="home.answer.edit")
|
||||
*/
|
||||
public function editAction($id)
|
||||
{
|
||||
$service = new AnswerService();
|
||||
|
||||
$answer = $service->getAnswer($id);
|
||||
|
||||
$this->view->setVar('answer', $answer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}", name="home.answer.show")
|
||||
*/
|
||||
public function showAction($id)
|
||||
{
|
||||
$service = new AnswerInfoService();
|
||||
|
||||
$answer = $service->handle($id);
|
||||
|
||||
$this->view->setVar('answer', $answer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/create", name="home.answer.create")
|
||||
*/
|
||||
public function createAction()
|
||||
{
|
||||
$service = new AnswerCreateService();
|
||||
|
||||
$service->handle();
|
||||
|
||||
$content = ['msg' => '创建答案成功'];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/update", name="home.answer.update")
|
||||
*/
|
||||
public function updateAction($id)
|
||||
{
|
||||
$service = new AnswerUpdateService();
|
||||
|
||||
$service->handle($id);
|
||||
|
||||
$content = ['msg' => '更新答案成功'];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/delete", name="home.answer.delete")
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$service = new AnswerDeleteService();
|
||||
|
||||
$service->handle($id);
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.answers']);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '删除答案成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/like", name="home.answer.like")
|
||||
*/
|
||||
public function likeAction($id)
|
||||
{
|
||||
$service = new AnswerLikeService();
|
||||
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/accept", name="home.answer.accept")
|
||||
*/
|
||||
public function acceptAction($id)
|
||||
{
|
||||
$service = new AnswerAcceptService();
|
||||
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $data['action'] == 'do' ? '采纳成功' : '取消采纳成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
@ -8,9 +8,8 @@ use App\Services\Logic\Article\ArticleFavorite as ArticleFavoriteService;
|
||||
use App\Services\Logic\Article\ArticleInfo as ArticleInfoService;
|
||||
use App\Services\Logic\Article\ArticleLike as ArticleLikeService;
|
||||
use App\Services\Logic\Article\ArticleList as ArticleListService;
|
||||
use App\Services\Logic\Article\CommentList as ArticleCommentListService;
|
||||
use App\Services\Logic\Article\HotAuthorList as HotAuthorListService;
|
||||
use App\Services\Logic\Article\RelatedList as ArticleRelatedListService;
|
||||
use App\Services\Logic\Article\RelatedArticleList as RelatedArticleListService;
|
||||
use Phalcon\Mvc\View;
|
||||
|
||||
/**
|
||||
@ -78,7 +77,7 @@ class ArticleController extends Controller
|
||||
$article = $service->getArticleModel();
|
||||
$xmTags = $service->getXmTags(0);
|
||||
|
||||
$this->seo->prependTitle('撰写文章');
|
||||
$this->seo->prependTitle('写文章');
|
||||
|
||||
$this->view->pick('article/edit');
|
||||
$this->view->setVar('source_types', $sourceTypes);
|
||||
@ -132,7 +131,7 @@ class ArticleController extends Controller
|
||||
*/
|
||||
public function relatedAction($id)
|
||||
{
|
||||
$service = new ArticleRelatedListService();
|
||||
$service = new RelatedArticleListService();
|
||||
|
||||
$articles = $service->handle($id);
|
||||
|
||||
@ -140,19 +139,6 @@ class ArticleController extends Controller
|
||||
$this->view->setVar('articles', $articles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/comments", name="home.article.comments")
|
||||
*/
|
||||
public function commentsAction($id)
|
||||
{
|
||||
$service = new ArticleCommentListService();
|
||||
|
||||
$comments = $service->handle($id);
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->setVar('comments', $comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/create", name="home.article.create")
|
||||
*/
|
||||
@ -200,8 +186,10 @@ class ArticleController extends Controller
|
||||
|
||||
$service->deleteArticle($id);
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.articles']);
|
||||
|
||||
$content = [
|
||||
'location' => $this->request->getHTTPReferer(),
|
||||
'location' => $location,
|
||||
'msg' => '删除文章成功',
|
||||
];
|
||||
|
||||
@ -236,5 +224,4 @@ class ArticleController extends Controller
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use App\Models\ChapterLive as LiveModel;
|
||||
use App\Models\Course as CourseModel;
|
||||
use App\Services\Logic\Chapter\ChapterInfo as ChapterInfoService;
|
||||
use App\Services\Logic\Chapter\ChapterLike as ChapterLikeService;
|
||||
use App\Services\Logic\Chapter\DanmuList as ChapterDanmuListService;
|
||||
use App\Services\Logic\Chapter\Learning as ChapterLearningService;
|
||||
use App\Services\Logic\Chapter\ResourceList as ChapterResourceListService;
|
||||
use App\Services\Logic\Course\BasicInfo as CourseInfoService;
|
||||
@ -30,18 +29,6 @@ class ChapterController extends Controller
|
||||
$this->view->setVar('items', $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/danmus", name="home.chapter.danmus")
|
||||
*/
|
||||
public function danmusAction($id)
|
||||
{
|
||||
$service = new ChapterDanmuListService();
|
||||
|
||||
$items = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess(['items' => $items]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}", name="home.chapter.show")
|
||||
*/
|
||||
|
@ -63,26 +63,6 @@ class CommentController extends Controller
|
||||
$this->view->setVar('comment', $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/add", name="home.comment.add")
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/reply", name="home.comment.reply")
|
||||
*/
|
||||
public function replyAction($id)
|
||||
{
|
||||
$service = new CommentInfoService();
|
||||
|
||||
$comment = $service->handle($id);
|
||||
|
||||
$this->view->setVar('comment', $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/create", name="home.comment.create")
|
||||
*/
|
||||
@ -100,9 +80,9 @@ class CommentController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/reply", name="home.comment.create_reply")
|
||||
* @Post("/{id:[0-9]+}/reply", name="home.comment.reply")
|
||||
*/
|
||||
public function createReplyAction($id)
|
||||
public function replyAction($id)
|
||||
{
|
||||
$service = new CommentReplyService();
|
||||
|
||||
|
252
app/Http/Home/Controllers/QuestionController.php
Normal file
252
app/Http/Home/Controllers/QuestionController.php
Normal file
@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Http\Home\Services\Question as QuestionService;
|
||||
use App\Http\Home\Services\QuestionQuery as QuestionQueryService;
|
||||
use App\Services\Logic\Question\AnswerList as AnswerListService;
|
||||
use App\Services\Logic\Question\HotQuestionList as HotQuestionListService;
|
||||
use App\Services\Logic\Question\QuestionCreate as QuestionCreateService;
|
||||
use App\Services\Logic\Question\QuestionDelete as QuestionDeleteService;
|
||||
use App\Services\Logic\Question\QuestionFavorite as QuestionFavoriteService;
|
||||
use App\Services\Logic\Question\QuestionInfo as QuestionInfoService;
|
||||
use App\Services\Logic\Question\QuestionLike as QuestionLikeService;
|
||||
use App\Services\Logic\Question\QuestionList as QuestionListService;
|
||||
use App\Services\Logic\Question\QuestionUpdate as QuestionUpdateService;
|
||||
use App\Services\Logic\Question\RelatedQuestionList as RelatedQuestionListService;
|
||||
use App\Services\Logic\Question\TopAnswererList as TopAnswererListService;
|
||||
use Phalcon\Mvc\View;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/question")
|
||||
*/
|
||||
class QuestionController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/hot/questions", name="home.question.hot_questions")
|
||||
*/
|
||||
public function hotQuestionsAction()
|
||||
{
|
||||
$service = new HotQuestionListService();
|
||||
|
||||
$questions = $service->handle();
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('question/hot_questions');
|
||||
$this->view->setVar('questions', $questions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/top/answerers", name="home.question.top_answerers")
|
||||
*/
|
||||
public function topAnswerersAction()
|
||||
{
|
||||
$service = new TopAnswererListService();
|
||||
|
||||
$answerers = $service->handle();
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('question/top_answerers');
|
||||
$this->view->setVar('answerers', $answerers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/list", name="home.question.list")
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$service = new QuestionQueryService();
|
||||
|
||||
$sorts = $service->handleSorts();
|
||||
$params = $service->getParams();
|
||||
|
||||
$this->seo->prependTitle('问答');
|
||||
|
||||
$this->view->setVar('sorts', $sorts);
|
||||
$this->view->setVar('params', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/pager", name="home.question.pager")
|
||||
*/
|
||||
public function pagerAction()
|
||||
{
|
||||
$service = new QuestionListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$pager->target = 'question-list';
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/add", name="home.question.add")
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
$service = new QuestionService();
|
||||
|
||||
$question = $service->getQuestionModel();
|
||||
|
||||
$xmTags = $service->getXmTags(0);
|
||||
|
||||
$this->seo->prependTitle('提问题');
|
||||
|
||||
$this->view->pick('question/edit');
|
||||
$this->view->setVar('question', $question);
|
||||
$this->view->setVar('xm_tags', $xmTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/edit", name="home.question.edit")
|
||||
*/
|
||||
public function editAction($id)
|
||||
{
|
||||
$service = new QuestionService();
|
||||
|
||||
$question = $service->getQuestion($id);
|
||||
|
||||
$xmTags = $service->getXmTags($id);
|
||||
|
||||
$this->seo->prependTitle('编辑问题');
|
||||
|
||||
$this->view->setVar('question', $question);
|
||||
$this->view->setVar('xm_tags', $xmTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}", name="home.question.show")
|
||||
*/
|
||||
public function showAction($id)
|
||||
{
|
||||
$service = new QuestionInfoService();
|
||||
|
||||
$question = $service->handle($id);
|
||||
|
||||
$this->seo->prependTitle($question['title']);
|
||||
|
||||
$this->view->setVar('question', $question);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/answers", name="home.question.answers")
|
||||
*/
|
||||
public function answersAction($id)
|
||||
{
|
||||
$service = new QuestionService();
|
||||
|
||||
$question = $service->getQuestion($id);
|
||||
|
||||
$service = new AnswerListService();
|
||||
|
||||
$pager = $service->handle($id);
|
||||
|
||||
$pager->target = 'answer-list';
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->setVar('question', $question);
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/related", name="home.question.related")
|
||||
*/
|
||||
public function relatedAction($id)
|
||||
{
|
||||
$service = new RelatedQuestionListService();
|
||||
|
||||
$questions = $service->handle($id);
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->setVar('questions', $questions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/create", name="home.question.create")
|
||||
*/
|
||||
public function createAction()
|
||||
{
|
||||
$service = new QuestionCreateService();
|
||||
|
||||
$service->handle();
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.questions']);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '创建问题成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/update", name="home.question.update")
|
||||
*/
|
||||
public function updateAction($id)
|
||||
{
|
||||
$service = new QuestionUpdateService();
|
||||
|
||||
$service->handle($id);
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.questions']);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '更新问题成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/delete", name="home.question.delete")
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$service = new QuestionDeleteService();
|
||||
|
||||
$service->handle($id);
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.questions']);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '删除问题成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/favorite", name="home.question.favorite")
|
||||
*/
|
||||
public function favoriteAction($id)
|
||||
{
|
||||
$service = new QuestionFavoriteService();
|
||||
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/like", name="home.question.like")
|
||||
*/
|
||||
public function likeAction($id)
|
||||
{
|
||||
$service = new QuestionLikeService();
|
||||
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ namespace App\Http\Home\Controllers;
|
||||
use App\Services\Logic\Search\Article as ArticleSearchService;
|
||||
use App\Services\Logic\Search\Course as CourseSearchService;
|
||||
use App\Services\Logic\Search\Group as GroupSearchService;
|
||||
use App\Services\Logic\Search\Question as QuestionSearchService;
|
||||
use App\Services\Logic\Search\User as UserSearchService;
|
||||
|
||||
/**
|
||||
@ -42,7 +43,7 @@ class SearchController extends Controller
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return ArticleSearchService|CourseSearchService|GroupSearchService|UserSearchService
|
||||
* @return ArticleSearchService|QuestionSearchService|CourseSearchService|GroupSearchService|UserSearchService
|
||||
*/
|
||||
protected function getSearchService($type)
|
||||
{
|
||||
@ -50,6 +51,9 @@ class SearchController extends Controller
|
||||
case 'article':
|
||||
$service = new ArticleSearchService();
|
||||
break;
|
||||
case 'question':
|
||||
$service = new QuestionSearchService();
|
||||
break;
|
||||
case 'group':
|
||||
$service = new GroupSearchService();
|
||||
break;
|
||||
|
70
app/Http/Home/Controllers/TagController.php
Normal file
70
app/Http/Home/Controllers/TagController.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Services\Logic\Tag\TagFollow as TagFollowService;
|
||||
use App\Services\Logic\Tag\TagList as TagListService;
|
||||
use App\Services\Logic\Tag\FollowList as FollowListService;
|
||||
use Phalcon\Mvc\View;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/tag")
|
||||
*/
|
||||
class TagController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/list", name="home.tag.list")
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$this->seo->prependTitle('标签');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/list/pager", name="home.tag.list_pager")
|
||||
*/
|
||||
public function listPagerAction()
|
||||
{
|
||||
$service = new TagListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$pager->target = 'all-tag-list';
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('tag/list_pager');
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/my/pager", name="home.tag.my_pager")
|
||||
*/
|
||||
public function myPagerAction()
|
||||
{
|
||||
$service = new FollowListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$pager->target = 'my-tag-list';
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('tag/my_pager');
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/follow", name="home.tag.follow")
|
||||
*/
|
||||
public function followAction($id)
|
||||
{
|
||||
$service = new TagFollowService();
|
||||
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $data['action'] == 'do' ? '关注成功' : '取消关注成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ namespace App\Http\Home\Controllers;
|
||||
use App\Repos\WeChatSubscribe as WeChatSubscribeRepo;
|
||||
use App\Services\Logic\Account\OAuthProvider as OAuthProviderService;
|
||||
use App\Services\Logic\User\Console\AccountInfo as AccountInfoService;
|
||||
use App\Services\Logic\User\Console\AnswerList as AnswerListService;
|
||||
use App\Services\Logic\User\Console\ArticleList as ArticleListService;
|
||||
use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService;
|
||||
use App\Services\Logic\User\Console\ConnectList as ConnectListService;
|
||||
@ -24,6 +25,7 @@ use App\Services\Logic\User\Console\PointHistory as PointHistoryService;
|
||||
use App\Services\Logic\User\Console\PointRedeemList as PointRedeemListService;
|
||||
use App\Services\Logic\User\Console\ProfileInfo as ProfileInfoService;
|
||||
use App\Services\Logic\User\Console\ProfileUpdate as ProfileUpdateService;
|
||||
use App\Services\Logic\User\Console\QuestionList as QuestionListService;
|
||||
use App\Services\Logic\User\Console\RefundList as RefundListService;
|
||||
use App\Services\Logic\User\Console\ReviewList as ReviewListService;
|
||||
use Phalcon\Mvc\Dispatcher;
|
||||
@ -154,6 +156,32 @@ class UserConsoleController extends Controller
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/questions", name="home.uc.questions")
|
||||
*/
|
||||
public function questionsAction()
|
||||
{
|
||||
$service = new QuestionListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$this->view->pick('user/console/questions');
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/answers", name="home.uc.answers")
|
||||
*/
|
||||
public function answersAction()
|
||||
{
|
||||
$service = new AnswerListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$this->view->pick('user/console/answers');
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/favorites", name="home.uc.favorites")
|
||||
*/
|
||||
|
58
app/Http/Home/Controllers/WidgetController.php
Normal file
58
app/Http/Home/Controllers/WidgetController.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Services\Logic\Question\HotQuestionList as HotQuestionListService;
|
||||
use App\Services\Logic\Question\TopAnswererList as TopAnswererListService;
|
||||
use App\Services\Logic\Tag\FollowList as FollowListService;
|
||||
use Phalcon\Mvc\View;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/widget")
|
||||
*/
|
||||
class WidgetController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/my/tags", name="home.widget.my_tags")
|
||||
*/
|
||||
public function myTagsAction()
|
||||
{
|
||||
$service = new FollowListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('widget/my_tags');
|
||||
$this->view->setVar('tags', $pager->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/hot/questions", name="home.widget.hot_questions")
|
||||
*/
|
||||
public function hotQuestionsAction()
|
||||
{
|
||||
$service = new HotQuestionListService();
|
||||
|
||||
$questions = $service->handle();
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('widget/hot_questions');
|
||||
$this->view->setVar('questions', $questions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/top/answerers", name="home.widget.top_answerers")
|
||||
*/
|
||||
public function topAnswerersAction()
|
||||
{
|
||||
$service = new TopAnswererListService();
|
||||
|
||||
$answerers = $service->handle();
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('widget/top_answerers');
|
||||
$this->view->setVar('answerers', $answerers);
|
||||
}
|
||||
|
||||
}
|
24
app/Http/Home/Services/Answer.php
Normal file
24
app/Http/Home/Services/Answer.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Services;
|
||||
|
||||
use App\Models\Answer as AnswerModel;
|
||||
use App\Services\Logic\AnswerTrait;
|
||||
use App\Services\Logic\Service as LogicService;
|
||||
|
||||
class Answer extends LogicService
|
||||
{
|
||||
|
||||
use AnswerTrait;
|
||||
|
||||
public function getAnswerModel()
|
||||
{
|
||||
return new AnswerModel();
|
||||
}
|
||||
|
||||
public function getAnswer($id)
|
||||
{
|
||||
return $this->checkAnswer($id);
|
||||
}
|
||||
|
||||
}
|
61
app/Http/Home/Services/Question.php
Normal file
61
app/Http/Home/Services/Question.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Services;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
use App\Repos\Tag as TagRepo;
|
||||
use App\Services\Logic\QuestionTrait;
|
||||
use App\Services\Logic\Service as LogicService;
|
||||
|
||||
class Question extends LogicService
|
||||
{
|
||||
|
||||
use QuestionTrait;
|
||||
|
||||
public function getQuestionModel()
|
||||
{
|
||||
$question = new QuestionModel();
|
||||
|
||||
$question->afterFetch();
|
||||
|
||||
return $question;
|
||||
}
|
||||
|
||||
public function getQuestion($id)
|
||||
{
|
||||
return $this->checkQuestion($id);
|
||||
}
|
||||
|
||||
public function getXmTags($id)
|
||||
{
|
||||
$tagRepo = new TagRepo();
|
||||
|
||||
$allTags = $tagRepo->findAll(['published' => 1], 'priority');
|
||||
|
||||
if ($allTags->count() == 0) return [];
|
||||
|
||||
$questionTagIds = [];
|
||||
|
||||
if ($id > 0) {
|
||||
$question = $this->checkQuestion($id);
|
||||
if (!empty($question->tags)) {
|
||||
$questionTagIds = kg_array_column($question->tags, 'id');
|
||||
}
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($allTags as $tag) {
|
||||
$selected = in_array($tag->id, $questionTagIds);
|
||||
$list[] = [
|
||||
'name' => $tag->name,
|
||||
'value' => $tag->id,
|
||||
'selected' => $selected,
|
||||
];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
}
|
64
app/Http/Home/Services/QuestionQuery.php
Normal file
64
app/Http/Home/Services/QuestionQuery.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Services;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
use App\Validators\QuestionQuery as QuestionQueryValidator;
|
||||
|
||||
class QuestionQuery extends Service
|
||||
{
|
||||
|
||||
protected $baseUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = $this->url->get(['for' => 'home.question.list']);
|
||||
}
|
||||
|
||||
public function handleSorts()
|
||||
{
|
||||
$params = $this->getParams();
|
||||
|
||||
$result = [];
|
||||
|
||||
$sorts = QuestionModel::sortTypes();
|
||||
|
||||
foreach ($sorts as $key => $value) {
|
||||
$params['sort'] = $key;
|
||||
$result[] = [
|
||||
'id' => $key,
|
||||
'name' => $value,
|
||||
'url' => $this->baseUrl . $this->buildParams($params),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getParams()
|
||||
{
|
||||
$query = $this->request->getQuery();
|
||||
|
||||
$params = [];
|
||||
|
||||
$validator = new QuestionQueryValidator();
|
||||
|
||||
if (isset($query['tag_id'])) {
|
||||
$validator->checkTag($query['tag_id']);
|
||||
$params['tag_id'] = $query['tag_id'];
|
||||
}
|
||||
|
||||
if (isset($query['sort'])) {
|
||||
$validator->checkSort($query['sort']);
|
||||
$params['sort'] = $query['sort'];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
protected function buildParams($params)
|
||||
{
|
||||
return $params ? '?' . http_build_query($params) : '';
|
||||
}
|
||||
|
||||
}
|
34
app/Http/Home/Views/answer/add.volt
Normal file
34
app/Http/Home/Views/answer/add.volt
Normal file
@ -0,0 +1,34 @@
|
||||
{% extends 'templates/layer.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form" method="POST" action="{{ url({'for':'home.answer.create'}) }}">
|
||||
<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-form-item center">
|
||||
<div class="layui-input-block" style="margin:0;">
|
||||
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="add_answer">提交回答</button>
|
||||
<input type="hidden" name="question_id" value="{{ request.get('question_id') }}">
|
||||
</div>
|
||||
</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('home/js/answer.js') }}
|
||||
{{ js_include('home/js/vditor.js') }}
|
||||
|
||||
{% endblock %}
|
33
app/Http/Home/Views/answer/edit.volt
Normal file
33
app/Http/Home/Views/answer/edit.volt
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends 'templates/layer.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form" method="POST" action="{{ url({'for':'home.answer.update','id':answer.id}) }}">
|
||||
<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-form-item center">
|
||||
<div class="layui-input-block" style="margin:0;">
|
||||
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="edit_answer">提交回答</button>
|
||||
</div>
|
||||
</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('home/js/answer.js') }}
|
||||
{{ js_include('home/js/vditor.js') }}
|
||||
|
||||
{% endblock %}
|
@ -9,7 +9,7 @@
|
||||
<div class="footer" id="comment-footer" style="display:none;">
|
||||
<div class="toolbar"></div>
|
||||
<div class="action">
|
||||
<button class="{{ submit_class }}" lay-submit="true" lay-filter="addComment">发布</button>
|
||||
<button class="{{ submit_class }}" lay-submit="true" lay-filter="add_comment">发布</button>
|
||||
<button class="layui-btn layui-btn-sm layui-bg-gray" id="btn-cancel-comment" type="button">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set title = article.id > 0 ? '编辑文章' : '撰写文章' %}
|
||||
{% set title = article.id > 0 ? '编辑文章' : '写文章' %}
|
||||
{% set action_url = article.id > 0 ? url({'for':'home.article.update','id':article.id}) : url({'for':'home.article.create'}) %}
|
||||
{% set source_url_display = article.source_type == 1 ? 'display:none' : 'display:block' %}
|
||||
|
||||
@ -30,8 +30,7 @@
|
||||
</div>
|
||||
<div class="layui-form-item center">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button class="kg-back layui-btn layui-btn-primary" type="button">返回</button>
|
||||
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="go">发布文章</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -41,25 +40,6 @@
|
||||
<div class="layui-card-header">基本信息</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="writer-sidebar">
|
||||
<div class="layui-form-item cover-wrap">
|
||||
<label class="layui-form-label">封面</label>
|
||||
<div class="layui-input-block">
|
||||
<img id="img-cover" class="cover" src="{{ article.cover }}!cover_270">
|
||||
<input type="hidden" name="cover" value="{{ article.cover }}">
|
||||
</div>
|
||||
<button id="change-cover" class="layui-btn layui-btn-sm btn-change" type="button">更换</button>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="category_id" lay-verify="required">
|
||||
<option value="">请选择</option>
|
||||
{% for item in categories %}
|
||||
<option value="{{ item.id }}" {% if article.category_id == item.id %}selected="selected"{% endif %}>{{ item.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">标签</label>
|
||||
<div class="layui-input-block">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% if authors %}
|
||||
{% if authors|length > 0 %}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">推荐作者</div>
|
||||
<div class="layui-card-body">
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set category_val = request.get('category_id','int','all') %}
|
||||
{% set sort_val = request.get('sort','trim','latest') %}
|
||||
{% set pager_url = url({'for':'home.article.pager'}, params) %}
|
||||
{% set hot_author_url = url({'for':'home.article.hot_authors'}) %}
|
||||
{% set hot_authors_url = url({'for':'home.article.hot_authors'}) %}
|
||||
{% set my_tags_url = url({'for':'home.widget.my_tags'},{'type':'article'}) %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
@ -17,30 +17,26 @@
|
||||
<div class="layout-main clearfix">
|
||||
<div class="layout-content">
|
||||
<div class="content-wrap wrap">
|
||||
<div class="article-sort">
|
||||
{% for sort in sorts %}
|
||||
{% set class = sort_val == sort.id ? 'layui-btn layui-btn-xs' : 'none' %}
|
||||
<a class="{{ class }}" href="{{ sort.url }}">{{ sort.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="article-list" id="article-list" data-url="{{ pager_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-sidebar">
|
||||
<div class="sidebar">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">文章分类</div>
|
||||
<div class="layui-card-body">
|
||||
<ul class="article-cate-list">
|
||||
{% for category in categories %}
|
||||
{% set class = category_val == category.id ? 'active' : '' %}
|
||||
<li class="{{ class }}"><a href="{{ category.url }}">{{ category.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="layui-tab layui-tab-brief search-tab">
|
||||
<ul class="layui-tab-title">
|
||||
{% for sort in sorts %}
|
||||
{% set class = sort_val == sort.id ? 'layui-this' : 'none' %}
|
||||
<li class="{{ class }}"><a href="{{ sort.url }}">{{ sort.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div id="article-list" data-url="{{ pager_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar" id="hot-author-list" data-url="{{ hot_author_url }}"></div>
|
||||
</div>
|
||||
<div class="layout-sidebar">
|
||||
{% if auth_user.id > 0 %}
|
||||
<div class="sidebar" id="sidebar-my-tags" data-url="{{ my_tags_url }}"></div>
|
||||
{% endif %}
|
||||
<div class="sidebar" id="sidebar-hot-authors" data-url="{{ hot_authors_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,16 +1,11 @@
|
||||
{{ partial('macros/article') }}
|
||||
|
||||
{% if pager.total_pages > 0 %}
|
||||
<div class="article-list clearfix">
|
||||
<div class="article-list">
|
||||
{% for item in pager.items %}
|
||||
{% set article_url = url({'for':'home.article.show','id':item.id}) %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
<div class="article-card clearfix">
|
||||
<div class="cover">
|
||||
<a href="{{ article_url }}" target="_blank">
|
||||
<img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="article-card">
|
||||
<div class="info">
|
||||
<div class="title layui-elip">
|
||||
<a href="{{ article_url }}" target="_blank">{{ item.title }}</a>
|
||||
@ -18,12 +13,19 @@
|
||||
<div class="summary">{{ item.summary }}</div>
|
||||
<div class="meta">
|
||||
<span class="owner"><a href="{{ owner_url }}">{{ item.owner.name }}</a></span>
|
||||
<span class="view">{{ item.view_count }} 浏览</span>
|
||||
<span class="comment">{{ item.comment_count }} 评论</span>
|
||||
<span class="like">{{ item.like_count }} 点赞</span>
|
||||
<span class="time">{{ item.create_time|time_ago }}</span>
|
||||
<span class="view">{{ item.view_count }} 浏览</span>
|
||||
<span class="like">{{ item.like_count }} 点赞</span>
|
||||
<span class="comment">{{ item.comment_count }} 评论</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if item.cover %}
|
||||
<div class="cover">
|
||||
<a href="{{ article_url }}" target="_blank">
|
||||
<img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<div class="meta">
|
||||
<span class="view">{{ article.view_count }} 浏览</span>
|
||||
<span class="like">{{ article.like_count }} 点赞</span>
|
||||
<span class="comment">{{ article.like_count }} 评论</span>
|
||||
<span class="comment">{{ article.comment_count }} 评论</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -5,14 +5,14 @@
|
||||
{{ partial('macros/article') }}
|
||||
|
||||
{% set article_list_url = url({'for':'home.article.list'}) %}
|
||||
{% set related_article_url = url({'for':'home.article.related','id':article.id}) %}
|
||||
{% set related_url = url({'for':'home.article.related','id':article.id}) %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':article.owner.id}) %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
<a href="/">首页</a>
|
||||
<a href="{{ article_list_url }}">专栏</a>
|
||||
<a><cite>文章详情</cite></a>
|
||||
<a><cite>详情</cite></a>
|
||||
</span>
|
||||
<span class="share">
|
||||
<a href="javascript:" title="分享到微信"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
|
||||
@ -82,7 +82,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar" id="related-article-list" data-url="{{ related_article_url }}"></div>
|
||||
<div class="sidebar" id="sidebar-related" data-url="{{ related_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="footer" id="comment-footer" style="display:none;">
|
||||
<div class="toolbar"></div>
|
||||
<div class="action">
|
||||
<button class="layui-btn layui-btn-sm" lay-submit="true" lay-filter="addComment">发布</button>
|
||||
<button class="layui-btn layui-btn-sm" lay-submit="true" lay-filter="add_comment">发布</button>
|
||||
<button class="layui-btn layui-btn-sm layui-bg-gray" id="btn-cancel-comment" type="button">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,10 +3,10 @@
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
{% set like_url = url({'for':'home.comment.like','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'home.comment.delete','id':item.id}) %}
|
||||
{% set reply_create_url = url({'for':'home.comment.create_reply','id':item.id}) %}
|
||||
{% set reply_url = url({'for':'home.comment.reply','id':item.id}) %}
|
||||
{% set reply_list_url = url({'for':'home.comment.replies','id':item.id},{'limit':5}) %}
|
||||
<div class="comment-box" id="comment-{{ item.id }}">
|
||||
<div class="comment-card clearfix">
|
||||
<div class="comment-card">
|
||||
<div class="avatar">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}" target="_blank">
|
||||
<img src="{{ item.owner.avatar }}!avatar_160" alt="{{ item.owner.name }}">
|
||||
@ -52,12 +52,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-form" id="comment-form-{{ item.id }}" style="display:none;">
|
||||
<form class="layui-form" method="post" action="{{ reply_create_url }}">
|
||||
<form class="layui-form" method="post" action="{{ reply_url }}">
|
||||
<textarea class="layui-textarea" name="content" placeholder="撰写评论..." lay-verify="required"></textarea>
|
||||
<div class="footer">
|
||||
<div class="toolbar"></div>
|
||||
<div class="action">
|
||||
<button class="layui-btn layui-btn-sm" lay-submit="true" lay-filter="replyComment" data-comment-id="{{ item.id }}" data-parent-id="{{ item.parent_id }}">发布</button>
|
||||
<button class="layui-btn layui-btn-sm" lay-submit="true" lay-filter="reply_comment" data-comment-id="{{ item.id }}" data-parent-id="{{ item.parent_id }}">发布</button>
|
||||
<button class="layui-btn layui-btn-sm layui-bg-gray btn-cancel-reply" type="button" data-id="{{ item.id }}">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@
|
||||
{% set delete_url = url({'for':'home.comment.delete','id':item.id}) %}
|
||||
{% set reply_create_url = url({'for':'home.comment.create_reply','id':item.id}) %}
|
||||
<div class="comment-box" id="comment-{{ item.id }}">
|
||||
<div class="comment-card clearfix">
|
||||
<div class="comment-card">
|
||||
<div class="avatar">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}" target="_blank">
|
||||
<img src="{{ item.owner.avatar }}!avatar_160" alt="{{ item.owner.name }}">
|
||||
@ -57,7 +57,7 @@
|
||||
<div class="footer">
|
||||
<div class="toolbar"></div>
|
||||
<div class="action">
|
||||
<button class="layui-btn layui-btn-sm" lay-submit="true" lay-filter="replyComment" data-comment-id="{{ item.id }}" data-parent-id="{{ item.parent_id }}">发布</button>
|
||||
<button class="layui-btn layui-btn-sm" lay-submit="true" lay-filter="reply_comment" data-comment-id="{{ item.id }}" data-parent-id="{{ item.parent_id }}">发布</button>
|
||||
<button class="layui-btn layui-btn-sm layui-bg-gray btn-cancel-reply" type="button" data-id="{{ item.id }}">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,19 +0,0 @@
|
||||
{% extends 'templates/layer.volt' %}
|
||||
|
||||
{% block content %}
|
||||
<form class="layui-form" method="post" action="{{ url({'for':'home.comment.create_reply','id':comment.id}) }}">
|
||||
<div class="layui-form-item">
|
||||
<textarea class="layui-textarea" name="content" placeholder="@{{ comment.owner.name }}" lay-verify="required"></textarea>
|
||||
</div>
|
||||
<div class="layui-form-item center">
|
||||
<button class="layui-btn" lay-submit="true" lay-filter="replyComment">提交</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('home/js/comment.js') }}
|
||||
|
||||
{% endblock %}
|
@ -5,7 +5,7 @@
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
{% set consult_url = url({'for':'home.consult.show','id':item.id}) %}
|
||||
{% set like_url = url({'for':'home.consult.like','id':item.id}) %}
|
||||
<div class="comment-card review-card clearfix">
|
||||
<div class="comment-card consult-card">
|
||||
<div class="avatar">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}" target="_blank">
|
||||
<img src="{{ item.owner.avatar }}!avatar_160" alt="{{ item.owner.name }}">
|
||||
@ -17,8 +17,8 @@
|
||||
<i class="layui-icon layui-icon-more"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ item.question }}</div>
|
||||
<div class="content">{{ item.answer }}</div>
|
||||
<div class="question">{{ item.question }}</div>
|
||||
<div class="answer">{{ item.answer }}</div>
|
||||
<div class="footer">
|
||||
<div class="left">
|
||||
<div class="column">
|
||||
|
@ -5,7 +5,7 @@
|
||||
{% for item in pager.items %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
{% set like_url = url({'for':'home.review.like','id':item.id}) %}
|
||||
<div class="comment-card review-card clearfix">
|
||||
<div class="comment-card review-card">
|
||||
<div class="avatar">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}" target="_blank">
|
||||
<img src="{{ item.owner.avatar }}!avatar_160" alt="{{ item.owner.name }}">
|
||||
|
11
app/Http/Home/Views/macros/answer.volt
Normal file
11
app/Http/Home/Views/macros/answer.volt
Normal file
@ -0,0 +1,11 @@
|
||||
{%- macro publish_status(type) %}
|
||||
{% if type == 1 %}
|
||||
审核中
|
||||
{% elseif type == 2 %}
|
||||
已发布
|
||||
{% elseif type == 3 %}
|
||||
未通过
|
||||
{% else %}
|
||||
未知
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
11
app/Http/Home/Views/macros/question.volt
Normal file
11
app/Http/Home/Views/macros/question.volt
Normal file
@ -0,0 +1,11 @@
|
||||
{%- macro publish_status(type) %}
|
||||
{% if type == 1 %}
|
||||
审核中
|
||||
{% elseif type == 2 %}
|
||||
已发布
|
||||
{% elseif type == 3 %}
|
||||
未通过
|
||||
{% else %}
|
||||
未知
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
@ -42,7 +42,7 @@
|
||||
<li class="layui-nav-item">
|
||||
<a href="javascript:">创建</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a href="javascript:">提问题</a></dd>
|
||||
<dd><a href="{{ url({'for':'home.question.add'}) }}" target="_blank">提问题</a></dd>
|
||||
<dd><a href="{{ url({'for':'home.article.add'}) }}" target="_blank">写文章</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
62
app/Http/Home/Views/question/answers.volt
Normal file
62
app/Http/Home/Views/question/answers.volt
Normal file
@ -0,0 +1,62 @@
|
||||
{% if pager.total_pages > 0 %}
|
||||
{% for item in pager.items %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
{% set accept_url = url({'for':'home.answer.accept','id':item.id}) %}
|
||||
{% set like_url = url({'for':'home.answer.like','id':item.id}) %}
|
||||
{% set edit_url = url({'for':'home.answer.edit','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'home.answer.delete','id':item.id}) %}
|
||||
{% set report_url = '' %}
|
||||
<div class="comment-card answer-card" id="answer-{{ item.id }}">
|
||||
<div class="avatar">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}" target="_blank">
|
||||
<img src="{{ item.owner.avatar }}!avatar_160" alt="{{ item.owner.name }}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="info">
|
||||
{% if item.accepted == 1 %}
|
||||
<div class="accepted">
|
||||
<span class="layui-badge layui-bg-green">已采纳</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="user">
|
||||
<a href="{{ owner_url }}" target="_blank">{{ item.owner.name }}</a>
|
||||
</div>
|
||||
<div class="content">{{ item.content }}</div>
|
||||
<div class="footer">
|
||||
<div class="left">
|
||||
<div class="column">
|
||||
<span class="time" title="{{ date('Y-m-d H:i',item.create_time) }}">{{ item.create_time|time_ago }}</span>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span class="like-count" data-count="{{ item.like_count }}">{{ item.like_count }}</span>
|
||||
{% if item.me.liked == 1 %}
|
||||
<span class="action action-like liked" title="取消点赞" data-url="{{ like_url }}">已赞</span>
|
||||
{% else %}
|
||||
<span class="action action-like" title="点赞支持" data-url="{{ like_url }}">点赞</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
{% if question.solved == 0 and auth_user.id == question.owner_id %}
|
||||
<div class="column">
|
||||
<span class="action action-accept" data-url="{{ accept_url }}">采纳</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="column">
|
||||
<span class="action action-report" data-url="{{ report_url }}">举报</span>
|
||||
</div>
|
||||
{% if auth_user.id == item.owner.id %}
|
||||
<div class="column">
|
||||
<span class="action action-edit" data-url="{{ edit_url }}">编辑</span>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span class="action action-delete" data-url="{{ delete_url }}">删除</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ partial('partials/pager_ajax') }}
|
||||
{% endif %}
|
71
app/Http/Home/Views/question/edit.volt
Normal file
71
app/Http/Home/Views/question/edit.volt
Normal file
@ -0,0 +1,71 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set title = question.id > 0 ? '编辑问题' : '提问题' %}
|
||||
{% set action_url = question.id > 0 ? url({'for':'home.question.update','id':question.id}) : url({'for':'home.question.create'}) %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
<a href="/">首页</a>
|
||||
<a><cite>{{ title }}</cite></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form class="layui-form" method="POST" action="{{ action_url }}">
|
||||
<div class="layout-main clearfix">
|
||||
<div class="layout-content">
|
||||
<div class="writer-content wrap">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="title" value="{{ question.title }}" placeholder="请输入标题..." lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<div id="vditor"></div>
|
||||
<textarea name="content" class="layui-hide" id="vditor-textarea">{{ question.content }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item center">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="go">提交问题</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-sidebar">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">基本信息</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="writer-sidebar">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">标签</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="xm-tag-ids"></div>
|
||||
<input type="hidden" name="xm_tags" value='{{ xm_tags|json_encode }}'>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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('lib/xm-select.js') }}
|
||||
{{ js_include('home/js/question.edit.js') }}
|
||||
{{ js_include('home/js/vditor.js') }}
|
||||
|
||||
{% endblock %}
|
27
app/Http/Home/Views/question/hot_questions.volt
Normal file
27
app/Http/Home/Views/question/hot_questions.volt
Normal file
@ -0,0 +1,27 @@
|
||||
{% if questions|length > 0 %}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">热门问题</div>
|
||||
<div class="layui-card-body">
|
||||
{% for item in questions %}
|
||||
{% set url = url({'for':'home.question.show','id':item.id}) %}
|
||||
{% set rank = loop.index %}
|
||||
<div class="sidebar-rank-card">
|
||||
<div class="rank">
|
||||
{% if rank == 1 %}
|
||||
<span class="layui-badge layui-bg-red">{{ rank }}</span>
|
||||
{% elseif rank == 2 %}
|
||||
<span class="layui-badge layui-bg-blue">{{ rank }}</span>
|
||||
{% elseif rank == 3 %}
|
||||
<span class="layui-badge layui-bg-green">{{ rank }}</span>
|
||||
{% else %}
|
||||
<span class="layui-badge layui-bg-gray">{{ rank }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="title">
|
||||
<a href="{{ url }}">{{ item.title }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
51
app/Http/Home/Views/question/list.volt
Normal file
51
app/Http/Home/Views/question/list.volt
Normal file
@ -0,0 +1,51 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set sort_val = request.get('sort','trim','latest') %}
|
||||
{% set pager_url = url({'for':'home.question.pager'}, params) %}
|
||||
{% set hot_questions_url = url({'for':'home.question.hot_questions'}) %}
|
||||
{% set top_answerers_url = url({'for':'home.question.top_answerers'}) %}
|
||||
{% set my_tags_url = url({'for':'home.widget.my_tags'},{'type':'question'}) %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
<a href="/">首页</a>
|
||||
<a><cite>问答</cite></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-main clearfix">
|
||||
<div class="layout-content">
|
||||
<div class="content-wrap wrap">
|
||||
<div class="layui-tab layui-tab-brief search-tab">
|
||||
<ul class="layui-tab-title">
|
||||
{% for sort in sorts %}
|
||||
{% set class = sort_val == sort.id ? 'layui-this' : 'none' %}
|
||||
<li class="{{ class }}"><a href="{{ sort.url }}">{{ sort.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div id="question-list" data-url="{{ pager_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-sidebar">
|
||||
{% if auth_user.id > 0 %}
|
||||
<div class="sidebar" id="sidebar-my-tags" data-url="{{ my_tags_url }}"></div>
|
||||
{% endif %}
|
||||
<div class="sidebar" id="sidebar-hot-questions" data-url="{{ hot_questions_url }}"></div>
|
||||
<div class="sidebar" id="sidebar-top-answerers" data-url="{{ top_answerers_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('home/js/question.list.js') }}
|
||||
|
||||
{% endblock %}
|
43
app/Http/Home/Views/question/pager.volt
Normal file
43
app/Http/Home/Views/question/pager.volt
Normal file
@ -0,0 +1,43 @@
|
||||
{{ partial('macros/question') }}
|
||||
|
||||
{% if pager.total_pages > 0 %}
|
||||
<div class="question-list">
|
||||
{% for item in pager.items %}
|
||||
{% set question_url = url({'for':'home.question.show','id':item.id}) %}
|
||||
{% set solved_class = item.solved ? 'column solved' : 'column' %}
|
||||
<div class="article-card question-card">
|
||||
<div class="info">
|
||||
<div class="title layui-elip">
|
||||
<a href="{{ question_url }}" target="_blank">{{ item.title }}</a>
|
||||
</div>
|
||||
<div class="summary">{{ item.summary }}</div>
|
||||
<div class="meta">
|
||||
{% if item.last_replier.id is defined %}
|
||||
{% set last_replier_url = url({'for':'home.user.show','id':item.last_replier.id}) %}
|
||||
<span class="replier"><a href="{{ last_replier_url }}">{{ item.last_replier.name }}</a></span>
|
||||
<span class="time">{{ item.last_reply_time|time_ago }}</span>
|
||||
<span class="view">{{ item.view_count }} 浏览</span>
|
||||
<span class="like">{{ item.like_count }} 点赞</span>
|
||||
<span class="answer">{{ item.answer_count }} 回答</span>
|
||||
{% else %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
<span class="owner"><a href="{{ owner_url }}">{{ item.owner.name }}</a></span>
|
||||
<span class="time">{{ item.create_time|time_ago }}</span>
|
||||
<span class="view">{{ item.view_count }} 浏览</span>
|
||||
<span class="like">{{ item.like_count }} 点赞</span>
|
||||
<span class="answer">{{ item.answer_count }} 回答</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if item.cover %}
|
||||
<div class="cover">
|
||||
<a href="{{ question_url }}" target="_blank">
|
||||
<img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ partial('partials/pager_ajax') }}
|
||||
{% endif %}
|
20
app/Http/Home/Views/question/related.volt
Normal file
20
app/Http/Home/Views/question/related.volt
Normal file
@ -0,0 +1,20 @@
|
||||
{% if questions %}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">相关问题</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="sidebar-article-list">
|
||||
{% for question in questions %}
|
||||
{% set question_url = url({'for':'home.question.show','id':question.id}) %}
|
||||
<div class="title">
|
||||
<a href="{{ question_url }}" target="_blank">{{ question.title }}</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="view">{{ question.view_count }} 浏览</span>
|
||||
<span class="like">{{ question.like_count }} 点赞</span>
|
||||
<span class="answer">{{ question.answer_count }} 回答</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
108
app/Http/Home/Views/question/show.volt
Normal file
108
app/Http/Home/Views/question/show.volt
Normal file
@ -0,0 +1,108 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ partial('macros/question') }}
|
||||
|
||||
{% set question_list_url = url({'for':'home.question.list'}) %}
|
||||
{% set add_answer_url = url({'for':'home.answer.add'},{'question_id':question.id}) %}
|
||||
{% set answer_list_url = url({'for':'home.question.answers','id':question.id}) %}
|
||||
{% set related_url = url({'for':'home.question.related','id':question.id}) %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':question.owner.id}) %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
<a href="/">首页</a>
|
||||
<a href="{{ question_list_url }}">问答</a>
|
||||
<a><cite>详情</cite></a>
|
||||
</span>
|
||||
<span class="share">
|
||||
<a href="javascript:" title="分享到微信"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
|
||||
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
|
||||
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-main clearfix">
|
||||
<div class="layout-sticky">
|
||||
{{ partial('question/sticky') }}
|
||||
</div>
|
||||
<div class="layout-content">
|
||||
<div class="article-info wrap">
|
||||
<div class="title">{{ question.title }}</div>
|
||||
<div class="meta">
|
||||
<span class="owner">
|
||||
<a href="{{ owner_url }}">{{ question.owner.name }}</a>
|
||||
</span>
|
||||
<span class="view">{{ question.view_count }} 阅读</span>
|
||||
<span class="answer">{{ question.answer_count }} 回答</span>
|
||||
<span class="time" title="{{ date('Y-m-d H:i:s',question.create_time) }}">{{ question.create_time|time_ago }}</span>
|
||||
</div>
|
||||
<div class="content markdown-body">{{ question.content }}</div>
|
||||
{% if question.tags %}
|
||||
<div class="tags">
|
||||
{% for item in question.tags %}
|
||||
{% set url = url({'for':'home.question.list'},{'tag_id':item.id}) %}
|
||||
<a href="{{ url }}" class="layui-btn layui-btn-xs">{{ item.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="answer-anchor"></div>
|
||||
<div class="answer-wrap wrap">
|
||||
<div id="answer-list" data-url="{{ answer_list_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-sidebar">
|
||||
<div class="sidebar">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">关于作者</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="sidebar-user-card clearfix">
|
||||
<div class="avatar">
|
||||
<img src="{{ question.owner.avatar }}!avatar_160" alt="{{ question.owner.name }}">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ owner_url }}" title="{{ question.owner.about }}">{{ question.owner.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ question.owner.title|default('初出江湖') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if question.me.answered == 0 %}
|
||||
<div class="sidebar wrap">
|
||||
<button class="layui-btn layui-btn-fluid btn-answer" data-url="{{ add_answer_url }}">回答问题</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="sidebar" id="sidebar-related" data-url="{{ related_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set share_url = full_url({'for':'home.share'},{'id':question.id,'type':'question','referer':auth_user.id}) %}
|
||||
{% set qrcode_url = url({'for':'home.qrcode'},{'text':share_url}) %}
|
||||
|
||||
<div class="layui-hide">
|
||||
<input type="hidden" name="share.title" value="{{ question.title }}">
|
||||
<input type="hidden" name="share.pic" value="">
|
||||
<input type="hidden" name="share.url" value="{{ share_url }}">
|
||||
<input type="hidden" name="share.qrcode" value="{{ qrcode_url }}">
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block link_css %}
|
||||
|
||||
{{ css_link('home/css/markdown.css') }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('home/js/question.show.js') }}
|
||||
{{ js_include('home/js/question.share.js') }}
|
||||
{{ js_include('home/js/answer.js') }}
|
||||
|
||||
{% endblock %}
|
27
app/Http/Home/Views/question/sticky.volt
Normal file
27
app/Http/Home/Views/question/sticky.volt
Normal file
@ -0,0 +1,27 @@
|
||||
{% set favorite_url = url({'for':'home.question.favorite','id':question.id}) %}
|
||||
{% set like_url = url({'for':'home.question.like','id':question.id}) %}
|
||||
{% set favorite_title = question.me.favorited == 1 ? '取消收藏' : '收藏问题' %}
|
||||
{% set like_title = question.me.liked == 1 ? '取消点赞' : '点赞支持' %}
|
||||
{% set favorite_class = question.me.favorited == 1 ? 'layui-icon-star-fill' : 'layui-icon-star' %}
|
||||
{% set like_class = question.me.liked == 1 ? 'active' : '' %}
|
||||
|
||||
<div class="toolbar-sticky">
|
||||
<div class="item" id="toolbar-like">
|
||||
<div class="icon" title="{{ like_title }}" data-url="{{ like_url }}">
|
||||
<i class="layui-icon layui-icon-praise icon-praise {{ like_class }}"></i>
|
||||
</div>
|
||||
<div class="text" data-count="{{ question.like_count }}">{{ question.like_count }}</div>
|
||||
</div>
|
||||
<div class="item" id="toolbar-answer">
|
||||
<div class="icon" title="回答问题">
|
||||
<i class="layui-icon layui-icon-reply-fill icon-reply"></i>
|
||||
</div>
|
||||
<div class="text" data-count="{{ question.answer_count }}">{{ question.answer_count }}</div>
|
||||
</div>
|
||||
<div class="item" id="toolbar-favorite">
|
||||
<div class="icon" title="{{ favorite_title }}" data-url="{{ favorite_url }}">
|
||||
<i class="layui-icon layui-icon-star icon-star {{ favorite_class }}"></i>
|
||||
</div>
|
||||
<div class="text" data-count="{{ question.favorite_count }}">{{ question.favorite_count }}</div>
|
||||
</div>
|
||||
</div>
|
27
app/Http/Home/Views/question/top_answerers.volt
Normal file
27
app/Http/Home/Views/question/top_answerers.volt
Normal file
@ -0,0 +1,27 @@
|
||||
{% if questions|length > 0 %}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">热门问题</div>
|
||||
<div class="layui-card-body">
|
||||
{% for item in questions %}
|
||||
{% set url = url({'for':'home.question.show','id':item.id}) %}
|
||||
{% set rank = loop.index %}
|
||||
<div class="sidebar-rank-card">
|
||||
<div class="rank">
|
||||
{% if rank == 1 %}
|
||||
<span class="layui-badge layui-bg-red">{{ rank }}</span>
|
||||
{% elseif rank == 2 %}
|
||||
<span class="layui-badge layui-bg-blue">{{ rank }}</span>
|
||||
{% elseif rank == 3 %}
|
||||
<span class="layui-badge layui-bg-green">{{ rank }}</span>
|
||||
{% else %}
|
||||
<span class="layui-badge layui-bg-gray">{{ rank }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="title">
|
||||
<a href="{{ url }}">{{ item.title }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
@ -1,14 +1,9 @@
|
||||
{% if pager.total_pages > 0 %}
|
||||
<div class="search-course-list">
|
||||
<div class="search-article-list">
|
||||
{% for item in pager.items %}
|
||||
{% set article_url = url({'for':'home.article.show','id':item.id}) %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
<div class="search-course-card clearfix">
|
||||
<div class="cover">
|
||||
<a href="{{ article_url }}" target="_blank">
|
||||
<img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="search-article-card article-card">
|
||||
<div class="info">
|
||||
<div class="title layui-elip">
|
||||
<a href="{{ article_url }}" target="_blank">{{ item.title }}</a>
|
||||
@ -21,6 +16,13 @@
|
||||
<span>评论:{{ item.comment_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if item.cover %}
|
||||
<div class="cover">
|
||||
<a href="{{ article_url }}" target="_blank">
|
||||
<img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
{{ partial('macros/course') }}
|
||||
|
||||
{% set types = {'course':'课程','article':'专栏','group':'群组','user':'用户'} %}
|
||||
{% set types = {'course':'课程','article':'专栏','question':'问答','group':'群组','user':'用户'} %}
|
||||
{% set type = request.get('type','trim','course') %}
|
||||
{% set query = request.get('query','striptags','') %}
|
||||
|
||||
@ -35,6 +35,10 @@
|
||||
<div class="layui-tab-item layui-show">
|
||||
{{ partial('search/article') }}
|
||||
</div>
|
||||
{% elseif type == 'question' %}
|
||||
<div class="layui-tab-item layui-show">
|
||||
{{ partial('search/question') }}
|
||||
</div>
|
||||
{% elseif type == 'group' %}
|
||||
<div class="layui-tab-item layui-show">
|
||||
{{ partial('search/group') }}
|
||||
|
25
app/Http/Home/Views/search/question.volt
Normal file
25
app/Http/Home/Views/search/question.volt
Normal file
@ -0,0 +1,25 @@
|
||||
{% if pager.total_pages > 0 %}
|
||||
<div class="search-question-list">
|
||||
{% for item in pager.items %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
|
||||
{% set question_url = url({'for':'home.question.show','id':item.id}) %}
|
||||
{% set solved_class = item.solved ? 'column solved' : 'column' %}
|
||||
<div class="search-question-card article-card question-card">
|
||||
<div class="info">
|
||||
<div class="title layui-elip">
|
||||
<a href="{{ question_url }}" target="_blank">{{ item.title }}</a>
|
||||
</div>
|
||||
<div class="summary">{{ item.summary }}</div>
|
||||
<div class="meta">
|
||||
<span class="owner">提问:<a href="{{ owner_url }}">{{ item.owner.name }}</a></span>
|
||||
<span class="view">浏览:{{ item.view_count }}</span>
|
||||
<span class="like">点赞:{{ item.like_count }}</span>
|
||||
<span class="answer">回答:{{ item.answer_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ partial('search/empty') }}
|
||||
{% endif %}
|
38
app/Http/Home/Views/tag/list.volt
Normal file
38
app/Http/Home/Views/tag/list.volt
Normal file
@ -0,0 +1,38 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set list_pager_url = url({'for':'home.tag.list_pager'}) %}
|
||||
{% set my_pager_url = url({'for':'home.tag.my_pager'}) %}
|
||||
|
||||
<div class="layui-breadcrumb breadcrumb">
|
||||
<a href="/">首页</a>
|
||||
<a><cite>标签</cite></a>
|
||||
</div>
|
||||
|
||||
<div class="tab-wrap">
|
||||
<div class="layui-tab layui-tab-brief user-tab" lay-filter="tag">
|
||||
<ul class="layui-tab-title">
|
||||
<li class="layui-this">所有标签</li>
|
||||
<li>我的关注</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div id="all-tag-list" data-url="{{ list_pager_url }}"></div>
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{% if auth_user.id > 0 %}
|
||||
<div id="my-tag-list" data-url="{{ my_pager_url }}"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('home/js/tag.list.js') }}
|
||||
|
||||
{% endblock %}
|
23
app/Http/Home/Views/tag/list_pager.volt
Normal file
23
app/Http/Home/Views/tag/list_pager.volt
Normal file
@ -0,0 +1,23 @@
|
||||
{% if pager.total_pages > 0 %}
|
||||
<div class="tag-list clearfix">
|
||||
<div class="layui-row layui-col-space20">
|
||||
{% for item in pager.items %}
|
||||
{% set follow_url = url({'for':'home.tag.follow','id':item.id}) %}
|
||||
{% set follow_class = item.me.followed == 1 ? 'layui-btn layui-btn-sm followed btn-follow' : 'layui-btn layui-btn-primary layui-btn-sm btn-follow' %}
|
||||
{% set follow_text = item.me.followed == 1 ? '已关注' : '关注' %}
|
||||
<div class="layui-col-md3">
|
||||
<div class="tag-card">
|
||||
<div class="icon">
|
||||
<img src="{{ item.icon }}" alt="{{ item.name }}">
|
||||
</div>
|
||||
<div class="name">{{ item.name }}<span class="stats">({{ item.follow_count }} 关注 )</span></div>
|
||||
<div class="action">
|
||||
<span class="{{ follow_class }}" data-url="{{ follow_url }}">{{ follow_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{{ partial('partials/pager_ajax') }}
|
||||
{% endif %}
|
21
app/Http/Home/Views/tag/my_pager.volt
Normal file
21
app/Http/Home/Views/tag/my_pager.volt
Normal file
@ -0,0 +1,21 @@
|
||||
{% if pager.total_pages > 0 %}
|
||||
<div class="tag-list clearfix">
|
||||
<div class="layui-row layui-col-space20">
|
||||
{% for item in pager.items %}
|
||||
{% set follow_url = url({'for':'home.tag.follow','id':item.id}) %}
|
||||
<div class="layui-col-md3">
|
||||
<div class="tag-card">
|
||||
<div class="icon">
|
||||
<img src="{{ item.icon }}" alt="{{ item.name }}">
|
||||
</div>
|
||||
<div class="name">{{ item.name }}<span class="stats">({{ item.follow_count }} 关注 )</span></div>
|
||||
<div class="action">
|
||||
<span class="layui-btn layui-btn-sm followed btn-follow" data-url="{{ follow_url }}">已关注</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{{ partial('partials/pager_ajax') }}
|
||||
{% endif %}
|
65
app/Http/Home/Views/user/console/answers.volt
Normal file
65
app/Http/Home/Views/user/console/answers.volt
Normal file
@ -0,0 +1,65 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ partial('macros/answer') }}
|
||||
|
||||
{% set published_types = {'0':'全部','1':'审核中','2':'已发布','3':'未通过'} %}
|
||||
{% set published = request.get('published','trim','0') %}
|
||||
|
||||
<div class="layout-main clearfix">
|
||||
<div class="my-sidebar">{{ partial('user/console/menu') }}</div>
|
||||
<div class="my-content">
|
||||
<div class="wrap">
|
||||
<div class="my-nav">
|
||||
<span class="title">我的回答</span>
|
||||
{% for key,value in published_types %}
|
||||
{% set class = (published == key) ? 'layui-btn layui-btn-xs' : 'none' %}
|
||||
{% set url = (key == '0') ? url({'for':'home.uc.answers'}) : url({'for':'home.uc.answers'},{'published':key}) %}
|
||||
<a class="{{ class }}" href="{{ url }}">{{ value }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if pager.total_pages > 0 %}
|
||||
<table class="layui-table review-table">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col width="15%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<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 edit_url = url({'for':'home.review.edit','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'home.review.delete','id':item.id}) %}
|
||||
<tr>
|
||||
<td>
|
||||
<p>提问:<a href="{{ question_url }}" target="_blank">{{ item.question.title }}</a></p>
|
||||
<p>回答:{{ substr(item.summary,0,32) }}</p>
|
||||
<p class="meta">
|
||||
创建:<span class="layui-badge layui-bg-gray">{{ item.create_time|time_ago }}</span>
|
||||
状态:<span class="layui-badge layui-bg-gray">{{ publish_status(item.published) }}</span>
|
||||
</p>
|
||||
</td>
|
||||
<td>{{ item.like_count }}</td>
|
||||
<td>
|
||||
<button class="layui-btn layui-btn-xs btn-edit-review" data-url="{{ edit_url }}">修改</button>
|
||||
<button class="layui-btn layui-btn-xs layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ partial('partials/pager') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -18,18 +18,28 @@
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">内容中心</div>
|
||||
<div class="layui-card-header">课程中心</div>
|
||||
<div class="layui-card-body">
|
||||
<ul class="my-menu">
|
||||
<li><a href="{{ url({'for':'home.uc.courses'}) }}">我的课程</a></li>
|
||||
<li><a href="{{ url({'for':'home.uc.articles'}) }}">我的文章</a></li>
|
||||
<li><a href="{{ url({'for':'home.uc.favorites'}) }}">我的收藏</a></li>
|
||||
<li><a href="{{ url({'for':'home.uc.reviews'}) }}">我的评价</a></li>
|
||||
<li><a href="{{ url({'for':'home.uc.consults'}) }}">我的咨询</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">创作中心</div>
|
||||
<div class="layui-card-body">
|
||||
<ul class="my-menu">
|
||||
<li><a href="{{ url({'for':'home.uc.articles'}) }}">我的文章</a></li>
|
||||
<li><a href="{{ url({'for':'home.uc.questions'}) }}">我的提问</a></li>
|
||||
<li><a href="{{ url({'for':'home.uc.answers'}) }}">我的回答</a></li>
|
||||
<li><a href="{{ url({'for':'home.uc.favorites'}) }}">我的收藏</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">订单中心</div>
|
||||
<div class="layui-card-body">
|
||||
|
73
app/Http/Home/Views/user/console/questions.volt
Normal file
73
app/Http/Home/Views/user/console/questions.volt
Normal file
@ -0,0 +1,73 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ partial('macros/question') }}
|
||||
|
||||
{% set published_types = {'0':'全部','1':'审核中','2':'已发布','3':'未通过'} %}
|
||||
{% set published = request.get('published','trim','0') %}
|
||||
|
||||
<div class="layout-main clearfix">
|
||||
<div class="my-sidebar">{{ partial('user/console/menu') }}</div>
|
||||
<div class="my-content">
|
||||
<div class="wrap">
|
||||
<div class="my-nav">
|
||||
<span class="title">我的提问</span>
|
||||
{% for key,value in published_types %}
|
||||
{% set class = (published == key) ? 'layui-btn layui-btn-xs' : 'none' %}
|
||||
{% set url = (key == '0') ? url({'for':'home.uc.questions'}) : url({'for':'home.uc.questions'},{'published':key}) %}
|
||||
<a class="{{ class }}" href="{{ url }}">{{ value }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if pager.total_pages > 0 %}
|
||||
<table class="layui-table">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>问题</th>
|
||||
<th>回答</th>
|
||||
<th>浏览</th>
|
||||
<th>点赞</th>
|
||||
<th>收藏</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in pager.items %}
|
||||
{% set show_url = url({'for':'home.question.show','id':item.id}) %}
|
||||
{% set edit_url = url({'for':'home.question.edit','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'home.question.delete','id':item.id}) %}
|
||||
<tr>
|
||||
<td>
|
||||
<p>标题:<a href="{{ show_url }}" target="_blank">{{ item.title }}</a></p>
|
||||
<p class="meta">
|
||||
创建:<span class="layui-badge layui-bg-gray">{{ item.create_time|time_ago }}</span>
|
||||
状态:<span class="layui-badge layui-bg-gray">{{ publish_status(item.published) }}</span>
|
||||
</p>
|
||||
</td>
|
||||
<td>{{ item.answer_count }}</td>
|
||||
<td>{{ item.view_count }}</td>
|
||||
<td>{{ item.like_count }}</td>
|
||||
<td>{{ item.favorite_count }}</td>
|
||||
<td class="center">
|
||||
<a href="{{ edit_url }}" class="layui-btn layui-btn-xs">编辑</a>
|
||||
<a href="javascript:" class="layui-btn layui-btn-xs layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ partial('partials/pager') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
20
app/Http/Home/Views/widget/my_tags.volt
Normal file
20
app/Http/Home/Views/widget/my_tags.volt
Normal file
@ -0,0 +1,20 @@
|
||||
{% set type = request.get('type','string','article') %}
|
||||
|
||||
{% if type == 'article' %}
|
||||
{% set base_url = url({'for':'home.article.list'}) %}
|
||||
{% elseif type == 'question' %}
|
||||
{% set base_url = url({'for':'home.question.list'}) %}
|
||||
{% endif %}
|
||||
|
||||
<div class="layui-card widget-card">
|
||||
<div class="more">
|
||||
<a href="{{ url({'for':'home.tag.list'}) }}">管理</a>
|
||||
</div>
|
||||
<div class="layui-card-header">关注标签</div>
|
||||
<div class="layui-card-body">
|
||||
{% for item in tags %}
|
||||
{% set tagged_url = base_url ~ '?tag_id=' ~ item.id %}
|
||||
<a class="layui-badge-rim tag-badge" href="{{ tagged_url }}">{{ item.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
@ -263,6 +263,16 @@ function kg_default_slide_cover_path()
|
||||
return '/img/default/course_cover.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认图标路径
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function kg_default_icon_path()
|
||||
{
|
||||
return '/img/default/user_avatar.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储基准URL
|
||||
*
|
||||
@ -405,6 +415,20 @@ function kg_cos_slide_cover_url($path, $style = null)
|
||||
return kg_cos_img_url($path, $style);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图标URL
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $style
|
||||
* @return string
|
||||
*/
|
||||
function kg_cos_icon_url($path, $style = null)
|
||||
{
|
||||
$path = $path ?: kg_default_icon_path();
|
||||
|
||||
return kg_cos_img_url($path, $style);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除存储图片处理样式
|
||||
*
|
||||
@ -452,6 +476,26 @@ function kg_parse_summary($content, $length = 100)
|
||||
return kg_substr($content, 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析内容中上传首图
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
function kg_parse_first_content_image($content)
|
||||
{
|
||||
$result = '';
|
||||
|
||||
$matched = preg_match('/\((.*?)\/img\/content\/(.*?)\)/', $content, $matches);
|
||||
|
||||
if ($matched) {
|
||||
$url = sprintf('%s/img/content/%s', trim($matches[1]), trim($matches[2]));
|
||||
$result = kg_cos_img_style_trim($url);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏部分字符
|
||||
*
|
||||
|
41
app/Listeners/Answer.php
Normal file
41
app/Listeners/Answer.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Models\Answer as AnswerModel;
|
||||
use Phalcon\Events\Event as PhEvent;
|
||||
|
||||
class Answer extends Listener
|
||||
{
|
||||
|
||||
public function afterCreate(PhEvent $event, $source, AnswerModel $answer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterUpdate(PhEvent $event, $source, AnswerModel $answer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterDelete(PhEvent $event, $source, AnswerModel $answer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterRestore(PhEvent $event, $source, AnswerModel $answer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterLike(PhEvent $event, $source, AnswerModel $answer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterUndoLike(PhEvent $event, $source, AnswerModel $answer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
66
app/Listeners/Question.php
Normal file
66
app/Listeners/Question.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Models\Question as QuestionModel;
|
||||
use Phalcon\Events\Event as PhEvent;
|
||||
|
||||
class Question extends Listener
|
||||
{
|
||||
|
||||
public function afterCreate(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterUpdate(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterDelete(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterRestore(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterApprove(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterReject(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterView(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterFavorite(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterUndoFavorite(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterLike(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function afterUndoLike(PhEvent $event, $source, QuestionModel $question)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -66,6 +66,16 @@ class UserDailyCounter extends Listener
|
||||
$this->counter->hIncrBy($user->id, 'article_like_count');
|
||||
}
|
||||
|
||||
public function incrQuestionLikeCount(PhEvent $event, $source, UserModel $user)
|
||||
{
|
||||
$this->counter->hIncrBy($user->id, 'question_like_count');
|
||||
}
|
||||
|
||||
public function incrAnswerLikeCount(PhEvent $event, $source, UserModel $user)
|
||||
{
|
||||
$this->counter->hIncrBy($user->id, 'answer_like_count');
|
||||
}
|
||||
|
||||
public function incrCommentLikeCount(PhEvent $event, $source, UserModel $user)
|
||||
{
|
||||
$this->counter->hIncrBy($user->id, 'comment_like_count');
|
||||
|
181
app/Models/Answer.php
Normal file
181
app/Models/Answer.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Caches\MaxAnswerId as MaxAnswerIdCache;
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
|
||||
class Answer extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
const PUBLISH_PENDING = 1; // 审核中
|
||||
const PUBLISH_APPROVED = 2; // 已发布
|
||||
const PUBLISH_REJECTED = 3; // 未通过
|
||||
|
||||
/**
|
||||
* 自增编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $owner_id = 0;
|
||||
|
||||
/**
|
||||
* 问题编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $question_id = 0;
|
||||
|
||||
/**
|
||||
* 封面
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $cover = '';
|
||||
|
||||
/**
|
||||
* 内容
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* 匿名标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $anonymous = 0;
|
||||
|
||||
/**
|
||||
* 采纳标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $accepted = 0;
|
||||
|
||||
/**
|
||||
* 状态标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $published = self::PUBLISH_PENDING;
|
||||
|
||||
/**
|
||||
* 删除标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $deleted = 0;
|
||||
|
||||
/**
|
||||
* 终端类型
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $client_type = 0;
|
||||
|
||||
/**
|
||||
* 终端IP
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $client_ip = '';
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $comment_count = 0;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $like_count = 0;
|
||||
|
||||
/**
|
||||
* 举报数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $report_count = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $create_time = 0;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $update_time = 0;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_answer';
|
||||
}
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
$this->addBehavior(
|
||||
new SoftDelete([
|
||||
'field' => 'deleted',
|
||||
'value' => 1,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
if (empty($this->cover)) {
|
||||
$this->cover = kg_parse_first_content_image($this->content);
|
||||
}
|
||||
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
public function beforeUpdate()
|
||||
{
|
||||
if (empty($this->cover)) {
|
||||
$this->cover = kg_parse_first_content_image($this->content);
|
||||
}
|
||||
|
||||
$this->update_time = time();
|
||||
}
|
||||
|
||||
public function afterCreate()
|
||||
{
|
||||
$cache = new MaxAnswerIdCache();
|
||||
|
||||
$cache->rebuild();
|
||||
}
|
||||
|
||||
public static function publishTypes()
|
||||
{
|
||||
return [
|
||||
self::PUBLISH_PENDING => '审核中',
|
||||
self::PUBLISH_APPROVED => '已发布',
|
||||
self::PUBLISH_REJECTED => '未通过',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
46
app/Models/AnswerLike.php
Normal file
46
app/Models/AnswerLike.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class AnswerLike extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* 回答编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $answer_id = 0;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $user_id = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $create_time = 0;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_answer_like';
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
}
|
@ -4,8 +4,8 @@ namespace App\Models;
|
||||
|
||||
use App\Caches\MaxArticleId as MaxArticleIdCache;
|
||||
use App\Services\Sync\ArticleIndex as ArticleIndexSync;
|
||||
use App\Services\Sync\ArticleScore as ArticleScoreSync;
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
use Phalcon\Text;
|
||||
|
||||
class Article extends Model
|
||||
{
|
||||
@ -108,6 +108,13 @@ class Article extends Model
|
||||
*/
|
||||
public $client_ip = '';
|
||||
|
||||
/**
|
||||
* 综合得分
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
public $score = 0.00;
|
||||
|
||||
/**
|
||||
* 私有标识
|
||||
*
|
||||
@ -164,6 +171,13 @@ class Article extends Model
|
||||
*/
|
||||
public $comment_count = 0;
|
||||
|
||||
/**
|
||||
* 收藏数
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $favorite_count = 0;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*
|
||||
@ -172,11 +186,11 @@ class Article extends Model
|
||||
public $like_count = 0;
|
||||
|
||||
/**
|
||||
* 收藏数
|
||||
* 举报数
|
||||
*
|
||||
* @var int
|
||||
* @var integer
|
||||
*/
|
||||
public $favorite_count = 0;
|
||||
public $report_count;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
@ -201,8 +215,6 @@ class Article extends Model
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
$this->keepSnapshots(true);
|
||||
|
||||
$this->addBehavior(
|
||||
new SoftDelete([
|
||||
'field' => 'deleted',
|
||||
@ -214,9 +226,7 @@ class Article extends Model
|
||||
public function beforeCreate()
|
||||
{
|
||||
if (empty($this->cover)) {
|
||||
$this->cover = kg_default_article_cover_path();
|
||||
} elseif (Text::startsWith($this->cover, 'http')) {
|
||||
$this->cover = self::getCoverPath($this->cover);
|
||||
$this->cover = kg_parse_first_content_image($this->content);
|
||||
}
|
||||
|
||||
if (is_array($this->tags) || is_object($this->tags)) {
|
||||
@ -231,10 +241,13 @@ class Article extends Model
|
||||
if (time() - $this->update_time > 3 * 3600) {
|
||||
$sync = new ArticleIndexSync();
|
||||
$sync->addItem($this->id);
|
||||
|
||||
$sync = new ArticleScoreSync();
|
||||
$sync->addItem($this->id);
|
||||
}
|
||||
|
||||
if (Text::startsWith($this->cover, 'http')) {
|
||||
$this->cover = self::getCoverPath($this->cover);
|
||||
if (empty($this->cover)) {
|
||||
$this->cover = kg_parse_first_content_image($this->content);
|
||||
}
|
||||
|
||||
if (empty($this->summary)) {
|
||||
@ -257,24 +270,11 @@ class Article extends Model
|
||||
|
||||
public function afterFetch()
|
||||
{
|
||||
if (!Text::startsWith($this->cover, 'http')) {
|
||||
$this->cover = kg_cos_article_cover_url($this->cover);
|
||||
}
|
||||
|
||||
if (is_string($this->tags)) {
|
||||
$this->tags = json_decode($this->tags, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCoverPath($url)
|
||||
{
|
||||
if (Text::startsWith($url, 'http')) {
|
||||
return parse_url($url, PHP_URL_PATH);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function sourceTypes()
|
||||
{
|
||||
return [
|
||||
|
@ -14,6 +14,7 @@ class Category extends Model
|
||||
const TYPE_COURSE = 1; // 课程
|
||||
const TYPE_HELP = 2; // 帮助
|
||||
const TYPE_ARTICLE = 3; // 文章
|
||||
const TYPE_QUESTION = 4; // 问答
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
@ -143,6 +144,7 @@ class Category extends Model
|
||||
self::TYPE_COURSE => '课程',
|
||||
self::TYPE_HELP => '帮助',
|
||||
self::TYPE_ARTICLE => '专栏',
|
||||
self::TYPE_QUESTION => '问答',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -114,6 +114,13 @@ class Comment extends Model
|
||||
*/
|
||||
public $like_count = 0;
|
||||
|
||||
/**
|
||||
* 举报数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $report_count;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
|
@ -119,6 +119,13 @@ class Consult extends Model
|
||||
*/
|
||||
public $like_count = 0;
|
||||
|
||||
/**
|
||||
* 举报数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $report_count;
|
||||
|
||||
/**
|
||||
* 回复时间
|
||||
*
|
||||
|
@ -287,8 +287,6 @@ class Course extends Model
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
$this->keepSnapshots(true);
|
||||
|
||||
$this->addBehavior(
|
||||
new SoftDelete([
|
||||
'field' => 'deleted',
|
||||
|
@ -80,7 +80,7 @@ class Nav extends Model
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $priority = 100;
|
||||
public $priority = 99;
|
||||
|
||||
/**
|
||||
* 发布标识
|
||||
|
@ -20,6 +20,7 @@ class PointHistory extends Model
|
||||
const EVENT_ARTICLE_POST = 10; // 发布文章
|
||||
const EVENT_QUESTION_POST = 11; // 发布问题
|
||||
const EVENT_ANSWER_POST = 12; // 发布答案
|
||||
const EVENT_ANSWER_ACCEPT = 13; // 采纳答案
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
|
310
app/Models/Question.php
Normal file
310
app/Models/Question.php
Normal file
@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Caches\MaxQuestionId as MaxQuestionIdCache;
|
||||
use App\Services\Sync\QuestionIndex as QuestionIndexSync;
|
||||
use App\Services\Sync\QuestionScore as QuestionScoreSync;
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
|
||||
class Question extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
const PUBLISH_PENDING = 1; // 审核中
|
||||
const PUBLISH_APPROVED = 2; // 已发布
|
||||
const PUBLISH_REJECTED = 3; // 未通过
|
||||
|
||||
/**
|
||||
* 自增编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* 分类编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $category_id = 0;
|
||||
|
||||
/**
|
||||
* 提问者
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $owner_id = 0;
|
||||
|
||||
/**
|
||||
* 最后回应用户
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $last_replier_id = 0;
|
||||
|
||||
/**
|
||||
* 最后回答编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $last_answer_id = 0;
|
||||
|
||||
/**
|
||||
* 采纳答案编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $accept_answer_id = 0;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* 封面
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $cover = '';
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*
|
||||
* @var array|string
|
||||
*/
|
||||
public $tags = [];
|
||||
|
||||
/**
|
||||
* 概要
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $summary = '';
|
||||
|
||||
/**
|
||||
* 内容
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* 综合得分
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
public $score = 0.00;
|
||||
|
||||
/**
|
||||
* 悬赏积分
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $bounty = 0;
|
||||
|
||||
/**
|
||||
* 匿名标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $anonymous = 0;
|
||||
|
||||
/**
|
||||
* 解决标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $solved = 0;
|
||||
|
||||
/**
|
||||
* 关闭标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $closed = 0;
|
||||
|
||||
/**
|
||||
* 状态标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $published = self::PUBLISH_PENDING;
|
||||
|
||||
/**
|
||||
* 删除标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $deleted = 0;
|
||||
|
||||
/**
|
||||
* 终端类型
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $client_type = 0;
|
||||
|
||||
/**
|
||||
* 终端IP
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $client_ip = '';
|
||||
|
||||
/**
|
||||
* 浏览数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $view_count = 0;
|
||||
|
||||
/**
|
||||
* 答案数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $answer_count = 0;
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $comment_count = 0;
|
||||
|
||||
/**
|
||||
* 收藏数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $favorite_count = 0;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $like_count = 0;
|
||||
|
||||
/**
|
||||
* 举报数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $report_count = 0;
|
||||
|
||||
/**
|
||||
* 回应时间
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $last_reply_time = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $create_time = 0;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $update_time = 0;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_question';
|
||||
}
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
$this->addBehavior(
|
||||
new SoftDelete([
|
||||
'field' => 'deleted',
|
||||
'value' => 1,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
if (is_array($this->tags) || is_object($this->tags)) {
|
||||
$this->tags = kg_json_encode($this->tags);
|
||||
}
|
||||
|
||||
if (empty($this->cover)) {
|
||||
$this->cover = kg_parse_first_content_image($this->content);
|
||||
}
|
||||
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
public function beforeUpdate()
|
||||
{
|
||||
if (time() - $this->update_time > 3 * 3600) {
|
||||
$sync = new QuestionIndexSync();
|
||||
$sync->addItem($this->id);
|
||||
|
||||
$sync = new QuestionScoreSync();
|
||||
$sync->addItem($this->id);
|
||||
}
|
||||
|
||||
if (is_array($this->tags) || is_object($this->tags)) {
|
||||
$this->tags = kg_json_encode($this->tags);
|
||||
}
|
||||
|
||||
if (empty($this->cover)) {
|
||||
$this->cover = kg_parse_first_content_image($this->content);
|
||||
}
|
||||
|
||||
if (empty($this->summary)) {
|
||||
$this->summary = kg_parse_summary($this->content);
|
||||
}
|
||||
|
||||
$this->update_time = time();
|
||||
}
|
||||
|
||||
public function afterCreate()
|
||||
{
|
||||
$cache = new MaxQuestionIdCache();
|
||||
|
||||
$cache->rebuild();
|
||||
}
|
||||
|
||||
public function afterFetch()
|
||||
{
|
||||
if (is_string($this->tags)) {
|
||||
$this->tags = json_decode($this->tags, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static function publishTypes()
|
||||
{
|
||||
return [
|
||||
self::PUBLISH_PENDING => '审核中',
|
||||
self::PUBLISH_APPROVED => '已发布',
|
||||
self::PUBLISH_REJECTED => '未通过',
|
||||
];
|
||||
}
|
||||
|
||||
public static function sortTypes()
|
||||
{
|
||||
return [
|
||||
'latest' => '最新提问',
|
||||
'active' => '最新回答',
|
||||
'unanswered' => '尚未回答',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
46
app/Models/QuestionFavorite.php
Normal file
46
app/Models/QuestionFavorite.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class QuestionFavorite extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* 问题编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $question_id = 0;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $user_id = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $create_time = 0;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_question_favorite';
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
}
|
46
app/Models/QuestionLike.php
Normal file
46
app/Models/QuestionLike.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class QuestionLike extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* 问题编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $question_id = 0;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $user_id = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $create_time = 0;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_question_like';
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
}
|
46
app/Models/QuestionTag.php
Normal file
46
app/Models/QuestionTag.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class QuestionTag extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* 问题编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $question_id = 0;
|
||||
|
||||
/**
|
||||
* 标签编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tag_id = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $create_time = 0;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_question_tag';
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
}
|
100
app/Models/Report.php
Normal file
100
app/Models/Report.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Report extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 条目类型
|
||||
*/
|
||||
const ITEM_USER = 100; // 用户
|
||||
const ITEM_GROUP = 101; // 小组
|
||||
const ITEM_COURSE = 102; // 课程
|
||||
const ITEM_CHAPTER = 103; // 章节
|
||||
const ITEM_CONSULT = 104; // 咨询
|
||||
const ITEM_REVIEW = 105; // 评价
|
||||
const ITEM_ARTICLE = 106; // 文章
|
||||
const ITEM_QUESTION = 107; // 问题
|
||||
const ITEM_ANSWER = 108; // 答案
|
||||
const ITEM_COMMENT = 109; // 评论
|
||||
|
||||
/**
|
||||
* 自增编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $owner_id;
|
||||
|
||||
/**
|
||||
* 条目编号
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $item_id;
|
||||
|
||||
/**
|
||||
* 条目类型
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $item_type;
|
||||
|
||||
/**
|
||||
* 举报理由
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $reason;
|
||||
|
||||
/**
|
||||
* 处理状态
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $reviewed;
|
||||
|
||||
/**
|
||||
* 采纳标识
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $accepted;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $create_time;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $update_time;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_report';
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
public function beforeUpdate()
|
||||
{
|
||||
$this->update_time = time();
|
||||
}
|
||||
|
||||
}
|
@ -112,6 +112,13 @@ class Review extends Model
|
||||
*/
|
||||
public $like_count = 0;
|
||||
|
||||
/**
|
||||
* 举报数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $report_count;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
|
@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use App\Caches\MaxTagId as MaxTagIdCache;
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
use Phalcon\Text;
|
||||
|
||||
class Tag extends Model
|
||||
{
|
||||
@ -97,11 +98,21 @@ class Tag extends Model
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
if (empty($this->icon)) {
|
||||
$this->icon = kg_default_icon_path();
|
||||
} elseif (Text::startsWith($this->icon, 'http')) {
|
||||
$this->icon = self::getIconPath($this->icon);
|
||||
}
|
||||
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
public function beforeUpdate()
|
||||
{
|
||||
if (Text::startsWith($this->icon, 'http')) {
|
||||
$this->icon = self::getIconPath($this->icon);
|
||||
}
|
||||
|
||||
if ($this->deleted == 1) {
|
||||
$this->published = 0;
|
||||
}
|
||||
@ -116,4 +127,20 @@ class Tag extends Model
|
||||
$cache->rebuild();
|
||||
}
|
||||
|
||||
public function afterFetch()
|
||||
{
|
||||
if (!Text::startsWith($this->icon, 'http')) {
|
||||
$this->icon = kg_cos_icon_url($this->icon);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getIconPath($url)
|
||||
{
|
||||
if (Text::startsWith($url, 'http')) {
|
||||
return parse_url($url, PHP_URL_PATH);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
}
|
46
app/Models/TagFollow.php
Normal file
46
app/Models/TagFollow.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class TagFollow extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* 标签编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tag_id = 0;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $user_id = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $create_time = 0;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_tag_follow';
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
}
|
@ -122,6 +122,20 @@ class User extends Model
|
||||
*/
|
||||
public $article_count;
|
||||
|
||||
/**
|
||||
* 提问数
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $question_count;
|
||||
|
||||
/**
|
||||
* 回答数
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $answer_count;
|
||||
|
||||
/**
|
||||
* 收藏数
|
||||
*
|
||||
|
104
app/Repos/Answer.php
Normal file
104
app/Repos/Answer.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
|
||||
use App\Models\Answer as AnswerModel;
|
||||
use App\Models\AnswerLike as AnswerLikeModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class Answer extends Repository
|
||||
{
|
||||
|
||||
public function paginate($where = [], $sort = 'accepted', $page = 1, $limit = 15)
|
||||
{
|
||||
$builder = $this->modelsManager->createBuilder();
|
||||
|
||||
$builder->from(AnswerModel::class);
|
||||
|
||||
$builder->where('1 = 1');
|
||||
|
||||
if (!empty($where['id'])) {
|
||||
$builder->andWhere('id = :id:', ['id' => $where['id']]);
|
||||
}
|
||||
|
||||
if (!empty($where['owner_id'])) {
|
||||
$builder->andWhere('owner_id = :owner_id:', ['owner_id' => $where['owner_id']]);
|
||||
}
|
||||
|
||||
if (!empty($where['question_id'])) {
|
||||
$builder->andWhere('question_id = :question_id:', ['question_id' => $where['question_id']]);
|
||||
}
|
||||
|
||||
if (isset($where['published'])) {
|
||||
$builder->andWhere('published = :published:', ['published' => $where['published']]);
|
||||
}
|
||||
|
||||
if (isset($where['deleted'])) {
|
||||
$builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
|
||||
}
|
||||
|
||||
switch ($sort) {
|
||||
case 'like':
|
||||
$orderBy = 'like_count DESC';
|
||||
break;
|
||||
case 'latest':
|
||||
$orderBy = 'id DESC';
|
||||
break;
|
||||
default:
|
||||
$orderBy = 'accepted DESC, like_count DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
$builder->orderBy($orderBy);
|
||||
|
||||
$pager = new PagerQueryBuilder([
|
||||
'builder' => $builder,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
|
||||
return $pager->paginate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return AnswerModel|Model|bool
|
||||
*/
|
||||
public function findById($id)
|
||||
{
|
||||
return AnswerModel::findFirst([
|
||||
'conditions' => 'id = :id:',
|
||||
'bind' => ['id' => $id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ids
|
||||
* @param array|string $columns
|
||||
* @return ResultsetInterface|Resultset|AnswerModel[]
|
||||
*/
|
||||
public function findByIds($ids, $columns = '*')
|
||||
{
|
||||
return AnswerModel::query()
|
||||
->columns($columns)
|
||||
->inWhere('id', $ids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function countAnswers()
|
||||
{
|
||||
return (int)AnswerModel::count(['conditions' => 'deleted = 0']);
|
||||
}
|
||||
|
||||
public function countLikes($answerId)
|
||||
{
|
||||
return (int)AnswerLikeModel::count([
|
||||
'conditions' => 'answer_id = :answer_id:',
|
||||
'bind' => ['answer_id' => $answerId],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
37
app/Repos/AnswerLike.php
Normal file
37
app/Repos/AnswerLike.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Models\AnswerLike as AnswerLikeModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class AnswerLike extends Repository
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $answerId
|
||||
* @param int $userId
|
||||
* @return AnswerLikeModel|Model|bool
|
||||
*/
|
||||
public function findAnswerLike($answerId, $userId)
|
||||
{
|
||||
return AnswerLikeModel::findFirst([
|
||||
'conditions' => 'answer_id = :answer_id: AND user_id = :user_id:',
|
||||
'bind' => ['answer_id' => $answerId, 'user_id' => $userId],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @return ResultsetInterface|Resultset|AnswerLikeModel[]
|
||||
*/
|
||||
public function findByUserId($userId)
|
||||
{
|
||||
return AnswerLikeModel::query()
|
||||
->where('user_id = :user_id:', ['user_id' => $userId])
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
202
app/Repos/Question.php
Normal file
202
app/Repos/Question.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
|
||||
use App\Models\Answer as AnswerModel;
|
||||
use App\Models\Comment as CommentModel;
|
||||
use App\Models\Question as QuestionModel;
|
||||
use App\Models\QuestionFavorite as QuestionFavoriteModel;
|
||||
use App\Models\QuestionLike as QuestionLikeModel;
|
||||
use App\Models\QuestionTag as QuestionTagModel;
|
||||
use App\Models\Tag as TagModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class Question extends Repository
|
||||
{
|
||||
|
||||
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
|
||||
{
|
||||
$builder = $this->modelsManager->createBuilder();
|
||||
|
||||
$builder->from(QuestionModel::class);
|
||||
|
||||
$builder->where('1 = 1');
|
||||
|
||||
if (!empty($where['tag_id'])) {
|
||||
$where['id'] = $this->getTagQuestionIds($where['tag_id']);
|
||||
}
|
||||
|
||||
if (!empty($where['id'])) {
|
||||
if (is_array($where['id'])) {
|
||||
$builder->inWhere('id', $where['id']);
|
||||
} else {
|
||||
$builder->andWhere('id = :id:', ['id' => $where['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($where['owner_id'])) {
|
||||
$builder->andWhere('owner_id = :owner_id:', ['owner_id' => $where['owner_id']]);
|
||||
}
|
||||
|
||||
if (!empty($where['title'])) {
|
||||
$builder->andWhere('title LIKE :title:', ['title' => "%{$where['title']}%"]);
|
||||
}
|
||||
|
||||
if (isset($where['anonymous'])) {
|
||||
$builder->andWhere('anonymous = :anonymous:', ['anonymous' => $where['anonymous']]);
|
||||
}
|
||||
|
||||
if (isset($where['closed'])) {
|
||||
$builder->andWhere('closed = :closed:', ['closed' => $where['closed']]);
|
||||
}
|
||||
|
||||
if (isset($where['solved'])) {
|
||||
$builder->andWhere('solved = :solved:', ['solved' => $where['solved']]);
|
||||
}
|
||||
|
||||
if (isset($where['published'])) {
|
||||
$builder->andWhere('published = :published:', ['published' => $where['published']]);
|
||||
}
|
||||
|
||||
if (isset($where['deleted'])) {
|
||||
$builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
|
||||
}
|
||||
|
||||
if ($sort == 'unanswered') {
|
||||
$builder->andWhere('answer_count = 0');
|
||||
}
|
||||
|
||||
switch ($sort) {
|
||||
case 'active':
|
||||
$orderBy = 'last_reply_time DESC';
|
||||
break;
|
||||
case 'score':
|
||||
$orderBy = 'score DESC';
|
||||
break;
|
||||
default:
|
||||
$orderBy = 'id DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
$builder->orderBy($orderBy);
|
||||
|
||||
$pager = new PagerQueryBuilder([
|
||||
'builder' => $builder,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
|
||||
return $pager->paginate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return QuestionModel|Model|bool
|
||||
*/
|
||||
public function findById($id)
|
||||
{
|
||||
return QuestionModel::findFirst([
|
||||
'conditions' => 'id = :id:',
|
||||
'bind' => ['id' => $id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ids
|
||||
* @param array|string $columns
|
||||
* @return ResultsetInterface|Resultset|QuestionModel[]
|
||||
*/
|
||||
public function findByIds($ids, $columns = '*')
|
||||
{
|
||||
return QuestionModel::query()
|
||||
->columns($columns)
|
||||
->inWhere('id', $ids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $questionId
|
||||
* @return ResultsetInterface|Resultset|TagModel[]
|
||||
*/
|
||||
public function findTags($questionId)
|
||||
{
|
||||
return $this->modelsManager->createBuilder()
|
||||
->columns('t.*')
|
||||
->addFrom(TagModel::class, 't')
|
||||
->join(QuestionTagModel::class, 't.id = qt.tag_id', 'qt')
|
||||
->where('qt.question_id = :question_id:', ['question_id' => $questionId])
|
||||
->andWhere('t.published = 1')
|
||||
->getQuery()->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $questionId
|
||||
* @param int $userId
|
||||
* @return ResultsetInterface|Resultset|AnswerModel[]
|
||||
*/
|
||||
public function findUserAnswers($questionId, $userId)
|
||||
{
|
||||
return AnswerModel::query()
|
||||
->where('question_id = :question_id:', ['question_id' => $questionId])
|
||||
->andWhere('owner_id = :owner_id:', ['owner_id' => $userId])
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function countQuestions()
|
||||
{
|
||||
return (int)QuestionModel::count(['conditions' => 'deleted = 0']);
|
||||
}
|
||||
|
||||
public function countAnswers($questionId)
|
||||
{
|
||||
return (int)AnswerModel::count([
|
||||
'conditions' => 'question_id = :question_id: AND deleted = 0',
|
||||
'bind' => ['question_id' => $questionId],
|
||||
]);
|
||||
}
|
||||
|
||||
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],
|
||||
]);
|
||||
}
|
||||
|
||||
public function countFavorites($questionId)
|
||||
{
|
||||
return (int)QuestionFavoriteModel::count([
|
||||
'conditions' => 'question_id = :question_id:',
|
||||
'bind' => ['question_id' => $questionId],
|
||||
]);
|
||||
}
|
||||
|
||||
public function countLikes($questionId)
|
||||
{
|
||||
return (int)QuestionLikeModel::count([
|
||||
'conditions' => 'question_id = :question_id:',
|
||||
'bind' => ['question_id' => $questionId],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getTagQuestionIds($tagId)
|
||||
{
|
||||
$tagIds = is_array($tagId) ? $tagId : [$tagId];
|
||||
|
||||
$repo = new QuestionTag();
|
||||
|
||||
$rows = $repo->findByTagIds($tagIds);
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($rows->count() > 0) {
|
||||
$result = kg_array_column($rows->toArray(), 'question_id');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
58
app/Repos/QuestionFavorite.php
Normal file
58
app/Repos/QuestionFavorite.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
|
||||
use App\Models\QuestionFavorite as QuestionFavoriteModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
|
||||
class QuestionFavorite extends Repository
|
||||
{
|
||||
|
||||
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
|
||||
{
|
||||
$builder = $this->modelsManager->createBuilder();
|
||||
|
||||
$builder->from(QuestionFavoriteModel::class);
|
||||
|
||||
$builder->where('1 = 1');
|
||||
|
||||
if (!empty($where['question_id'])) {
|
||||
$builder->andWhere('question_id = :question_id:', ['question_id' => $where['question_id']]);
|
||||
}
|
||||
|
||||
if (!empty($where['user_id'])) {
|
||||
$builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
|
||||
}
|
||||
|
||||
switch ($sort) {
|
||||
default:
|
||||
$orderBy = 'id DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
$builder->orderBy($orderBy);
|
||||
|
||||
$pager = new PagerQueryBuilder([
|
||||
'builder' => $builder,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
|
||||
return $pager->paginate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $questionId
|
||||
* @param int $userId
|
||||
* @return QuestionFavoriteModel|Model|bool
|
||||
*/
|
||||
public function findQuestionFavorite($questionId, $userId)
|
||||
{
|
||||
return QuestionFavoriteModel::findFirst([
|
||||
'conditions' => 'question_id = :question_id: AND user_id = :user_id:',
|
||||
'bind' => ['question_id' => $questionId, 'user_id' => $userId],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
24
app/Repos/QuestionLike.php
Normal file
24
app/Repos/QuestionLike.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Models\QuestionLike as QuestionLikeModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
|
||||
class QuestionLike extends Repository
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $questionId
|
||||
* @param int $userId
|
||||
* @return QuestionLikeModel|Model|bool
|
||||
*/
|
||||
public function findQuestionLike($questionId, $userId)
|
||||
{
|
||||
return QuestionLikeModel::findFirst([
|
||||
'conditions' => 'question_id = :question_id: AND user_id = :user_id:',
|
||||
'bind' => ['question_id' => $questionId, 'user_id' => $userId],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
48
app/Repos/QuestionTag.php
Normal file
48
app/Repos/QuestionTag.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Models\QuestionTag as QuestionTagModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class QuestionTag extends Repository
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $questionId
|
||||
* @param int $tagId
|
||||
* @return QuestionTagModel|Model|bool
|
||||
*/
|
||||
public function findQuestionTag($questionId, $tagId)
|
||||
{
|
||||
return QuestionTagModel::findFirst([
|
||||
'conditions' => 'question_id = :question_id: AND tag_id = :tag_id:',
|
||||
'bind' => ['question_id' => $questionId, 'tag_id' => $tagId],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $tagIds
|
||||
* @return ResultsetInterface|Resultset|QuestionTagModel[]
|
||||
*/
|
||||
public function findByTagIds($tagIds)
|
||||
{
|
||||
return QuestionTagModel::query()
|
||||
->inWhere('tag_id', $tagIds)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $questionIds
|
||||
* @return ResultsetInterface|Resultset|QuestionTagModel[]
|
||||
*/
|
||||
public function findByQuestionIds($questionIds)
|
||||
{
|
||||
return QuestionTagModel::query()
|
||||
->inWhere('question_id', $questionIds)
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user