mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-06-26 12:23:06 +08:00
Merge branch 'koogua/v1.3.1' into demo
This commit is contained in:
commit
a03dc6d626
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
/vendor
|
||||
/config/config.php
|
||||
/config/xs.course.ini
|
||||
/config/xs.article.ini
|
||||
/config/xs.group.ini
|
||||
/config/xs.user.ini
|
||||
/config/alipay/*.crt
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,3 +1,17 @@
|
||||
### [v1.3.1](https://gitee.com/koogua/course-tencent-cloud/releases/v1.3.1)(2021-04-09)
|
||||
|
||||
### 更新
|
||||
|
||||
- 后台增加文章功能
|
||||
- 后台增加标签功能
|
||||
- 增加文章全文检索
|
||||
- 整理命名空间别名
|
||||
- 更新部分链接打开方式
|
||||
- xm-select搜索忽略大小写
|
||||
- 补充遗漏的面授模型章节相关迁移文件
|
||||
- 修正上次字段整理导致的字段不存在问题
|
||||
- 修正上次整理发布字段导致的添加单页和帮助错误
|
||||
|
||||
### [v1.3.0](https://gitee.com/koogua/course-tencent-cloud/releases/v1.3.0)(2021-03-26)
|
||||
|
||||
### 更新
|
||||
|
78
app/Builders/ArticleFavoriteList.php
Normal file
78
app/Builders/ArticleFavoriteList.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
use App\Repos\User as UserRepo;
|
||||
|
||||
class ArticleFavoriteList extends Builder
|
||||
{
|
||||
|
||||
public function handleArticles(array $relations)
|
||||
{
|
||||
$articles = $this->getArticles($relations);
|
||||
|
||||
foreach ($relations as $key => $value) {
|
||||
$relations[$key]['article'] = $articles[$value['article_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 getArticles(array $relations)
|
||||
{
|
||||
$ids = kg_array_column($relations, 'article_id');
|
||||
|
||||
$articleRepo = new ArticleRepo();
|
||||
|
||||
$columns = [
|
||||
'id', 'title', 'cover',
|
||||
'view_count', 'like_count', 'comment_count', 'favorite_count',
|
||||
];
|
||||
|
||||
$articles = $articleRepo->findByIds($ids, $columns);
|
||||
|
||||
$baseUrl = kg_cos_url();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($articles->toArray() as $article) {
|
||||
$article['cover'] = $baseUrl . $article['cover'];
|
||||
$result[$article['id']] = $article;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
83
app/Builders/ArticleList.php
Normal file
83
app/Builders/ArticleList.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use App\Caches\CategoryList as CategoryListCache;
|
||||
use App\Models\Category as CategoryModel;
|
||||
use App\Repos\User as UserRepo;
|
||||
|
||||
class ArticleList extends Builder
|
||||
{
|
||||
|
||||
public function handleArticles(array $articles)
|
||||
{
|
||||
foreach ($articles as $key => $article) {
|
||||
$articles[$key]['tags'] = json_decode($article['tags'], true);
|
||||
}
|
||||
|
||||
return $articles;
|
||||
}
|
||||
|
||||
public function handleCategories(array $articles)
|
||||
{
|
||||
$categories = $this->getCategories();
|
||||
|
||||
foreach ($articles as $key => $article) {
|
||||
$articles[$key]['category'] = $categories[$article['category_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $articles;
|
||||
}
|
||||
|
||||
public function handleUsers(array $articles)
|
||||
{
|
||||
$users = $this->getUsers($articles);
|
||||
|
||||
foreach ($articles as $key => $article) {
|
||||
$articles[$key]['owner'] = $users[$article['owner_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $articles;
|
||||
}
|
||||
|
||||
public function getCategories()
|
||||
{
|
||||
$cache = new CategoryListCache();
|
||||
|
||||
$items = $cache->get(CategoryModel::TYPE_ARTICLE);
|
||||
|
||||
if (empty($items)) return [];
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$result[$item['id']] = [
|
||||
'id' => $item['id'],
|
||||
'name' => $item['name'],
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getUsers($articles)
|
||||
{
|
||||
$ids = kg_array_column($articles, '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;
|
||||
}
|
||||
|
||||
}
|
44
app/Builders/CommentList.php
Normal file
44
app/Builders/CommentList.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use App\Repos\User as UserRepo;
|
||||
|
||||
class CommentList extends Builder
|
||||
{
|
||||
|
||||
public function handleUsers(array $comments)
|
||||
{
|
||||
$users = $this->getUsers($comments);
|
||||
|
||||
foreach ($comments as $key => $comment) {
|
||||
$comments[$key]['owner'] = $users[$comment['owner_id']] ?? new \stdClass();
|
||||
$comments[$key]['to_user'] = $users[$comment['to_user_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $comments;
|
||||
}
|
||||
|
||||
public function getUsers(array $comments)
|
||||
{
|
||||
$ownerIds = kg_array_column($comments, 'owner_id');
|
||||
$toUserIds = kg_array_column($comments, 'to_user_id');
|
||||
$ids = array_merge($ownerIds, $toUserIds);
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
@ -37,9 +37,7 @@ class CourseList extends Builder
|
||||
|
||||
$items = $cache->get(CategoryModel::TYPE_COURSE);
|
||||
|
||||
if (empty($items)) {
|
||||
return [];
|
||||
}
|
||||
if (empty($items)) return [];
|
||||
|
||||
$result = [];
|
||||
|
||||
|
31
app/Caches/Article.php
Normal file
31
app/Caches/Article.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
|
||||
class Article extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return "article:{$id}";
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$articleRepo = new ArticleRepo();
|
||||
|
||||
$article = $articleRepo->findById($id);
|
||||
|
||||
return $article ?: null;
|
||||
}
|
||||
|
||||
}
|
103
app/Caches/ArticleHotAuthorList.php
Normal file
103
app/Caches/ArticleHotAuthorList.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 ArticleHotAuthorList extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'article_hot_author_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();
|
||||
}
|
||||
|
||||
}
|
81
app/Caches/ArticleRelatedList.php
Normal file
81
app/Caches/ArticleRelatedList.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
|
||||
class ArticleRelatedList extends Cache
|
||||
{
|
||||
|
||||
protected $articleId;
|
||||
|
||||
protected $limit = 5;
|
||||
|
||||
protected $lifetime = 1 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return "article_related_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],
|
||||
'published' => 1,
|
||||
];
|
||||
|
||||
$pager = $articleRepo->paginate($where);
|
||||
|
||||
if ($pager->total_items == 0) return [];
|
||||
|
||||
return $this->handleContent($pager->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArticleModel[] $articles
|
||||
* @return array
|
||||
*/
|
||||
public function handleContent($articles)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($articles as $article) {
|
||||
if ($article->id != $this->articleId && $count < $this->limit) {
|
||||
$result[] = [
|
||||
'id' => $article->id,
|
||||
'title' => $article->title,
|
||||
'cover' => $article->cover,
|
||||
'view_count' => $article->view_count,
|
||||
'like_count' => $article->like_count,
|
||||
'comment_count' => $article->comment_count,
|
||||
'favorite_count' => $article->favorite_count,
|
||||
];
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
29
app/Caches/MaxArticleId.php
Normal file
29
app/Caches/MaxArticleId.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Article as ArticleModel;
|
||||
|
||||
class MaxArticleId extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 365 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'max_article_id';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$article = ArticleModel::findFirst(['order' => 'id DESC']);
|
||||
|
||||
return $article->id ?? 0;
|
||||
}
|
||||
|
||||
}
|
29
app/Caches/MaxCommentId.php
Normal file
29
app/Caches/MaxCommentId.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Comment as CommentModel;
|
||||
|
||||
class MaxCommentId extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 365 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'max_comment_id';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$comment = CommentModel::findFirst(['order' => 'id DESC']);
|
||||
|
||||
return $comment->id ?? 0;
|
||||
}
|
||||
|
||||
}
|
29
app/Caches/MaxTagId.php
Normal file
29
app/Caches/MaxTagId.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Tag as TagModel;
|
||||
|
||||
class MaxTagId extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 365 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'max_tag_id';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$tag = TagModel::findFirst(['order' => 'id DESC']);
|
||||
|
||||
return $tag->id ?? 0;
|
||||
}
|
||||
|
||||
}
|
31
app/Caches/Tag.php
Normal file
31
app/Caches/Tag.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Repos\Tag as TagRepo;
|
||||
|
||||
class Tag extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 365 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return "tag:{$id}";
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$tagRepo = new TagRepo();
|
||||
|
||||
$tag = $tagRepo->findById($id);
|
||||
|
||||
return $tag ?: null;
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,8 @@ class UserDailyCounter extends Counter
|
||||
'chapter_like_count' => 0,
|
||||
'consult_like_count' => 0,
|
||||
'review_like_count' => 0,
|
||||
'article_like_count' => 0,
|
||||
'comment_like_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
|
125
app/Console/Tasks/ArticleIndexTask.php
Normal file
125
app/Console/Tasks/ArticleIndexTask.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Services\Search\ArticleDocument;
|
||||
use App\Services\Search\ArticleSearcher;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class ArticleIndexTask extends Task
|
||||
{
|
||||
|
||||
/**
|
||||
* 搜索测试
|
||||
*
|
||||
* @command: php console.php article_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->searchArticles($query);
|
||||
|
||||
var_export($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空索引
|
||||
*
|
||||
* @command: php console.php article_index clean
|
||||
*/
|
||||
public function cleanAction()
|
||||
{
|
||||
$this->cleanArticleIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建索引
|
||||
*
|
||||
* @command: php console.php article_index rebuild
|
||||
*/
|
||||
public function rebuildAction()
|
||||
{
|
||||
$this->rebuildArticleIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空索引
|
||||
*/
|
||||
protected function cleanArticleIndex()
|
||||
{
|
||||
$handler = new ArticleSearcher();
|
||||
|
||||
$index = $handler->getXS()->getIndex();
|
||||
|
||||
echo '------ start clean article index ------' . PHP_EOL;
|
||||
|
||||
$index->clean();
|
||||
|
||||
echo '------ end clean article index ------' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建索引
|
||||
*/
|
||||
protected function rebuildArticleIndex()
|
||||
{
|
||||
$articles = $this->findArticles();
|
||||
|
||||
if ($articles->count() == 0) return;
|
||||
|
||||
$handler = new ArticleSearcher();
|
||||
|
||||
$documenter = new ArticleDocument();
|
||||
|
||||
$index = $handler->getXS()->getIndex();
|
||||
|
||||
echo '------ start rebuild article index ------' . PHP_EOL;
|
||||
|
||||
$index->beginRebuild();
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$document = $documenter->setDocument($article);
|
||||
$index->add($document);
|
||||
}
|
||||
|
||||
$index->endRebuild();
|
||||
|
||||
echo '------ end rebuild article index ------' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索文章
|
||||
*
|
||||
* @param string $query
|
||||
* @return array
|
||||
* @throws \XSException
|
||||
*/
|
||||
protected function searchArticles($query)
|
||||
{
|
||||
$handler = new ArticleSearcher();
|
||||
|
||||
return $handler->search($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找文章
|
||||
*
|
||||
* @return ResultsetInterface|Resultset|ArticleModel[]
|
||||
*/
|
||||
protected function findArticles()
|
||||
{
|
||||
return ArticleModel::query()
|
||||
->where('published = 1')
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Library\Sitemap;
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Models\Course as CourseModel;
|
||||
use App\Models\Help as HelpModel;
|
||||
use App\Models\ImGroup as ImGroupModel;
|
||||
@ -35,6 +36,7 @@ class SitemapTask extends Task
|
||||
|
||||
$this->addIndex();
|
||||
$this->addCourses();
|
||||
$this->addArticles();
|
||||
$this->addTeachers();
|
||||
$this->addTopics();
|
||||
$this->addImGroups();
|
||||
@ -74,6 +76,21 @@ class SitemapTask extends Task
|
||||
}
|
||||
}
|
||||
|
||||
protected function addArticles()
|
||||
{
|
||||
/**
|
||||
* @var Resultset|ArticleModel[] $articles
|
||||
*/
|
||||
$articles = ArticleModel::query()->where('published = 1')->execute();
|
||||
|
||||
if ($articles->count() == 0) return;
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$loc = sprintf('%s/article/%s', $this->siteUrl, $article->id);
|
||||
$this->sitemap->addItem($loc, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addTeachers()
|
||||
{
|
||||
/**
|
||||
|
60
app/Console/Tasks/SyncArticleIndexTask.php
Normal file
60
app/Console/Tasks/SyncArticleIndexTask.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Tasks;
|
||||
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
use App\Services\Search\ArticleDocument;
|
||||
use App\Services\Search\ArticleSearcher;
|
||||
use App\Services\Sync\ArticleIndex as ArticleIndexSync;
|
||||
|
||||
class SyncArticleIndexTask 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;
|
||||
|
||||
$document = new ArticleDocument();
|
||||
|
||||
$handler = new ArticleSearcher();
|
||||
|
||||
$index = $handler->getXS()->getIndex();
|
||||
|
||||
$index->openBuffer();
|
||||
|
||||
foreach ($articles as $article) {
|
||||
|
||||
$doc = $document->setDocument($article);
|
||||
|
||||
if ($article->published == 1) {
|
||||
$index->update($doc);
|
||||
} else {
|
||||
$index->del($article->id);
|
||||
}
|
||||
}
|
||||
|
||||
$index->closeBuffer();
|
||||
|
||||
$redis->sRem($key, ...$articleIds);
|
||||
}
|
||||
|
||||
protected function getSyncKey()
|
||||
{
|
||||
$sync = new ArticleIndexSync();
|
||||
|
||||
return $sync->getSyncKey();
|
||||
}
|
||||
|
||||
}
|
155
app/Http/Admin/Controllers/ArticleController.php
Normal file
155
app/Http/Admin/Controllers/ArticleController.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Controllers;
|
||||
|
||||
use App\Http\Admin\Services\Article as ArticleService;
|
||||
use App\Models\Category as CategoryModel;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/admin/article")
|
||||
*/
|
||||
class ArticleController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/category", name="admin.article.category")
|
||||
*/
|
||||
public function categoryAction()
|
||||
{
|
||||
$location = $this->url->get(
|
||||
['for' => 'admin.category.list'],
|
||||
['type' => CategoryModel::TYPE_ARTICLE]
|
||||
);
|
||||
|
||||
$this->response->redirect($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/search", name="admin.article.search")
|
||||
*/
|
||||
public function searchAction()
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$sourceTypes = $articleService->getSourceTypes();
|
||||
$categories = $articleService->getCategories();
|
||||
$xmTags = $articleService->getXmTags(0);
|
||||
|
||||
$this->view->setVar('source_types', $sourceTypes);
|
||||
$this->view->setVar('categories', $categories);
|
||||
$this->view->setVar('xm_tags', $xmTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/list", name="admin.article.list")
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$pager = $articleService->getArticles();
|
||||
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/add", name="admin.article.add")
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$categories = $articleService->getCategories();
|
||||
|
||||
$this->view->setVar('categories', $categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/create", name="admin.article.create")
|
||||
*/
|
||||
public function createAction()
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$article = $articleService->createArticle();
|
||||
|
||||
$location = $this->url->get([
|
||||
'for' => 'admin.article.edit',
|
||||
'id' => $article->id,
|
||||
]);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '创建文章成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/edit", name="admin.article.edit")
|
||||
*/
|
||||
public function editAction($id)
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$sourceTypes = $articleService->getSourceTypes();
|
||||
$categories = $articleService->getCategories();
|
||||
$article = $articleService->getArticle($id);
|
||||
$xmTags = $articleService->getXmTags($id);
|
||||
|
||||
$this->view->setVar('source_types', $sourceTypes);
|
||||
$this->view->setVar('categories', $categories);
|
||||
$this->view->setVar('article', $article);
|
||||
$this->view->setVar('xm_tags', $xmTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/update", name="admin.article.update")
|
||||
*/
|
||||
public function updateAction($id)
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$articleService->updateArticle($id);
|
||||
|
||||
$content = ['msg' => '更新文章成功'];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/delete", name="admin.article.delete")
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$articleService->deleteArticle($id);
|
||||
|
||||
$content = [
|
||||
'location' => $this->request->getHTTPReferer(),
|
||||
'msg' => '删除文章成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/restore", name="admin.article.restore")
|
||||
*/
|
||||
public function restoreAction($id)
|
||||
{
|
||||
$articleService = new ArticleService();
|
||||
|
||||
$articleService->restoreArticle($id);
|
||||
|
||||
$content = [
|
||||
'location' => $this->request->getHTTPReferer(),
|
||||
'msg' => '还原文章成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
}
|
81
app/Http/Admin/Controllers/CommentController.php
Normal file
81
app/Http/Admin/Controllers/CommentController.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Controllers;
|
||||
|
||||
use App\Http\Admin\Services\Comment as CommentService;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/admin/comment")
|
||||
*/
|
||||
class CommentController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/search", name="admin.comment.search")
|
||||
*/
|
||||
public function searchAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/list", name="admin.comment.list")
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$commentService = new CommentService();
|
||||
|
||||
$pager = $commentService->getComments();
|
||||
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/update", name="admin.comment.update")
|
||||
*/
|
||||
public function updateAction($id)
|
||||
{
|
||||
$commentService = new CommentService();
|
||||
|
||||
$commentService->updateComment($id);
|
||||
|
||||
$content = ['msg' => '更新评论成功'];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/delete", name="admin.comment.delete")
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$commentService = new CommentService();
|
||||
|
||||
$commentService->deleteComment($id);
|
||||
|
||||
$content = [
|
||||
'location' => $this->request->getHTTPReferer(),
|
||||
'msg' => '删除评论成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/restore", name="admin.comment.restore")
|
||||
*/
|
||||
public function restoreAction($id)
|
||||
{
|
||||
$commentService = new CommentService();
|
||||
|
||||
$commentService->restoreComment($id);
|
||||
|
||||
$content = [
|
||||
'location' => $this->request->getHTTPReferer(),
|
||||
'msg' => '还原评论成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
}
|
@ -58,7 +58,7 @@ class Controller extends \Phalcon\Mvc\Controller
|
||||
* 特例白名单
|
||||
*/
|
||||
$whitelist = [
|
||||
'controllers' => ['public', 'index', 'vod', 'upload', 'test'],
|
||||
'controllers' => ['public', 'index', 'upload', 'test'],
|
||||
'routes' => [],
|
||||
];
|
||||
|
||||
|
@ -50,4 +50,17 @@ class PublicController extends \Phalcon\Mvc\Controller
|
||||
$this->view->setVar('region', $region);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/vod/player", name="admin.vod_player")
|
||||
*/
|
||||
public function vodPlayerAction()
|
||||
{
|
||||
$chapterId = $this->request->getQuery('chapter_id', 'int');
|
||||
$playUrl = $this->request->getQuery('play_url', 'string');
|
||||
|
||||
$this->view->pick('public/vod_player');
|
||||
$this->view->setVar('chapter_id', $chapterId);
|
||||
$this->view->setVar('play_url', $playUrl);
|
||||
}
|
||||
|
||||
}
|
||||
|
132
app/Http/Admin/Controllers/TagController.php
Normal file
132
app/Http/Admin/Controllers/TagController.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Controllers;
|
||||
|
||||
use App\Http\Admin\Services\Tag as TagService;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/admin/tag")
|
||||
*/
|
||||
class TagController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/list", name="admin.tag.list")
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$tagService = new TagService();
|
||||
|
||||
$pager = $tagService->getTags();
|
||||
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/search", name="admin.tag.search")
|
||||
*/
|
||||
public function searchAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/add", name="admin.tag.add")
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/edit", name="admin.tag.edit")
|
||||
*/
|
||||
public function editAction($id)
|
||||
{
|
||||
$tagService = new TagService;
|
||||
|
||||
$tag2 = $tagService->getTag($id);
|
||||
|
||||
/**
|
||||
* 注意:"tag"变量被volt引擎内置占用,另取名字避免冲突
|
||||
*/
|
||||
$this->view->setVar('tag2', $tag2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/create", name="admin.tag.create")
|
||||
*/
|
||||
public function createAction()
|
||||
{
|
||||
$tagService = new TagService();
|
||||
|
||||
$tagService->createTag();
|
||||
|
||||
$location = $this->url->get(['for' => 'admin.tag.list']);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '创建标签成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/update", name="admin.tag.update")
|
||||
*/
|
||||
public function updateAction($id)
|
||||
{
|
||||
$tagService = new TagService();
|
||||
|
||||
$tagService->updateTag($id);
|
||||
|
||||
$location = $this->url->get(['for' => 'admin.tag.list']);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '更新标签成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/delete", name="admin.tag.delete")
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$tagService = new TagService();
|
||||
|
||||
$tagService->deleteTag($id);
|
||||
|
||||
$location = $this->request->getHTTPReferer();
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '删除标签成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/restore", name="admin.tag.restore")
|
||||
*/
|
||||
public function restoreAction($id)
|
||||
{
|
||||
$tagService = new TagService();
|
||||
|
||||
$tagService->restoreTag($id);
|
||||
|
||||
$location = $this->request->getHTTPReferer();
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '还原标签成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Admin\Controllers;
|
||||
|
||||
use App\Services\MyStorage as StorageService;
|
||||
use App\Services\Vod as VodService;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/admin/upload")
|
||||
@ -126,8 +127,10 @@ class UploadController extends Controller
|
||||
|
||||
$items['user_avatar'] = $service->uploadDefaultUserAvatar();
|
||||
$items['group_avatar'] = $service->uploadDefaultGroupAvatar();
|
||||
$items['article_cover'] = $service->uploadDefaultArticleCover();
|
||||
$items['course_cover'] = $service->uploadDefaultCourseCover();
|
||||
$items['group_cover'] = $service->uploadDefaultPackageCover();
|
||||
$items['package_cover'] = $service->uploadDefaultPackageCover();
|
||||
$items['gift_cover'] = $service->uploadDefaultGiftCover();
|
||||
$items['vip_cover'] = $service->uploadDefaultVipCover();
|
||||
|
||||
foreach ($items as $item) {
|
||||
@ -138,9 +141,9 @@ class UploadController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/sign", name="admin.upload.sign")
|
||||
* @Post("/credentials", name="admin.upload.credentials")
|
||||
*/
|
||||
public function signatureAction()
|
||||
public function credentialsAction()
|
||||
{
|
||||
$service = new StorageService();
|
||||
|
||||
@ -155,4 +158,16 @@ class UploadController extends Controller
|
||||
return $this->jsonSuccess($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/vod/sign", name="admin.upload.vod_sign")
|
||||
*/
|
||||
public function vodSignatureAction()
|
||||
{
|
||||
$service = new VodService();
|
||||
|
||||
$sign = $service->getUploadSignature();
|
||||
|
||||
return $this->jsonSuccess(['sign' => $sign]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Controllers;
|
||||
|
||||
use App\Services\Vod as VodService;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/admin/vod")
|
||||
*/
|
||||
class VodController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/upload/sign", name="admin.vod.upload_sign")
|
||||
*/
|
||||
public function uploadSignatureAction()
|
||||
{
|
||||
$service = new VodService();
|
||||
|
||||
$sign = $service->getUploadSignature();
|
||||
|
||||
return $this->jsonSuccess(['sign' => $sign]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/player", name="admin.vod.player")
|
||||
*/
|
||||
public function playerAction()
|
||||
{
|
||||
$chapterId = $this->request->getQuery('chapter_id', 'int');
|
||||
$playUrl = $this->request->getQuery('play_url', 'string');
|
||||
|
||||
$this->view->pick('public/vod_player');
|
||||
$this->view->setVar('chapter_id', $chapterId);
|
||||
$this->view->setVar('play_url', $playUrl);
|
||||
}
|
||||
|
||||
}
|
287
app/Http/Admin/Services/Article.php
Normal file
287
app/Http/Admin/Services/Article.php
Normal file
@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Services;
|
||||
|
||||
use App\Builders\ArticleList as ArticleListBuilder;
|
||||
use App\Caches\Article as ArticleCache;
|
||||
use App\Library\Paginator\Query as PagerQuery;
|
||||
use App\Library\Utils\Word as WordUtil;
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Models\ArticleTag as ArticleTagModel;
|
||||
use App\Models\Category as CategoryModel;
|
||||
use App\Repos\Article as ArticleRepo;
|
||||
use App\Repos\ArticleTag as ArticleTagRepo;
|
||||
use App\Repos\Category as CategoryRepo;
|
||||
use App\Repos\Tag as TagRepo;
|
||||
use App\Services\Sync\ArticleIndex as ArticleIndexSync;
|
||||
use App\Validators\Article as ArticleValidator;
|
||||
|
||||
class Article extends Service
|
||||
{
|
||||
|
||||
public function getXmTags($id)
|
||||
{
|
||||
$tagRepo = new TagRepo();
|
||||
|
||||
$allTags = $tagRepo->findAll(['published' => 1], 'priority');
|
||||
|
||||
if ($allTags->count() == 0) return [];
|
||||
|
||||
$articleTagIds = [];
|
||||
|
||||
if ($id > 0) {
|
||||
$article = $this->findOrFail($id);
|
||||
if (!empty($article->tags)) {
|
||||
$articleTagIds = kg_array_column($article->tags, 'id');
|
||||
}
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($allTags as $tag) {
|
||||
$selected = in_array($tag->id, $articleTagIds);
|
||||
$list[] = [
|
||||
'name' => $tag->name,
|
||||
'value' => $tag->id,
|
||||
'selected' => $selected,
|
||||
];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function getCategories()
|
||||
{
|
||||
$categoryRepo = new CategoryRepo();
|
||||
|
||||
return $categoryRepo->findAll([
|
||||
'type' => CategoryModel::TYPE_ARTICLE,
|
||||
'level' => 1,
|
||||
'published' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSourceTypes()
|
||||
{
|
||||
return ArticleModel::sourceTypes();
|
||||
}
|
||||
|
||||
public function getArticles()
|
||||
{
|
||||
$pagerQuery = new PagerQuery();
|
||||
|
||||
$params = $pagerQuery->getParams();
|
||||
|
||||
if (!empty($params['xm_tag_ids'])) {
|
||||
$params['tag_id'] = explode(',', $params['xm_tag_ids']);
|
||||
}
|
||||
|
||||
$params['deleted'] = $params['deleted'] ?? 0;
|
||||
|
||||
$sort = $pagerQuery->getSort();
|
||||
$page = $pagerQuery->getPage();
|
||||
$limit = $pagerQuery->getLimit();
|
||||
|
||||
$articleRepo = new ArticleRepo();
|
||||
|
||||
$pager = $articleRepo->paginate($params, $sort, $page, $limit);
|
||||
|
||||
return $this->handleArticles($pager);
|
||||
}
|
||||
|
||||
public function getArticle($id)
|
||||
{
|
||||
return $this->findOrFail($id);
|
||||
}
|
||||
|
||||
public function createArticle()
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$loginUser = $this->getLoginUser();
|
||||
|
||||
$validator = new ArticleValidator();
|
||||
|
||||
$category = $validator->checkCategory($post['category_id']);
|
||||
$title = $validator->checkTitle($post['title']);
|
||||
|
||||
$article = new ArticleModel();
|
||||
|
||||
$article->owner_id = $loginUser->id;
|
||||
$article->category_id = $category->id;
|
||||
$article->title = $title;
|
||||
|
||||
$article->create();
|
||||
|
||||
return $article;
|
||||
}
|
||||
|
||||
public function updateArticle($id)
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$article = $this->findOrFail($id);
|
||||
|
||||
$validator = new ArticleValidator();
|
||||
|
||||
$data = [];
|
||||
|
||||
if (isset($post['category_id'])) {
|
||||
$category = $validator->checkCategory($post['category_id']);
|
||||
$data['category_id'] = $category->id;
|
||||
}
|
||||
|
||||
if (isset($post['title'])) {
|
||||
$data['title'] = $validator->checkTitle($post['title']);
|
||||
}
|
||||
|
||||
if (isset($post['cover'])) {
|
||||
$data['cover'] = $validator->checkCover($post['cover']);
|
||||
}
|
||||
|
||||
if (isset($post['summary'])) {
|
||||
$data['summary'] = $validator->checkSummary($post['summary']);
|
||||
}
|
||||
|
||||
if (isset($post['content'])) {
|
||||
$data['content'] = $validator->checkContent($post['content']);
|
||||
$data['word_count'] = WordUtil::getWordCount($data['content']);
|
||||
}
|
||||
|
||||
if (isset($post['source_type'])) {
|
||||
$data['source_type'] = $validator->checkSourceType($post['source_type']);
|
||||
if ($post['source_type'] != ArticleModel::SOURCE_ORIGIN) {
|
||||
$data['source_url'] = $validator->checkSourceUrl($post['source_url']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($post['allow_comment'])) {
|
||||
$data['allow_comment'] = $post['allow_comment'];
|
||||
}
|
||||
|
||||
if (isset($post['featured'])) {
|
||||
$data['featured'] = $validator->checkFeatureStatus($post['featured']);
|
||||
}
|
||||
|
||||
if (isset($post['published'])) {
|
||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||
}
|
||||
|
||||
if (isset($post['xm_tag_ids'])) {
|
||||
$this->saveTags($article, $post['xm_tag_ids']);
|
||||
}
|
||||
|
||||
$article->update($data);
|
||||
|
||||
return $article;
|
||||
}
|
||||
|
||||
public function deleteArticle($id)
|
||||
{
|
||||
$article = $this->findOrFail($id);
|
||||
$article->deleted = 1;
|
||||
$article->update();
|
||||
|
||||
return $article;
|
||||
}
|
||||
|
||||
public function restoreArticle($id)
|
||||
{
|
||||
$article = $this->findOrFail($id);
|
||||
$article->deleted = 0;
|
||||
$article->update();
|
||||
|
||||
return $article;
|
||||
}
|
||||
|
||||
protected function findOrFail($id)
|
||||
{
|
||||
$validator = new ArticleValidator();
|
||||
|
||||
return $validator->checkArticle($id);
|
||||
}
|
||||
|
||||
protected function rebuildArticleCache(ArticleModel $article)
|
||||
{
|
||||
$cache = new ArticleCache();
|
||||
|
||||
$cache->rebuild($article->id);
|
||||
}
|
||||
|
||||
protected function rebuildArticleIndex(ArticleModel $article)
|
||||
{
|
||||
$sync = new ArticleIndexSync();
|
||||
|
||||
$sync->addItem($article->id);
|
||||
}
|
||||
|
||||
protected function saveTags(ArticleModel $article, $tagIds)
|
||||
{
|
||||
$originTagIds = [];
|
||||
|
||||
if ($article->tags) {
|
||||
$originTagIds = kg_array_column($article->tags, 'id');
|
||||
}
|
||||
|
||||
$newTagIds = $tagIds ? explode(',', $tagIds) : [];
|
||||
$addedTagIds = array_diff($newTagIds, $originTagIds);
|
||||
|
||||
if ($addedTagIds) {
|
||||
foreach ($addedTagIds as $tagId) {
|
||||
$articleTag = new ArticleTagModel();
|
||||
$articleTag->article_id = $article->id;
|
||||
$articleTag->tag_id = $tagId;
|
||||
$articleTag->create();
|
||||
}
|
||||
}
|
||||
|
||||
$deletedTagIds = array_diff($originTagIds, $newTagIds);
|
||||
|
||||
if ($deletedTagIds) {
|
||||
$articleTagRepo = new ArticleTagRepo();
|
||||
foreach ($deletedTagIds as $tagId) {
|
||||
$articleTag = $articleTagRepo->findArticleTag($article->id, $tagId);
|
||||
if ($articleTag) {
|
||||
$articleTag->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$articleTags = [];
|
||||
|
||||
if ($newTagIds) {
|
||||
$tagRepo = new TagRepo();
|
||||
$tags = $tagRepo->findByIds($newTagIds);
|
||||
if ($tags->count() > 0) {
|
||||
$articleTags = [];
|
||||
foreach ($tags as $tag) {
|
||||
$articleTags[] = ['id' => $tag->id, 'name' => $tag->name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$article->tags = $articleTags;
|
||||
|
||||
$article->update();
|
||||
}
|
||||
|
||||
protected function handleArticles($pager)
|
||||
{
|
||||
if ($pager->total_items > 0) {
|
||||
|
||||
$builder = new ArticleListBuilder();
|
||||
|
||||
$items = $pager->items->toArray();
|
||||
|
||||
$pipeA = $builder->handleArticles($items);
|
||||
$pipeB = $builder->handleCategories($pipeA);
|
||||
$pipeC = $builder->handleUsers($pipeB);
|
||||
$pipeD = $builder->objects($pipeC);
|
||||
|
||||
$pager->items = $pipeD;
|
||||
}
|
||||
|
||||
return $pager;
|
||||
}
|
||||
|
||||
}
|
@ -243,6 +243,117 @@ class AuthNode extends Service
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => '1-7',
|
||||
'title' => '文章管理',
|
||||
'type' => 'menu',
|
||||
'children' => [
|
||||
[
|
||||
'id' => '1-7-1',
|
||||
'title' => '文章列表',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.article.list',
|
||||
],
|
||||
[
|
||||
'id' => '1-7-2',
|
||||
'title' => '搜索文章',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.article.search',
|
||||
],
|
||||
[
|
||||
'id' => '1-7-3',
|
||||
'title' => '添加文章',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.article.add',
|
||||
],
|
||||
[
|
||||
'id' => '1-7-4',
|
||||
'title' => '编辑文章',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.article.edit',
|
||||
],
|
||||
[
|
||||
'id' => '1-7-5',
|
||||
'title' => '删除文章',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.article.delete',
|
||||
],
|
||||
[
|
||||
'id' => '1-7-6',
|
||||
'title' => '文章分类',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.article.category',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => '1-8',
|
||||
'title' => '标签管理',
|
||||
'type' => 'menu',
|
||||
'children' => [
|
||||
[
|
||||
'id' => '1-8-1',
|
||||
'title' => '标签列表',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.tag.list',
|
||||
],
|
||||
[
|
||||
'id' => '1-8-2',
|
||||
'title' => '搜索标签',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.tag.search',
|
||||
],
|
||||
[
|
||||
'id' => '1-8-3',
|
||||
'title' => '添加标签',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.tag.add',
|
||||
],
|
||||
[
|
||||
'id' => '1-8-4',
|
||||
'title' => '编辑标签',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.tag.edit',
|
||||
],
|
||||
[
|
||||
'id' => '1-8-5',
|
||||
'title' => '删除标签',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.tag.delete',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => '1-9',
|
||||
'title' => '评论管理',
|
||||
'type' => 'button',
|
||||
'children' => [
|
||||
[
|
||||
'id' => '1-9-1',
|
||||
'title' => '评论列表',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.comment.list',
|
||||
],
|
||||
[
|
||||
'id' => '1-9-2',
|
||||
'title' => '搜索评论',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.comment.search',
|
||||
],
|
||||
[
|
||||
'id' => '1-9-3',
|
||||
'title' => '编辑评论',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.comment.edit',
|
||||
],
|
||||
[
|
||||
'id' => '1-9-4',
|
||||
'title' => '删除评论',
|
||||
'type' => 'button',
|
||||
'route' => 'admin.comment.delete',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
@ -74,7 +74,6 @@ class Category extends Service
|
||||
$data['type'] = $validator->checkType($post['type']);
|
||||
$data['name'] = $validator->checkName($post['name']);
|
||||
$data['priority'] = $validator->checkPriority($post['priority']);
|
||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||
|
||||
$category = new CategoryModel();
|
||||
|
||||
|
105
app/Http/Admin/Services/Comment.php
Normal file
105
app/Http/Admin/Services/Comment.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Services;
|
||||
|
||||
use App\Builders\CommentList as CommentListBuilder;
|
||||
use App\Library\Paginator\Query as PagerQuery;
|
||||
use App\Repos\Comment as CommentRepo;
|
||||
use App\Validators\Comment as CommentValidator;
|
||||
|
||||
class Comment extends Service
|
||||
{
|
||||
|
||||
public function getComments()
|
||||
{
|
||||
$pagerQuery = new PagerQuery();
|
||||
|
||||
$params = $pagerQuery->getParams();
|
||||
|
||||
$params['deleted'] = $params['deleted'] ?? 0;
|
||||
|
||||
$sort = $pagerQuery->getSort();
|
||||
$page = $pagerQuery->getPage();
|
||||
$limit = $pagerQuery->getLimit();
|
||||
|
||||
$commentRepo = new CommentRepo();
|
||||
|
||||
$pager = $commentRepo->paginate($params, $sort, $page, $limit);
|
||||
|
||||
return $this->handleComments($pager);
|
||||
}
|
||||
|
||||
public function getComment($id)
|
||||
{
|
||||
return $this->findOrFail($id);
|
||||
}
|
||||
|
||||
public function updateComment($id)
|
||||
{
|
||||
$comment = $this->findOrFail($id);
|
||||
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$validator = new CommentValidator();
|
||||
|
||||
$data = [];
|
||||
|
||||
if (isset($post['content'])) {
|
||||
$data['content'] = $validator->checkContent($post['content']);
|
||||
}
|
||||
|
||||
if (isset($post['published'])) {
|
||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||
}
|
||||
|
||||
$comment->update($data);
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
public function deleteComment($id)
|
||||
{
|
||||
$page = $this->findOrFail($id);
|
||||
|
||||
$page->deleted = 1;
|
||||
|
||||
$page->update();
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
public function restoreComment($id)
|
||||
{
|
||||
$page = $this->findOrFail($id);
|
||||
|
||||
$page->deleted = 0;
|
||||
|
||||
$page->update();
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function findOrFail($id)
|
||||
{
|
||||
$validator = new CommentValidator();
|
||||
|
||||
return $validator->checkComment($id);
|
||||
}
|
||||
|
||||
protected function handleComments($pager)
|
||||
{
|
||||
if ($pager->total_items > 0) {
|
||||
|
||||
$builder = new CommentListBuilder();
|
||||
|
||||
$pipeA = $pager->items->toArray();
|
||||
$pipeB = $builder->handleUsers($pipeA);
|
||||
$pipeC = $builder->objects($pipeB);
|
||||
|
||||
$pager->items = $pipeC;
|
||||
}
|
||||
|
||||
return $pager;
|
||||
}
|
||||
|
||||
}
|
@ -38,13 +38,11 @@ class Course extends Service
|
||||
$params = $pagerQuery->getParams();
|
||||
|
||||
if (!empty($params['xm_category_ids'])) {
|
||||
$xmCategoryIds = explode(',', $params['xm_category_ids']);
|
||||
$params['category_id'] = count($xmCategoryIds) > 1 ? $xmCategoryIds : $xmCategoryIds[0];
|
||||
$params['category_id'] = explode(',', $params['xm_category_ids']);
|
||||
}
|
||||
|
||||
if (!empty($params['xm_teacher_ids'])) {
|
||||
$xmTeacherIds = explode(',', $params['xm_teacher_ids']);
|
||||
$params['teacher_id'] = count($xmTeacherIds) > 1 ? $xmTeacherIds : $xmTeacherIds[0];
|
||||
$params['teacher_id'] = explode(',', $params['xm_teacher_ids']);
|
||||
}
|
||||
|
||||
$params['deleted'] = $params['deleted'] ?? 0;
|
||||
@ -288,7 +286,7 @@ class Course extends Service
|
||||
|
||||
$allCategories = $categoryRepo->findAll([
|
||||
'type' => CategoryModel::TYPE_COURSE,
|
||||
'deleted' => 0,
|
||||
'published' => 1,
|
||||
]);
|
||||
|
||||
if ($allCategories->count() == 0) return [];
|
||||
@ -456,12 +454,11 @@ class Course extends Service
|
||||
if ($addedTeacherIds) {
|
||||
foreach ($addedTeacherIds as $teacherId) {
|
||||
$courseTeacher = new CourseUserModel();
|
||||
$courseTeacher->create([
|
||||
'course_id' => $course->id,
|
||||
'user_id' => $teacherId,
|
||||
'role_type' => CourseUserModel::ROLE_TEACHER,
|
||||
'source_type' => CourseUserModel::SOURCE_IMPORT,
|
||||
]);
|
||||
$courseTeacher->course_id = $course->id;
|
||||
$courseTeacher->user_id = $teacherId;
|
||||
$courseTeacher->role_type = CourseUserModel::ROLE_TEACHER;
|
||||
$courseTeacher->source_type = CourseUserModel::SOURCE_IMPORT;
|
||||
$courseTeacher->create();
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,10 +506,9 @@ class Course extends Service
|
||||
if ($addedCategoryIds) {
|
||||
foreach ($addedCategoryIds as $categoryId) {
|
||||
$courseCategory = new CourseCategoryModel();
|
||||
$courseCategory->create([
|
||||
'course_id' => $course->id,
|
||||
'category_id' => $categoryId,
|
||||
]);
|
||||
$courseCategory->course_id = $course->id;
|
||||
$courseCategory->category_id = $categoryId;
|
||||
$courseCategory->create();
|
||||
}
|
||||
}
|
||||
|
||||
@ -568,18 +564,16 @@ class Course extends Service
|
||||
$record = $courseRelatedRepo->findCourseRelated($course->id, $relatedId);
|
||||
if (!$record) {
|
||||
$courseRelated = new CourseRelatedModel();
|
||||
$courseRelated->create([
|
||||
'course_id' => $course->id,
|
||||
'related_id' => $relatedId,
|
||||
]);
|
||||
$courseRelated->course_id = $course->id;
|
||||
$courseRelated->related_id = $relatedId;
|
||||
$courseRelated->create();
|
||||
}
|
||||
$record = $courseRelatedRepo->findCourseRelated($relatedId, $course->id);
|
||||
if (!$record) {
|
||||
$courseRelated = new CourseRelatedModel();
|
||||
$courseRelated->create([
|
||||
'course_id' => $relatedId,
|
||||
'related_id' => $course->id,
|
||||
]);
|
||||
$courseRelated->course_id = $relatedId;
|
||||
$courseRelated->related_id = $course->id;
|
||||
$courseRelated->create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ class Help extends Service
|
||||
$data['title'] = $validator->checkTitle($post['title']);
|
||||
$data['content'] = $validator->checkContent($post['content']);
|
||||
$data['priority'] = $validator->checkPriority($post['priority']);
|
||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||
$data['category_id'] = $category->id;
|
||||
|
||||
$help = new HelpModel();
|
||||
|
@ -72,7 +72,6 @@ class Nav extends Service
|
||||
$data['url'] = $validator->checkUrl($post['url']);
|
||||
$data['target'] = $validator->checkTarget($post['target']);
|
||||
$data['position'] = $validator->checkPosition($post['position']);
|
||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||
|
||||
$nav = new NavModel();
|
||||
|
||||
|
@ -43,7 +43,6 @@ class Page extends Service
|
||||
|
||||
$data['title'] = $validator->checkTitle($post['title']);
|
||||
$data['content'] = $validator->checkContent($post['content']);
|
||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||
|
||||
$page = new PageModel();
|
||||
|
||||
@ -109,11 +108,11 @@ class Page extends Service
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function rebuildPageCache(PageModel $help)
|
||||
protected function rebuildPageCache(PageModel $page)
|
||||
{
|
||||
$cache = new PageCache();
|
||||
|
||||
$cache->rebuild($help->id);
|
||||
$cache->rebuild($page->id);
|
||||
}
|
||||
|
||||
protected function findOrFail($id)
|
||||
|
@ -62,7 +62,7 @@ class Setting extends Service
|
||||
{
|
||||
$oa = $this->getSettings('wechat.oa');
|
||||
|
||||
$oa['notify_url'] = $oa['notify_url'] ?: kg_full_url(['for' => 'home.wechat.oa.notify']);
|
||||
$oa['notify_url'] = $oa['notify_url'] ?: kg_full_url(['for' => 'home.wechat_oa.notify']);
|
||||
|
||||
$oa['menu'] = json_decode($oa['menu'], true);
|
||||
|
||||
|
@ -63,8 +63,6 @@ class Student extends Service
|
||||
|
||||
$params = $pagerQuery->getParams();
|
||||
|
||||
$params['deleted'] = 0;
|
||||
|
||||
$sort = $pagerQuery->getSort();
|
||||
$page = $pagerQuery->getPage();
|
||||
$limit = $pagerQuery->getLimit();
|
||||
|
126
app/Http/Admin/Services/Tag.php
Normal file
126
app/Http/Admin/Services/Tag.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Services;
|
||||
|
||||
use App\Caches\Tag as TagCache;
|
||||
use App\Library\Paginator\Query as PagerQuery;
|
||||
use App\Models\Tag as TagModel;
|
||||
use App\Repos\Tag as TagRepo;
|
||||
use App\Validators\Tag as TagValidator;
|
||||
|
||||
class Tag extends Service
|
||||
{
|
||||
|
||||
public function getTags()
|
||||
{
|
||||
$pagerQuery = new PagerQuery();
|
||||
|
||||
$params = $pagerQuery->getParams();
|
||||
|
||||
$params['deleted'] = $params['deleted'] ?? 0;
|
||||
|
||||
$sort = 'priority';
|
||||
$page = $pagerQuery->getPage();
|
||||
$limit = $pagerQuery->getLimit();
|
||||
|
||||
$tagRepo = new TagRepo();
|
||||
|
||||
return $tagRepo->paginate($params, $sort, $page, $limit);
|
||||
}
|
||||
|
||||
public function getTag($id)
|
||||
{
|
||||
return $this->findOrFail($id);
|
||||
}
|
||||
|
||||
public function createTag()
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$validator = new TagValidator();
|
||||
|
||||
$tag = new TagModel();
|
||||
|
||||
$tag->name = $validator->checkName($post['name']);
|
||||
$tag->published = $validator->checkPublishStatus($post['published']);
|
||||
|
||||
$tag->create();
|
||||
|
||||
$this->rebuildTagCache($tag);
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public function updateTag($id)
|
||||
{
|
||||
$tag = $this->findOrFail($id);
|
||||
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$validator = new TagValidator();
|
||||
|
||||
$data = [];
|
||||
|
||||
if (isset($post['name'])) {
|
||||
$data['name'] = $validator->checkName($post['name']);
|
||||
if ($data['name'] != $tag->name) {
|
||||
$validator->checkIfNameExists($data['name']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($post['priority'])) {
|
||||
$data['priority'] = $validator->checkPriority($post['priority']);
|
||||
}
|
||||
|
||||
if (isset($post['published'])) {
|
||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||
}
|
||||
|
||||
$tag->update($data);
|
||||
|
||||
$this->rebuildTagCache($tag);
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public function deleteTag($id)
|
||||
{
|
||||
$tag = $this->findOrFail($id);
|
||||
|
||||
$tag->deleted = 1;
|
||||
|
||||
$tag->update();
|
||||
|
||||
$this->rebuildTagCache($tag);
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public function restoreTag($id)
|
||||
{
|
||||
$tag = $this->findOrFail($id);
|
||||
|
||||
$tag->deleted = 0;
|
||||
|
||||
$tag->update();
|
||||
|
||||
$this->rebuildTagCache($tag);
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
protected function rebuildTagCache(TagModel $tag)
|
||||
{
|
||||
$cache = new TagCache();
|
||||
|
||||
$cache->rebuild($tag->id);
|
||||
}
|
||||
|
||||
protected function findOrFail($id)
|
||||
{
|
||||
$validator = new TagValidator();
|
||||
|
||||
return $validator->checkTag($id);
|
||||
}
|
||||
|
||||
}
|
35
app/Http/Admin/Views/article/add.volt
Normal file
35
app/Http/Admin/Views/article/add.volt
Normal file
@ -0,0 +1,35 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.article.create'}) }}">
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>添加文章</legend>
|
||||
</fieldset>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="category_id" lay-verify="required">
|
||||
<option value="">请选择</option>
|
||||
{% for item in categories %}
|
||||
<option value="{{ item.id }}">{{ item.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</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="title" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button id="kg-submit" class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
74
app/Http/Admin/Views/article/edit.volt
Normal file
74
app/Http/Admin/Views/article/edit.volt
Normal file
@ -0,0 +1,74 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>编辑文章</legend>
|
||||
</fieldset>
|
||||
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title kg-tab-title">
|
||||
<li class="layui-this">基本信息</li>
|
||||
<li>文章内容</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
{{ partial('article/edit_basic') }}
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{{ partial('article/edit_desc') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block link_css %}
|
||||
|
||||
{{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }}
|
||||
{{ js_include('lib/xm-select.js') }}
|
||||
{{ js_include('admin/js/cover.upload.js') }}
|
||||
{{ js_include('admin/js/vditor.js') }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block inline_js %}
|
||||
|
||||
<script>
|
||||
|
||||
layui.use(['jquery', 'form'], function () {
|
||||
|
||||
xmSelect.render({
|
||||
el: '#xm-tag-ids',
|
||||
name: 'xm_tag_ids',
|
||||
max: 3,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_tags|json_encode }}
|
||||
});
|
||||
|
||||
var $ = layui.jquery;
|
||||
var form = layui.form;
|
||||
|
||||
form.on('radio(source_type)', function (data) {
|
||||
var block = $('#source-url-block');
|
||||
if (data.value === '1') {
|
||||
block.hide();
|
||||
} else {
|
||||
block.show();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
67
app/Http/Admin/Views/article/edit_basic.volt
Normal file
67
app/Http/Admin/Views/article/edit_basic.volt
Normal file
@ -0,0 +1,67 @@
|
||||
{% set source_url_display = article.source_type == 1 ? 'display:none' : 'display:block' %}
|
||||
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.article.update','id':article.id}) }}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">封面</label>
|
||||
<div class="layui-input-inline">
|
||||
<img id="img-cover" class="kg-cover" src="{{ article.cover }}">
|
||||
<input type="hidden" name="cover" value="{{ article.cover }}">
|
||||
</div>
|
||||
<div class="layui-input-inline" style="padding-top:35px;">
|
||||
<button id="change-cover" class="layui-btn layui-btn-sm" type="button">更换</button>
|
||||
</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="title" value="{{ article.title }}" lay-verify="required">
|
||||
</div>
|
||||
</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">
|
||||
<div id="xm-tag-ids"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">来源类型</label>
|
||||
<div class="layui-input-block">
|
||||
{% for value,title in source_types %}
|
||||
<input type="radio" name="source_type" value="{{ value }}" title="{{ title }}" {% if article.source_type == value %}checked="checked"{% endif %} lay-filter="source_type">
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="source-url-block" style="{{ source_url_display }}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">来源网址</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="source_url" value="{{ article.source_url }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">允许评论</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="allow_comment" value="1" title="是" {% if article.allow_comment == 1 %}checked="checked"{% endif %}>
|
||||
<input type="radio" name="allow_comment" value="0" title="否" {% if article.allow_comment == 0 %}checked="checked"{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn kg-submit" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
22
app/Http/Admin/Views/article/edit_desc.volt
Normal file
22
app/Http/Admin/Views/article/edit_desc.volt
Normal file
@ -0,0 +1,22 @@
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.article.update','id':article.id}) }}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">详情</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="vditor"></div>
|
||||
<textarea name="content" class="layui-hide" id="vditor-textarea">{{ article.content }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">简介</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="summary" class="layui-textarea">{{ article.summary }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
183
app/Http/Admin/Views/article/list.volt
Normal file
183
app/Http/Admin/Views/article/list.volt
Normal file
@ -0,0 +1,183 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ partial('macros/article') }}
|
||||
|
||||
{% set add_url = url({'for':'admin.article.add'}) %}
|
||||
{% set search_url = url({'for':'admin.article.search'}) %}
|
||||
|
||||
<div class="kg-nav">
|
||||
<div class="kg-nav-left">
|
||||
<span class="layui-breadcrumb">
|
||||
<a><cite>文章管理</cite></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="kg-nav-right">
|
||||
<a class="layui-btn layui-btn-sm" href="{{ add_url }}">
|
||||
<i class="layui-icon layui-icon-add-1"></i>添加文章
|
||||
</a>
|
||||
<a class="layui-btn layui-btn-sm" href="{{ search_url }}">
|
||||
<i class="layui-icon layui-icon-search"></i>搜索文章
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="layui-table kg-table layui-form">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col width="10%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文章</th>
|
||||
<th>来源</th>
|
||||
<th>作者</th>
|
||||
<th>统计</th>
|
||||
<th>推荐</th>
|
||||
<th>评论</th>
|
||||
<th>发布</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in pager.items %}
|
||||
{% set preview_url = url({'for':'home.article.show','id':item.id}) %}
|
||||
{% set edit_url = url({'for':'admin.article.edit','id':item.id}) %}
|
||||
{% set update_url = url({'for':'admin.article.update','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'admin.article.delete','id':item.id}) %}
|
||||
{% set restore_url = url({'for':'admin.article.restore','id':item.id}) %}
|
||||
{% set comment_url = url({'for':'admin.comment.list'},{'item_id':item.id,'item_type':2}) %}
|
||||
<tr>
|
||||
<td>
|
||||
<p>标题:<a href="{{ edit_url }}">{{ item.title }}</a>({{ item.id }})</p>
|
||||
<p class="meta">
|
||||
{% if item.category.id is defined %}
|
||||
<span>分类:{{ item.category.name }}</span>
|
||||
{% endif %}
|
||||
{% if item.tags %}
|
||||
<span>标签:{{ tags_info(item.tags) }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>创建:{{ date('Y-m-d H:i',item.create_time) }},更新:{{ date('Y-m-d H:i',item.update_time) }}</p>
|
||||
</td>
|
||||
<td>{{ source_info(item.source_type,item.source_url) }}</td>
|
||||
<td>
|
||||
<p>昵称:{{ item.owner.name }}</p>
|
||||
<p>编号:{{ item.owner.id }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>浏览:{{ item.view_count }},评论:{{ item.comment_count }}</p>
|
||||
<p>点赞:{{ item.like_count }},收藏:{{ item.favorite_count }}</p>
|
||||
</td>
|
||||
<td><input type="checkbox" name="featured" value="1" lay-skin="switch" lay-text="是|否" lay-filter="featured" data-url="{{ update_url }}" {% if item.featured == 1 %}checked="checked"{% endif %}></td>
|
||||
<td><input type="checkbox" name="comment" value="1" lay-skin="switch" lay-text="开|关" lay-filter="comment" data-url="{{ update_url }}" {% if item.allow_comment == 1 %}checked="checked"{% endif %}></td>
|
||||
<td><input type="checkbox" name="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">
|
||||
<div class="layui-dropdown">
|
||||
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
|
||||
<ul>
|
||||
<li><a href="{{ preview_url }}" target="_blank">预览文章</a></li>
|
||||
<li><a href="{{ edit_url }}">编辑文章</a></li>
|
||||
{% if item.deleted == 0 %}
|
||||
<li><a href="javascript:" class="kg-delete" data-url="{{ delete_url }}">删除文章</a></li>
|
||||
{% else %}
|
||||
<li><a href="javascript:" class="kg-restore" data-url="{{ restore_url }}">还原文章</a></li>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<li><a href="javascript:" class="kg-comment" data-url="{{ comment_url }}">评论管理</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ partial('partials/pager') }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block inline_js %}
|
||||
|
||||
<script>
|
||||
|
||||
layui.define(['jquery', 'form', 'layer'], function () {
|
||||
|
||||
var $ = layui.jquery;
|
||||
var form = layui.form;
|
||||
var layer = layui.layer;
|
||||
|
||||
form.on('switch(featured)', function (data) {
|
||||
var checked = $(this).is(':checked');
|
||||
var featured = checked ? 1 : 0;
|
||||
var url = $(this).data('url');
|
||||
var tips = featured === 1 ? '确定要推荐?' : '确定要取消推荐?';
|
||||
layer.confirm(tips, function () {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: {featured: featured},
|
||||
success: function (res) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
},
|
||||
error: function (xhr) {
|
||||
var json = JSON.parse(xhr.responseText);
|
||||
layer.msg(json.msg, {icon: 2});
|
||||
data.elem.checked = !checked;
|
||||
form.render();
|
||||
}
|
||||
});
|
||||
}, function () {
|
||||
data.elem.checked = !checked;
|
||||
form.render();
|
||||
});
|
||||
});
|
||||
|
||||
form.on('switch(comment)', function (data) {
|
||||
var checked = $(this).is(':checked');
|
||||
var allowComment = checked ? 1 : 0;
|
||||
var url = $(this).data('url');
|
||||
var tips = allowComment === 1 ? '确定要开启评论?' : '确定要关闭评论?';
|
||||
layer.confirm(tips, function () {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: {allow_comment: allowComment},
|
||||
success: function (res) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
},
|
||||
error: function (xhr) {
|
||||
var json = JSON.parse(xhr.responseText);
|
||||
layer.msg(json.msg, {icon: 2});
|
||||
data.elem.checked = !checked;
|
||||
form.render();
|
||||
}
|
||||
});
|
||||
}, function () {
|
||||
data.elem.checked = !checked;
|
||||
form.render();
|
||||
});
|
||||
});
|
||||
|
||||
$('.kg-comment').on('click', function () {
|
||||
var url = $(this).data('url');
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '评论管理',
|
||||
area: ['1000px', '600px'],
|
||||
content: url
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
104
app/Http/Admin/Views/article/search.volt
Normal file
104
app/Http/Admin/Views/article/search.volt
Normal file
@ -0,0 +1,104 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form kg-form" method="GET" action="{{ url({'for':'admin.article.list'}) }}">
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>搜索文章</legend>
|
||||
</fieldset>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">文章编号</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="id" placeholder="文章编号精确匹配">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">作者编号</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="owner_id" placeholder="作者编号精确匹配">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="title" placeholder="标题模糊匹配">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分类</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="category_id">
|
||||
<option value="">请选择</option>
|
||||
{% for item in categories %}
|
||||
<option value="{{ item.id }}">{{ item.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">标签</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="xm-tag-ids"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">来源类型</label>
|
||||
<div class="layui-input-block">
|
||||
{% for value,title in source_types %}
|
||||
<input type="radio" name="source_type" value="{{ value }}" title="{{ title }}">
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">推荐</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="featured" value="1" title="是">
|
||||
<input type="radio" name="featured" value="0" title="否">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">发布</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="published" value="1" title="是">
|
||||
<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">
|
||||
<input type="radio" name="deleted" value="1" title="是">
|
||||
<input type="radio" name="deleted" value="0" title="否">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="true">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('lib/xm-select.js') }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block inline_js %}
|
||||
|
||||
<script>
|
||||
|
||||
xmSelect.render({
|
||||
el: '#xm-tag-ids',
|
||||
name: 'xm_tag_ids',
|
||||
max: 5,
|
||||
filterable: true,
|
||||
data: {{ xm_tags|json_encode }}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
@ -31,13 +31,6 @@
|
||||
<input class="layui-input" type="text" name="priority" value="10" 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">
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
{% set back_url = url({'for':'admin.category.list'},{'type':type}) %}
|
||||
{% set add_url = url({'for':'admin.category.add'},{'type':type,'parent_id':parent.id}) %}
|
||||
{% set allow_add = (type == 1 and parent.level < 2) or (type == 2 and parent.level < 1) %}
|
||||
{% set allow_add = (type == 1 and parent.level < 2) or (type == 2 and parent.level < 1) or (type == 3 and parent.level < 1) %}
|
||||
|
||||
<div class="kg-nav">
|
||||
<div class="kg-nav-left">
|
||||
@ -55,7 +55,9 @@
|
||||
<td>{{ item.id }}</td>
|
||||
{% if item.type == 1 and item.level < 2 %}
|
||||
<td><a href="{{ child_url }}">{{ item.name }}</a></td>
|
||||
{% else %}
|
||||
{% elseif item.type == 2 %}
|
||||
<td><a href="{{ edit_url }}">{{ item.name }}</a></td>
|
||||
{% elseif item.type == 3 %}
|
||||
<td><a href="{{ edit_url }}">{{ item.name }}</a></td>
|
||||
{% endif %}
|
||||
<td>{{ item.level }}</td>
|
||||
|
98
app/Http/Admin/Views/comment/edit.volt
Normal file
98
app/Http/Admin/Views/comment/edit.volt
Normal file
@ -0,0 +1,98 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.review.update','id':review.id}) }}">
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>编辑评价</legend>
|
||||
</fieldset>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">内容实用</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="rating1" class="kg-rating"></div>
|
||||
<input type="hidden" name="rating1" value="{{ review.rating1 }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">通俗易懂</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="rating2" class="kg-rating"></div>
|
||||
<input type="hidden" name="rating2" value="{{ review.rating2 }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">逻辑清晰</label>
|
||||
<div class="layui-input-block">
|
||||
<div id="rating3" class="kg-rating"></div>
|
||||
<input type="hidden" name="rating3" value="{{ review.rating3 }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">课程评价</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-form-mid gray">{{ review.content }}</div>
|
||||
</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="是" {% if review.published == 1 %}checked="checked"{% endif %}>
|
||||
<input type="radio" name="published" value="0" title="否" {% if review.published == 0 %}checked="checked"{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block inline_js %}
|
||||
|
||||
<script>
|
||||
|
||||
layui.use(['jquery', 'rate'], function () {
|
||||
|
||||
var $ = layui.jquery;
|
||||
var rate = layui.rate;
|
||||
|
||||
var $rating1 = $('input[name=rating1]');
|
||||
var $rating2 = $('input[name=rating2]');
|
||||
var $rating3 = $('input[name=rating3]');
|
||||
|
||||
rate.render({
|
||||
elem: '#rating1',
|
||||
value: $rating1.val(),
|
||||
readonly: true,
|
||||
choose: function (value) {
|
||||
$rating1.val(value);
|
||||
}
|
||||
});
|
||||
|
||||
rate.render({
|
||||
elem: '#rating2',
|
||||
value: $rating2.val(),
|
||||
readonly: true,
|
||||
choose: function (value) {
|
||||
$rating2.val(value);
|
||||
}
|
||||
});
|
||||
|
||||
rate.render({
|
||||
elem: '#rating3',
|
||||
value: $rating3.val(),
|
||||
readonly: true,
|
||||
choose: function (value) {
|
||||
$rating3.val(value);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
63
app/Http/Admin/Views/comment/list.volt
Normal file
63
app/Http/Admin/Views/comment/list.volt
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ partial('macros/common') }}
|
||||
|
||||
<table class="layui-table kg-table layui-form">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col width="10%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>评论</th>
|
||||
<th>用户</th>
|
||||
<th>终端</th>
|
||||
<th>发布</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in pager.items %}
|
||||
{% set update_url = url({'for':'admin.comment.update','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'admin.comment.delete','id':item.id}) %}
|
||||
{% set restore_url = url({'for':'admin.comment.restore','id':item.id}) %}
|
||||
<tr>
|
||||
<td>
|
||||
<p>内容:<a href="javascript:" title="{{ item.content }}">{{ substr(item.content,0,30) }}</a></p>
|
||||
<p>时间:{{ date('Y-m-d H:i',item.create_time) }},点赞:{{ item.like_count }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>昵称:{{ item.owner.name }}</p>
|
||||
<p>编号:{{ item.owner.id }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>类型:{{ client_type(item.client_type) }}</p>
|
||||
<p>地址:<a href="javascript:" class="kg-ip2region" data-ip="{{ item.client_ip }}">查看</a></p>
|
||||
</td>
|
||||
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="published" data-url="{{ update_url }}" {% if item.published == 1 %}checked="checked"{% endif %}></td>
|
||||
<td class="center">
|
||||
{% if item.deleted == 0 %}
|
||||
<a href="javascript:" class="layui-badge layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</a>
|
||||
{% else %}
|
||||
<a href="javascript:" class="layui-badge layui-bg-green kg-restore" data-url="{{ restore_url }}">还原</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ partial('partials/pager') }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('admin/js/ip2region.js') }}
|
||||
|
||||
{% endblock %}
|
50
app/Http/Admin/Views/comment/search.volt
Normal file
50
app/Http/Admin/Views/comment/search.volt
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form kg-form" method="GET" action="{{ url({'for':'admin.review.list'}) }}">
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>搜索评价</legend>
|
||||
</fieldset>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">评价编号</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="id" placeholder="评价编号精确匹配">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">课程编号</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="course_id" placeholder="课程编号精确匹配">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户编号</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="owner_id" placeholder="用户编号精确匹配">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">发布</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="published" value="1" title="是">
|
||||
<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">
|
||||
<input type="radio" name="deleted" value="1" title="是">
|
||||
<input type="radio" name="deleted" value="0" title="否">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="true">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -61,25 +61,34 @@
|
||||
xmSelect.render({
|
||||
el: '#xm-category-ids',
|
||||
name: 'xm_category_ids',
|
||||
filterable: true,
|
||||
max: 5,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_categories|json_encode }}
|
||||
});
|
||||
|
||||
xmSelect.render({
|
||||
el: '#xm-teacher-ids',
|
||||
name: 'xm_teacher_ids',
|
||||
filterable: true,
|
||||
max: 5,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_teachers|json_encode }}
|
||||
});
|
||||
|
||||
xmSelect.render({
|
||||
el: '#xm-course-ids',
|
||||
name: 'xm_course_ids',
|
||||
max: 10,
|
||||
autoRow: true,
|
||||
filterable: true,
|
||||
max: 10,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_courses|json_encode }}
|
||||
});
|
||||
|
||||
|
@ -100,6 +100,9 @@
|
||||
name: 'xm_category_ids',
|
||||
max: 5,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_categories|json_encode }}
|
||||
});
|
||||
|
||||
@ -108,6 +111,9 @@
|
||||
name: 'xm_teacher_ids',
|
||||
max: 5,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_teachers|json_encode }}
|
||||
});
|
||||
|
||||
|
@ -69,6 +69,9 @@
|
||||
name: 'xm_course_id',
|
||||
radio: true,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_courses|json_encode }}
|
||||
});
|
||||
|
||||
@ -77,6 +80,9 @@
|
||||
name: 'xm_package_id',
|
||||
radio: true,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_packages|json_encode }}
|
||||
});
|
||||
|
||||
|
18
app/Http/Admin/Views/macros/article.volt
Normal file
18
app/Http/Admin/Views/macros/article.volt
Normal file
@ -0,0 +1,18 @@
|
||||
{%- macro source_info(type,url) %}
|
||||
{% if type == 1 %}
|
||||
<span>原创</span>
|
||||
{% elseif type == 2 %}
|
||||
<a href="{{ url }}" target="_blank">转载</a>
|
||||
{% elseif type == 3 %}
|
||||
<span>翻译</span>
|
||||
{% else %}
|
||||
<span>N/A</span>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro tags_info(items) %}
|
||||
{% for item in items %}
|
||||
{% set comma = loop.last ? '' : ',' %}
|
||||
{{ item.name ~ comma }}
|
||||
{% endfor %}
|
||||
{%- endmacro %}
|
13
app/Http/Admin/Views/macros/common.volt
Normal file
13
app/Http/Admin/Views/macros/common.volt
Normal file
@ -0,0 +1,13 @@
|
||||
{%- macro client_type(value) %}
|
||||
{% if value == 1 %}
|
||||
PC
|
||||
{% elseif value == 2 %}
|
||||
H5
|
||||
{% elseif value == 3 %}
|
||||
APP
|
||||
{% elseif value == 4 %}
|
||||
小程序
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
@ -4,7 +4,7 @@
|
||||
{% elseif value == 2 %}
|
||||
直播
|
||||
{% elseif value == 3 %}
|
||||
专栏
|
||||
图文
|
||||
{% elseif value == 4 %}
|
||||
面授
|
||||
{% else %}
|
||||
|
@ -51,13 +51,6 @@
|
||||
<input type="radio" name="target" value="_self" title="原窗口">
|
||||
</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">
|
||||
|
@ -79,8 +79,12 @@
|
||||
xmSelect.render({
|
||||
el: '#xm-course-ids',
|
||||
name: 'xm_course_ids',
|
||||
max: 15,
|
||||
autoRow: true,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_courses|json_encode }}
|
||||
});
|
||||
|
||||
|
@ -61,6 +61,9 @@
|
||||
name: 'xm_course_id',
|
||||
radio: true,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_courses|json_encode }}
|
||||
});
|
||||
|
||||
|
@ -141,7 +141,6 @@
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block inline_js %}
|
||||
|
||||
<script>
|
||||
|
@ -121,6 +121,10 @@
|
||||
<td>群组头像</td>
|
||||
<td>public/static/admin/img/default/group_cover.png</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>文章封面</td>
|
||||
<td>public/static/admin/img/default/article_cover.png</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>课程封面</td>
|
||||
<td>public/static/admin/img/default/course_cover.png</td>
|
||||
|
@ -75,6 +75,9 @@
|
||||
name: 'xm_course_id',
|
||||
radio: true,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_courses|json_encode }}
|
||||
});
|
||||
|
||||
@ -83,6 +86,9 @@
|
||||
name: 'xm_page_id',
|
||||
radio: true,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_pages|json_encode }}
|
||||
});
|
||||
|
||||
|
@ -61,13 +61,13 @@
|
||||
<tbody>
|
||||
{% for item in pager.items %}
|
||||
{% set learning_url = url({'for':'admin.student.learning'},{'course_id':item.course_id,'user_id':item.user_id,'plan_id':item.plan_id}) %}
|
||||
{% set list_by_course_url = url({'for':'admin.student.list'},{'course_id':item.course.id}) %}
|
||||
{% set list_by_user_url = url({'for':'admin.student.list'},{'user_id':item.user_id}) %}
|
||||
{% set course_url = url({'for':'home.course.show','id':item.course.id}) %}
|
||||
{% set user_url = url({'for':'home.user.show','id':item.user_id}) %}
|
||||
{% set edit_url = url({'for':'admin.student.edit'},{'relation_id':item.id}) %}
|
||||
<tr>
|
||||
<td>
|
||||
<p>课程:<a href="{{ list_by_course_url }}">{{ item.course.title }}</a>({{ item.course.id }})</p>
|
||||
<p>学员:<a href="{{ list_by_user_url }}">{{ item.user.name }}</a>({{ item.user.id }})</p>
|
||||
<p>课程:<a href="{{ course_url }}" target="_blank">{{ item.course.title }}</a>({{ item.course.id }})</p>
|
||||
<p>学员:<a href="{{ user_url }}" target="_blank">{{ item.user.name }}</a>({{ item.user.id }})</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>进度:<a href="javascript:" class="kg-learning" title="学习记录" data-url="{{ learning_url }}">{{ item.progress }}%</a></p>
|
||||
|
30
app/Http/Admin/Views/tag/add.volt
Normal file
30
app/Http/Admin/Views/tag/add.volt
Normal file
@ -0,0 +1,30 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.tag.create'}) }}">
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>添加标签</legend>
|
||||
</fieldset>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="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">
|
||||
<button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
37
app/Http/Admin/Views/tag/edit.volt
Normal file
37
app/Http/Admin/Views/tag/edit.volt
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.tag.update','id':tag2.id}) }}">
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>编辑标签</legend>
|
||||
</fieldset>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="name" value="{{ tag2.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" value="{{ tag2.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="是" {% if tag2.published == 1 %}checked="checked"{% endif %}>
|
||||
<input type="radio" name="published" value="0" title="否" {% if tag2.published == 0 %}checked="checked"{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
78
app/Http/Admin/Views/tag/list.volt
Normal file
78
app/Http/Admin/Views/tag/list.volt
Normal file
@ -0,0 +1,78 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set add_url = url({'for':'admin.tag.add'}) %}
|
||||
{% set search_url = url({'for':'admin.tag.search'}) %}
|
||||
|
||||
<div class="kg-nav">
|
||||
<div class="kg-nav-left">
|
||||
<span class="layui-breadcrumb">
|
||||
<a><cite>标签管理</cite></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="kg-nav-right">
|
||||
<a class="layui-btn layui-btn-sm" href="{{ add_url }}">
|
||||
<i class="layui-icon layui-icon-add-1"></i>添加标签
|
||||
</a>
|
||||
<a class="layui-btn layui-btn-sm" href="{{ search_url }}">
|
||||
<i class="layui-icon layui-icon-search"></i>搜索标签
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="layui-table kg-table layui-form">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col width="12%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>编号</th>
|
||||
<th>名称</th>
|
||||
<th>创建时间</th>
|
||||
<th>更新时间</th>
|
||||
<th>排序</th>
|
||||
<th>发布</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in pager.items %}
|
||||
{% set edit_url = url({'for':'admin.tag.edit','id':item.id}) %}
|
||||
{% set update_url = url({'for':'admin.tag.update','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'admin.tag.delete','id':item.id}) %}
|
||||
<tr>
|
||||
<td>{{ item.id }}</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><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">
|
||||
<div class="layui-dropdown">
|
||||
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
|
||||
<ul>
|
||||
<li><a href="{{ edit_url }}">编辑</a></li>
|
||||
{% if item.deleted == 0 %}
|
||||
<li><a href="javascript:" class="kg-delete" data-url="{{ delete_url }}">删除</a></li>
|
||||
{% else %}
|
||||
<li><a href="javascript:" class="kg-restore" data-url="{{ restore_url }}">还原</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ partial('partials/pager') }}
|
||||
|
||||
{% endblock %}
|
44
app/Http/Admin/Views/tag/search.volt
Normal file
44
app/Http/Admin/Views/tag/search.volt
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="layui-form kg-form" method="GET" action="{{ url({'for':'admin.tag.list'}) }}">
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>搜索标签</legend>
|
||||
</fieldset>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">编号</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="id" placeholder="编号精确匹配">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="title" placeholder="名称模糊匹配">
|
||||
</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="是">
|
||||
<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">
|
||||
<input type="radio" name="deleted" value="1" title="是">
|
||||
<input type="radio" name="deleted" value="0" title="否">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="true">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -50,8 +50,12 @@
|
||||
xmSelect.render({
|
||||
el: '#xm-course-ids',
|
||||
name: 'xm_course_ids',
|
||||
max: 15,
|
||||
autoRow: true,
|
||||
filterable: true,
|
||||
filterMethod: function (val, item, index, prop) {
|
||||
return item.name.toLowerCase().indexOf(val.toLowerCase()) !== -1;
|
||||
},
|
||||
data: {{ xm_courses|json_encode }}
|
||||
});
|
||||
|
||||
|
@ -61,9 +61,11 @@ class ChapterController extends Controller
|
||||
{
|
||||
$service = new ChapterLikeService();
|
||||
|
||||
$service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,9 +77,11 @@ class ConsultController extends Controller
|
||||
{
|
||||
$service = new ConsultLikeService();
|
||||
|
||||
$service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,9 +91,11 @@ class ConsultController extends Controller
|
||||
{
|
||||
$service = new ConsultLikeService();
|
||||
|
||||
$service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -108,9 +108,11 @@ class CourseController extends Controller
|
||||
{
|
||||
$service = new CourseFavoriteService();
|
||||
|
||||
$service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,9 +122,11 @@ class CourseController extends Controller
|
||||
{
|
||||
$service = new CourseFavoriteService();
|
||||
|
||||
$service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Api\Controllers;
|
||||
|
||||
use App\Services\Logic\Page\PageInfo as PageInfoService;
|
||||
use App\Services\Logic\Page\ArticleInfo as PageInfoService;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/api/page")
|
||||
|
@ -77,9 +77,11 @@ class ReviewController extends Controller
|
||||
{
|
||||
$service = new ReviewLikeService();
|
||||
|
||||
$service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,9 +91,11 @@ class ReviewController extends Controller
|
||||
{
|
||||
$service = new ReviewLikeService();
|
||||
|
||||
$service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ class UploadController extends Controller
|
||||
*/
|
||||
public function uploadImImageAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
136
app/Http/Home/Controllers/ArticleController.php
Normal file
136
app/Http/Home/Controllers/ArticleController.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Http\Home\Services\ArticleQuery as ArticleQueryService;
|
||||
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 Phalcon\Mvc\View;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/article")
|
||||
*/
|
||||
class ArticleController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/list", name="home.article.list")
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$service = new ArticleQueryService();
|
||||
|
||||
$categories = $service->handleCategories();
|
||||
$sorts = $service->handleSorts();
|
||||
$params = $service->getParams();
|
||||
|
||||
$this->seo->prependTitle('专栏');
|
||||
|
||||
$this->view->setVar('categories', $categories);
|
||||
$this->view->setVar('sorts', $sorts);
|
||||
$this->view->setVar('params', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/pager", name="home.article.pager")
|
||||
*/
|
||||
public function pagerAction()
|
||||
{
|
||||
$service = new ArticleListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$pager->target = 'article-list';
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/hot/authors", name="home.article.hot_authors")
|
||||
*/
|
||||
public function hotAuthorsAction()
|
||||
{
|
||||
$service = new HotAuthorListService();
|
||||
|
||||
$authors = $service->handle();
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('article/hot_authors');
|
||||
$this->view->setVar('authors', $authors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}", name="home.article.show")
|
||||
*/
|
||||
public function showAction($id)
|
||||
{
|
||||
$service = new ArticleInfoService();
|
||||
|
||||
$article = $service->handle($id);
|
||||
|
||||
$this->seo->prependTitle($article['title']);
|
||||
|
||||
$this->view->setVar('article', $article);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/related", name="home.article.related")
|
||||
*/
|
||||
public function relatedAction($id)
|
||||
{
|
||||
$service = new ArticleRelatedListService();
|
||||
|
||||
$articles = $service->handle($id);
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$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("/{id:[0-9]+}/favorite", name="home.article.favorite")
|
||||
*/
|
||||
public function favoriteAction($id)
|
||||
{
|
||||
$service = new ArticleFavoriteService();
|
||||
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/like", name="home.article.like")
|
||||
*/
|
||||
public function likeAction($id)
|
||||
{
|
||||
$service = new ArticleLikeService();
|
||||
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
@ -100,11 +100,11 @@ class ChapterController extends Controller
|
||||
{
|
||||
$service = new ChapterLikeService();
|
||||
|
||||
$like = $service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $like->deleted == 0 ? '点赞成功' : '取消点赞成功';
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['msg' => $msg]);
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,11 +140,11 @@ class ConsultController extends Controller
|
||||
{
|
||||
$service = new ConsultLikeService();
|
||||
|
||||
$like = $service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $like->deleted == 0 ? '点赞成功' : '取消点赞成功';
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['msg' => $msg]);
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -178,11 +178,11 @@ class CourseController extends Controller
|
||||
{
|
||||
$service = new CourseFavoriteService();
|
||||
|
||||
$favorite = $service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $favorite->deleted == 0 ? '收藏成功' : '取消收藏成功';
|
||||
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
|
||||
|
||||
return $this->jsonSuccess(['msg' => $msg]);
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Http\Home\Services\Index as IndexService;
|
||||
use App\Services\Logic\Page\PageInfo as PageInfoService;
|
||||
use App\Services\Logic\Page\ArticleInfo as PageInfoService;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/page")
|
||||
|
@ -109,11 +109,11 @@ class ReviewController extends Controller
|
||||
{
|
||||
$service = new ReviewLikeService();
|
||||
|
||||
$like = $service->handle($id);
|
||||
$data = $service->handle($id);
|
||||
|
||||
$msg = $like->deleted == 0 ? '点赞成功' : '取消点赞成功';
|
||||
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
|
||||
|
||||
return $this->jsonSuccess(['msg' => $msg]);
|
||||
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,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\User as UserSearchService;
|
||||
@ -41,11 +42,14 @@ class SearchController extends Controller
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return CourseSearchService|GroupSearchService|UserSearchService
|
||||
* @return ArticleSearchService|CourseSearchService|GroupSearchService|UserSearchService
|
||||
*/
|
||||
protected function getSearchService($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'article':
|
||||
$service = new ArticleSearchService();
|
||||
break;
|
||||
case 'group':
|
||||
$service = new GroupSearchService();
|
||||
break;
|
||||
|
@ -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\ArticleList as ArticleListService;
|
||||
use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService;
|
||||
use App\Services\Logic\User\Console\ConnectList as ConnectListService;
|
||||
use App\Services\Logic\User\Console\ConsultList as ConsultListService;
|
||||
@ -134,6 +135,19 @@ class UserConsoleController extends Controller
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/articles", name="home.uc.articles")
|
||||
*/
|
||||
public function articlesAction()
|
||||
{
|
||||
$service = new ArticleListService();
|
||||
|
||||
$pager = $service->handle();
|
||||
|
||||
$this->view->pick('user/console/articles');
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/favorites", name="home.uc.favorites")
|
||||
*/
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Services\Logic\User\ArticleList as UserArticleListService;
|
||||
use App\Services\Logic\User\CourseList as UserCourseListService;
|
||||
use App\Services\Logic\User\FavoriteList as UserFavoriteListService;
|
||||
use App\Services\Logic\User\FriendList as UserFriendListService;
|
||||
use App\Services\Logic\User\GroupList as UserGroupListService;
|
||||
use App\Services\Logic\User\UserInfo as UserInfoService;
|
||||
@ -46,18 +46,18 @@ class UserController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/favorites", name="home.user.favorites")
|
||||
* @Get("/{id:[0-9]+}/articles", name="home.user.articles")
|
||||
*/
|
||||
public function favoritesAction($id)
|
||||
public function articlesAction($id)
|
||||
{
|
||||
$service = new UserFavoriteListService();
|
||||
$service = new UserArticleListService();
|
||||
|
||||
$pager = $service->handle($id);
|
||||
|
||||
$pager->target = 'tab-favorites';
|
||||
$pager->target = 'tab-articles';
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('user/favorites');
|
||||
$this->view->pick('user/articles');
|
||||
$this->view->setVar('pager', $pager);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ class VerifyController extends \Phalcon\Mvc\Controller
|
||||
use ResponseTrait;
|
||||
|
||||
/**
|
||||
* @Post("/sms/code", name="verify.sms_code")
|
||||
* @Post("/sms/code", name="home.verify.sms_code")
|
||||
*/
|
||||
public function smsCodeAction()
|
||||
{
|
||||
@ -27,7 +27,7 @@ class VerifyController extends \Phalcon\Mvc\Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/email/code", name="verify.email_code")
|
||||
* @Post("/email/code", name="home.verify.email_code")
|
||||
*/
|
||||
public function emailCodeAction()
|
||||
{
|
||||
|
105
app/Http/Home/Services/ArticleQuery.php
Normal file
105
app/Http/Home/Services/ArticleQuery.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Services;
|
||||
|
||||
use App\Models\Article as ArticleModel;
|
||||
use App\Models\Category as CategoryModel;
|
||||
use App\Services\Category as CategoryService;
|
||||
use App\Validators\ArticleQuery as ArticleQueryValidator;
|
||||
|
||||
class ArticleQuery extends Service
|
||||
{
|
||||
|
||||
protected $baseUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = $this->url->get(['for' => 'home.article.list']);
|
||||
}
|
||||
|
||||
public function handleCategories()
|
||||
{
|
||||
$params = $this->getParams();
|
||||
|
||||
if (isset($params['category_id'])) {
|
||||
unset($params['category_id']);
|
||||
}
|
||||
|
||||
$defaultItem = [
|
||||
'id' => 'all',
|
||||
'name' => '全部',
|
||||
'url' => $this->baseUrl . $this->buildParams($params),
|
||||
];
|
||||
|
||||
$result = [];
|
||||
|
||||
$result[] = $defaultItem;
|
||||
|
||||
$categoryService = new CategoryService();
|
||||
|
||||
$topCategories = $categoryService->getChildCategories(CategoryModel::TYPE_ARTICLE, 0);
|
||||
|
||||
foreach ($topCategories as $key => $category) {
|
||||
$params['category_id'] = $category['id'];
|
||||
$result[] = [
|
||||
'id' => $category['id'],
|
||||
'name' => $category['name'],
|
||||
'url' => $this->baseUrl . $this->buildParams($params),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function handleSorts()
|
||||
{
|
||||
$params = $this->getParams();
|
||||
|
||||
$result = [];
|
||||
|
||||
$sorts = ArticleModel::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 ArticleQueryValidator();
|
||||
|
||||
if (isset($query['category_id']) && $query['category_id'] != 'all') {
|
||||
$validator->checkCategory($query['category_id']);
|
||||
$params['category_id'] = $query['category_id'];
|
||||
}
|
||||
|
||||
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) : '';
|
||||
}
|
||||
|
||||
}
|
@ -30,12 +30,10 @@ class CourseQuery extends Service
|
||||
unset($params['sc']);
|
||||
}
|
||||
|
||||
$baseUrl = $this->url->get(['for' => 'home.course.list']);
|
||||
|
||||
$defaultItem = [
|
||||
'id' => 'all',
|
||||
'name' => '全部',
|
||||
'url' => $baseUrl . $this->buildParams($params),
|
||||
'url' => $this->baseUrl . $this->buildParams($params),
|
||||
];
|
||||
|
||||
$result = [];
|
||||
@ -51,7 +49,7 @@ class CourseQuery extends Service
|
||||
$result[] = [
|
||||
'id' => $category['id'],
|
||||
'name' => $category['name'],
|
||||
'url' => $baseUrl . $this->buildParams($params),
|
||||
'url' => $this->baseUrl . $this->buildParams($params),
|
||||
];
|
||||
}
|
||||
|
||||
@ -78,12 +76,10 @@ class CourseQuery extends Service
|
||||
unset($params['sc']);
|
||||
}
|
||||
|
||||
$baseUrl = $this->url->get(['for' => 'home.course.list']);
|
||||
|
||||
$defaultItem = [
|
||||
'id' => 'all',
|
||||
'name' => '全部',
|
||||
'url' => $baseUrl . $this->buildParams($params),
|
||||
'url' => $this->baseUrl . $this->buildParams($params),
|
||||
];
|
||||
|
||||
$result = [];
|
||||
@ -95,7 +91,7 @@ class CourseQuery extends Service
|
||||
$result[] = [
|
||||
'id' => $category['id'],
|
||||
'name' => $category['name'],
|
||||
'url' => $baseUrl . $this->buildParams($params),
|
||||
'url' => $this->baseUrl . $this->buildParams($params),
|
||||
];
|
||||
}
|
||||
|
||||
|
24
app/Http/Home/Views/article/hot_authors.volt
Normal file
24
app/Http/Home/Views/article/hot_authors.volt
Normal file
@ -0,0 +1,24 @@
|
||||
{% if authors %}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">推荐作者</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="sidebar-user-list">
|
||||
{% for author in authors %}
|
||||
{% set author.title = author.title ? author.title : '暂露头角' %}
|
||||
{% set author_url = url({'for':'home.user.show','id':author.id}) %}
|
||||
<div class="sidebar-user-card clearfix">
|
||||
<div class="avatar">
|
||||
<img src="{{ author.avatar }}!avatar_160" alt="{{ author.name }}">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ author_url }}" title="{{ author.about }}" target="_blank">{{ author.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ author.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
46
app/Http/Home/Views/article/list.volt
Normal file
46
app/Http/Home/Views/article/list.volt
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% 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'}) %}
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar" id="hot-author-list" data-url="{{ hot_author_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('home/js/article.list.js') }}
|
||||
|
||||
{% endblock %}
|
31
app/Http/Home/Views/article/pager.volt
Normal file
31
app/Http/Home/Views/article/pager.volt
Normal file
@ -0,0 +1,31 @@
|
||||
{{ partial('macros/article') }}
|
||||
|
||||
{% if pager.total_pages > 0 %}
|
||||
<div class="article-list clearfix">
|
||||
{% 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="info">
|
||||
<div class="title layui-elip">
|
||||
<a href="{{ article_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="comment">{{ item.comment_count }} 评论</span>
|
||||
<span class="like">{{ item.like_count }} 点赞</span>
|
||||
<span class="time">{{ item.create_time|time_ago }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ partial('partials/pager_ajax') }}
|
||||
{% endif %}
|
20
app/Http/Home/Views/article/related.volt
Normal file
20
app/Http/Home/Views/article/related.volt
Normal file
@ -0,0 +1,20 @@
|
||||
{% if articles %}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">相关文章</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="sidebar-article-list">
|
||||
{% for article in articles %}
|
||||
{% set article_url = url({'for':'home.article.show','id':article.id}) %}
|
||||
<div class="title">
|
||||
<a href="{{ article_url }}" target="_blank">{{ article.title }}</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="view">{{ article.view_count }} 浏览</span>
|
||||
<span class="like">{{ article.like_count }} 点赞</span>
|
||||
<span class="comment">{{ article.like_count }} 评论</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
141
app/Http/Home/Views/article/show.volt
Normal file
141
app/Http/Home/Views/article/show.volt
Normal file
@ -0,0 +1,141 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ partial('macros/article') }}
|
||||
|
||||
{% set list_url = url({'for':'home.article.list'}) %}
|
||||
{% set category_url = url({'for':'home.article.list'},{'category_id':article.category.id}) %}
|
||||
{% set owner_url = url({'for':'home.user.show','id':article.owner.id}) %}
|
||||
{% set favorite_url = url({'for':'home.article.favorite','id':article.id}) %}
|
||||
{% set like_url = url({'for':'home.article.like','id':article.id}) %}
|
||||
{% set favorited_class = article.me.favorited ? 'layui-icon-star-fill' : 'layui-icon-star' %}
|
||||
{% set liked_class = article.me.liked ? 'active' : '' %}
|
||||
{% set article.owner.title = article.owner.title ? article.owner.title : '默默无名' %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
<a href="/">首页</a>
|
||||
<a href="{{ list_url }}">专栏</a>
|
||||
<a href="{{ category_url }}">{{ article.category.name }}</a>
|
||||
<a><cite>文章详情</cite></a>
|
||||
</span>
|
||||
<span class="share">
|
||||
<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>
|
||||
<a href="javascript:" title="分享到微信"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-main clearfix">
|
||||
|
||||
<div class="action-sticky">
|
||||
<div class="item">
|
||||
<div class="icon" data-url="{{ like_url }}">
|
||||
<i class="layui-icon layui-icon-praise icon-praise {{ liked_class }}"></i>
|
||||
</div>
|
||||
<div class="text">{{ article.like_count }}</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon icon-reply">
|
||||
<i class="layui-icon layui-icon-reply-fill"></i>
|
||||
</div>
|
||||
<div class="text">{{ article.comment_count }}</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon" data-url="{{ favorite_url }}">
|
||||
<i class="layui-icon layui-icon-star icon-star {{ favorited_class }}"></i>
|
||||
</div>
|
||||
<div class="text">{{ article.favorite_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-content">
|
||||
<div class="article-info wrap">
|
||||
<div class="title">{{ article.title }}</div>
|
||||
<div class="meta">
|
||||
<span class="source layui-badge layui-bg-green">{{ source_type(article.source_type) }}</span>
|
||||
<span class="owner">
|
||||
<a href="{{ owner_url }}">{{ article.owner.name }}</a>
|
||||
</span>
|
||||
<span class="view">{{ article.view_count }} 阅读</span>
|
||||
<span class="word">{{ article.word_count }} 字数</span>
|
||||
<span class="time">{{ article.create_time|time_ago }}</span>
|
||||
</div>
|
||||
<div class="content markdown-body">{{ article.content }}</div>
|
||||
{% if article.tags %}
|
||||
<div class="tags">
|
||||
{% for item in article.tags %}
|
||||
{% set url = url({'for':'home.article.list'},{'tag_id':item.id}) %}
|
||||
<a href="{{ url }}" class="layui-btn layui-btn-xs">{{ item.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if article.source_type == 1 %}
|
||||
<div class="source-tips">本作品系原创,转载请注明出处</div>
|
||||
{% elseif article.source_url %}
|
||||
<div class="source-tips">
|
||||
<a href="{{ article.source_url }}" target="_blank">阅读原文</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="comment-wrap" id="comment-wrap">
|
||||
<div class="comment-form">
|
||||
|
||||
</div>
|
||||
<div class="comment-list">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set related_article_url = url({'for':'home.article.related','id':article.id}) %}
|
||||
|
||||
<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="{{ article.owner.avatar }}!avatar_160" alt="{{ article.owner.name }}">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ owner_url }}" title="{{ article.owner.about }}">{{ article.owner.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ article.owner.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar" id="related-article-list" data-url="{{ related_article_url }}"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% set share_url = full_url({'for':'home.article.show','id':article.id}) %}
|
||||
{% set qrcode_url = url({'for':'home.qrcode'},{'text':share_url}) %}
|
||||
|
||||
<div class="layui-hide">
|
||||
<input type="hidden" name="share.title" value="{{ article.title }}">
|
||||
<input type="hidden" name="share.pic" value="{{ article.cover }}">
|
||||
<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/article.show.js') }}
|
||||
{{ js_include('home/js/article.share.js') }}
|
||||
|
||||
{% endblock %}
|
@ -7,7 +7,7 @@
|
||||
{% set like_url = url({'for':'home.consult.like','id':item.id}) %}
|
||||
<div class="review-card clearfix">
|
||||
<div class="avatar">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}" target="_blank">
|
||||
<img src="{{ item.owner.avatar }}!avatar_160" alt="{{ item.owner.name }}">
|
||||
</a>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@
|
||||
{% set course_url = url({'for':'home.course.show','id':course.id}) %}
|
||||
<div class="package-course-card">
|
||||
<div class="cover"><img src="{{ course.cover }}!cover_270" alt="{{ course.title }}"></div>
|
||||
<div class="title"><a href="{{ course_url }}">{{ course.title }}</a></div>
|
||||
<div class="title"><a href="{{ course_url }}" target="_blank">{{ course.title }}</a></div>
|
||||
</div>
|
||||
{% if loop.first %}
|
||||
<div class="separator"><i class="layui-icon layui-icon-add-1"></i></div>
|
||||
|
@ -7,14 +7,14 @@
|
||||
{% set like_url = url({'for':'home.review.like','id':item.id}) %}
|
||||
<div class="review-card clearfix">
|
||||
<div class="avatar">
|
||||
<a href="{{ owner_url }}" title="{{ item.owner.name }}">
|
||||
<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">
|
||||
<div class="rating">{{ star_info(item.rating) }}</div>
|
||||
<div class="user">
|
||||
<a href="{{ owner_url }}">{{ item.owner.name }}</a>
|
||||
<a href="{{ owner_url }}" target="_blank">{{ item.owner.name }}</a>
|
||||
</div>
|
||||
<div class="content">{{ item.content }}</div>
|
||||
<div class="footer">
|
||||
|
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ teacher_url }}" title="{{ teacher.about }}">{{ teacher.name }}</a>
|
||||
<a href="{{ teacher_url }}" title="{{ teacher.about }}" target="_blank">{{ teacher.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ teacher.title }}</div>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="layui-card-body">
|
||||
{% for topic in topics %}
|
||||
{% set topic_url = url({'for':'home.topic.show','id':topic.id}) %}
|
||||
<a class="layui-badge-rim topic-badge" href="{{ topic_url }}">{{ topic.title }}</a>
|
||||
<a class="layui-badge-rim topic-badge" href="{{ topic_url }}" target="_blank">{{ topic.title }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
<ul class="help-list">
|
||||
{% for help in item.helps %}
|
||||
{% set show_url = url({'for':'home.help.show','id':help.id}) %}
|
||||
<li><a href="{{ show_url }}"><i class="layui-icon layui-icon-right"></i>{{ help.title }}</a></li>
|
||||
<li><a href="{{ show_url }}" target="_blank"><i class="layui-icon layui-icon-right"></i>{{ help.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -11,7 +11,7 @@
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ user_url }}" title="{{ user.about }}">{{ user.name }}</a>
|
||||
<a href="{{ user_url }}" title="{{ user.about }}" target="_blank">{{ user.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ user.title }}</div>
|
||||
</div>
|
||||
|
@ -10,12 +10,12 @@
|
||||
<div class="user-card">
|
||||
<span class="type layui-badge layui-bg-green">{{ type_info(item.type) }}</span>
|
||||
<div class="avatar">
|
||||
<a href="{{ group_url }}" title="{{ item.about }}">
|
||||
<a href="{{ group_url }}" title="{{ item.about }}" target="_blank">
|
||||
<img src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ group_url }}" title="{{ item.name }}">{{ item.name }}</a>
|
||||
<a href="{{ group_url }}" title="{{ item.name }}" target="_blank">{{ item.name }}</a>
|
||||
</div>
|
||||
<div class="meta layui-elip">
|
||||
<span>成员:{{ item.user_count }}</span>
|
||||
|
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ owner_url }}" title="{{ group.owner.about }}">{{ group.owner.name }}</a>
|
||||
<a href="{{ owner_url }}" title="{{ group.owner.about }}" target="_blank">{{ group.owner.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ group.owner.title }}</div>
|
||||
</div>
|
||||
|
@ -7,12 +7,12 @@
|
||||
<div class="layui-col-md3">
|
||||
<div class="user-card">
|
||||
<div class="{{ avatar_class }}">
|
||||
<a href="{{ user_url }}" title="{{ item.user.about }}">
|
||||
<a href="{{ user_url }}" title="{{ item.user.about }}" target="_blank">
|
||||
<img src="{{ item.user.avatar }}" alt="{{ item.user.name }}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ user_url }}" title="{{ item.user.about }}">{{ item.user.name }}</a>
|
||||
<a href="{{ user_url }}" title="{{ item.user.about }}" target="_blank">{{ item.user.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ item.user.title }}</div>
|
||||
<div class="action">
|
||||
|
@ -7,12 +7,12 @@
|
||||
<div class="user-card">
|
||||
<span class="type layui-badge layui-bg-green">{{ type_info(group.type) }}</span>
|
||||
<div class="avatar">
|
||||
<a href="{{ group_url }}" title="{{ group.about }}" target="group">
|
||||
<a href="{{ group_url }}" title="{{ group.about }}" target="_blank">
|
||||
<img src="{{ group.avatar }}!avatar_160" alt="{{ group.name }}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ group_url }}" title="{{ group.name }}" target="group">{{ group.name }}</a>
|
||||
<a href="{{ group_url }}" title="{{ group.name }}" target="_blank">{{ group.name }}</a>
|
||||
</div>
|
||||
<div class="meta layui-elip">
|
||||
<span>成员:{{ group.user_count }}</span>
|
||||
|
@ -8,12 +8,12 @@
|
||||
<div class="layui-col-md3">
|
||||
<div class="user-card">
|
||||
<div class="{{ avatar_class }}">
|
||||
<a href="{{ user_url }}" title="{{ user.about }}" target="user">
|
||||
<a href="{{ user_url }}" title="{{ user.about }}" target="_blank">
|
||||
<img src="{{ user.avatar }}!avatar_160" alt="{{ user.name }}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="name layui-elip">
|
||||
<a href="{{ user_url }}" title="{{ user.about }}" target="user">{{ user.name }}</a>
|
||||
<a href="{{ user_url }}" title="{{ user.about }}" target="_blank">{{ user.name }}</a>
|
||||
</div>
|
||||
<div class="title layui-elip">{{ user.title }}</div>
|
||||
<div class="action">
|
||||
|
11
app/Http/Home/Views/macros/article.volt
Normal file
11
app/Http/Home/Views/macros/article.volt
Normal file
@ -0,0 +1,11 @@
|
||||
{%- macro source_type(type) %}
|
||||
{% if type == 1 %}
|
||||
原创
|
||||
{% elseif type == 2 %}
|
||||
转载
|
||||
{% elseif type == 3 %}
|
||||
翻译
|
||||
{% else %}
|
||||
未知
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
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