1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-27 21:10:24 +08:00

专栏阶段性提交

This commit is contained in:
koogua 2021-04-08 19:58:30 +08:00
parent 0c964b85a6
commit d98b936969
155 changed files with 6915 additions and 348 deletions

View 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;
}
}

View 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;
}
}

View File

@ -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
View 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;
}
}

View 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();
}
}

View 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;
}
}

View 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/MaxTagId.php Normal file
View 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
View 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;
}
}

View File

@ -28,6 +28,7 @@ class UserDailyCounter extends Counter
'chapter_like_count' => 0,
'consult_like_count' => 0,
'review_like_count' => 0,
'article_like_count' => 0,
];
}

View 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);
}
}

View 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);
}
}

View File

@ -47,7 +47,7 @@ class Controller extends \Phalcon\Mvc\Controller
* 特例白名单
*/
$whitelist = [
'controllers' => ['public', 'index', 'vod', 'upload', 'test'],
'controllers' => ['public', 'index', 'upload', 'test'],
'routes' => [],
];

View File

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

View 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);
}
}

View File

@ -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]);
}
}

View File

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

View 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;
}
}

View File

@ -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',
],
],
],
],
];
}

View 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;
}
}

View File

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

View File

@ -109,11 +109,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)

View File

@ -63,8 +63,6 @@ class Student extends Service
$params = $pagerQuery->getParams();
$params['deleted'] = 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();

View 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);
}
}

View 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 %}

View File

@ -0,0 +1,72 @@
{% 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',
filterable: true,
max: 3,
data: {{ xm_tags|json_encode }}
});
var $ = layui.jquery;
var form = layui.form;
form.on('radio(source_type)', function (data) {
var block = $('#source-url-block');
console.log(data);
if (data.value === '1') {
block.hide();
} else {
block.show();
}
});
});
</script>
{% endblock %}

View 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>

View 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>

View 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 %}

View 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',
filterable: true,
max: 5,
data: {{ xm_tags|json_encode }}
});
</script>
{% endblock %}

View File

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

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View File

@ -4,7 +4,7 @@
{% elseif value == 2 %}
直播
{% elseif value == 3 %}
专栏
图文
{% elseif value == 4 %}
面授
{% else %}

View File

@ -141,7 +141,6 @@
{% endblock %}
{% block inline_js %}
<script>

View File

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

View File

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

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View File

@ -77,9 +77,9 @@ class ConsultController extends Controller
{
$service = new ConsultLikeService();
$service->handle($id);
$likeCount = $service->handle($id);
return $this->jsonSuccess();
return $this->jsonSuccess(['like_count' => $likeCount]);
}
/**
@ -89,9 +89,9 @@ class ConsultController extends Controller
{
$service = new ConsultLikeService();
$service->handle($id);
$likeCount = $service->handle($id);
return $this->jsonSuccess();
return $this->jsonSuccess(['like_count' => $likeCount]);
}
}

View File

@ -108,9 +108,9 @@ class CourseController extends Controller
{
$service = new CourseFavoriteService();
$service->handle($id);
$favoriteCount = $service->handle($id);
return $this->jsonSuccess();
return $this->jsonSuccess(['favorite_count' => $favoriteCount]);
}
/**
@ -120,9 +120,9 @@ class CourseController extends Controller
{
$service = new CourseFavoriteService();
$service->handle($id);
$favoriteCount = $service->handle($id);
return $this->jsonSuccess();
return $this->jsonSuccess(['favorite_count' => $favoriteCount]);
}
}

View File

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

View File

@ -77,9 +77,9 @@ class ReviewController extends Controller
{
$service = new ReviewLikeService();
$service->handle($id);
$likeCount = $service->handle($id);
return $this->jsonSuccess();
return $this->jsonSuccess(['like_count' => $likeCount]);
}
/**
@ -89,9 +89,9 @@ class ReviewController extends Controller
{
$service = new ReviewLikeService();
$service->handle($id);
$likeCount = $service->handle($id);
return $this->jsonSuccess();
return $this->jsonSuccess(['like_count' => $likeCount]);
}
}

View File

@ -0,0 +1,132 @@
<?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();
$favoriteCount = $service->handle($id);
return $this->jsonSuccess(['favorite_count' => $favoriteCount]);
}
/**
* @Post("/{id:[0-9]+}/like", name="home.article.like")
*/
public function likeAction($id)
{
$service = new ArticleLikeService();
$likeCount = $service->handle($id);
return $this->jsonSuccess(['like_count' => $likeCount]);
}
}

View File

@ -100,11 +100,9 @@ class ChapterController extends Controller
{
$service = new ChapterLikeService();
$like = $service->handle($id);
$likeCount = $service->handle($id);
$msg = $like->deleted == 0 ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['msg' => $msg]);
return $this->jsonSuccess(['like_count' => $likeCount]);
}
/**

View File

@ -140,11 +140,9 @@ class ConsultController extends Controller
{
$service = new ConsultLikeService();
$like = $service->handle($id);
$likeCount = $service->handle($id);
$msg = $like->deleted == 0 ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['msg' => $msg]);
return $this->jsonSuccess(['like_count' => $likeCount]);
}
}

View File

@ -178,11 +178,9 @@ class CourseController extends Controller
{
$service = new CourseFavoriteService();
$favorite = $service->handle($id);
$favoriteCount = $service->handle($id);
$msg = $favorite->deleted == 0 ? '收藏成功' : '取消收藏成功';
return $this->jsonSuccess(['msg' => $msg]);
return $this->jsonSuccess(['favorite_count' => $favoriteCount]);
}
}

View File

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

View File

@ -109,11 +109,9 @@ class ReviewController extends Controller
{
$service = new ReviewLikeService();
$like = $service->handle($id);
$likeCount = $service->handle($id);
$msg = $like->deleted == 0 ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['msg' => $msg]);
return $this->jsonSuccess(['like_count' => $likeCount]);
}
}

View 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) : '';
}
}

View File

@ -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),
];
}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View File

@ -0,0 +1,125 @@
{% extends 'templates/main.volt' %}
{% block content %}
{{ partial('macros/article') }}
{% set article_list_url = url({'for':'home.article.list'}) %}
{% set article_cate_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="{{ article_list_url }}">专栏</a>
<a href="{{ article_cate_url }}">{{ article.category.name }}</a>
<a><cite>文章详情</cite></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>
{% endblock %}
{% block link_css %}
{{ css_link('home/css/markdown.css') }}
{% endblock %}
{% block include_js %}
{{ js_include('home/js/article.show.js') }}
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
{%- macro source_type(type) %}
{% if type == 1 %}
原创
{% elseif type == 2 %}
转载
{% elseif type == 3 %}
翻译
{% else %}
未知
{% endif %}
{%- endmacro %}

View File

@ -4,7 +4,7 @@
{% elseif value == 2 %}
直播
{% elseif value == 3 %}
专栏
图文
{% elseif value == 4 %}
面授
{% endif %}
@ -34,13 +34,13 @@
<div class="course-card">
<span class="model layui-badge layui-bg-green">{{ model_info(course.model) }}</span>
<div class="cover">
<a href="{{ course_url }}">
<a href="{{ course_url }}" target="_blank">
<img src="{{ course.cover }}!cover_270" alt="{{ course.title }}" title="{{ course.title }}">
</a>
</div>
<div class="info">
<div class="title layui-elip">
<a href="{{ course_url }}" title="{{ course.title }}">{{ course.title }}</a>
<a href="{{ course_url }}" title="{{ course.title }}" target="_blank">{{ course.title }}</a>
</div>
<div class="meta">
{% if course.market_price > course.vip_price %}
@ -76,7 +76,7 @@
</div>
<div class="info">
<div class="title layui-elip">
<a href="{{ course_url }}" title="{{ course.title }}">{{ course.title }}</a>
<a href="{{ course_url }}" title="{{ course.title }}" target="_blank">{{ course.title }}</a>
</div>
<div class="meta">
{% if course.market_price > 0 %}
@ -98,7 +98,7 @@
{% set course_url = url({'for':'home.course.show','id':item.course.id}) %}
<div class="course-card">
<div class="cover">
<a href="{{ course_url }}" title="{{ course_title }}">
<a href="{{ course_url }}" title="{{ course_title }}" target="_blank">
<img src="{{ item.course.cover }}!cover_270" alt="{{ course_title }}">
</a>
</div>

View File

@ -52,10 +52,10 @@
<p class="order">{{ event_info.order.subject }}</p>
{% elseif history.event_type == 2 %}
{% set gift_url = url({'for':'home.point_gift.show','id':event_info.point_redeem.gift_id}) %}
<p class="gift"><a href="{{ gift_url }}">{{ event_info.point_redeem.gift_name }}</a></p>
<p class="gift"><a href="{{ gift_url }}" target="_blank">{{ event_info.point_redeem.gift_name }}</a></p>
{% elseif history.event_type == 3 %}
{% set gift_url = url({'for':'home.point_gift.show','id':event_info.point_redeem.gift_id}) %}
<p class="gift"><a href="{{ gift_url }}">{{ event_info.point_redeem.gift_name }}</a></p>
<p class="gift"><a href="{{ gift_url }}" target="_blank">{{ event_info.point_redeem.gift_name }}</a></p>
{% elseif history.event_type == 4 %}
<span class="none">N/A</span>
{% elseif history.event_type == 5 %}
@ -63,11 +63,11 @@
{% elseif history.event_type == 6 %}
{% set course_url = url({'for':'home.course.show','id':event_info.course.id}) %}
{% set chapter_url = url({'for':'home.chapter.show','id':event_info.chapter.id}) %}
<p class="course">课程:<a href="{{ course_url }}">{{ event_info.course.title }}</a></p>
<p class="chapter">章节:<a href="{{ chapter_url }}">{{ event_info.chapter.title }}</a></p>
<p class="course">课程:<a href="{{ course_url }}" target="_blank">{{ event_info.course.title }}</a></p>
<p class="chapter">章节:<a href="{{ chapter_url }}" target="_blank">{{ event_info.chapter.title }}</a></p>
{% elseif history.event_type == 7 %}
{% set course_url = url({'for':'home.course.show','id':event_info.course.id}) %}
<p class="course"><a href="{{ course_url }}">{{ event_info.course.title }}</a></p>
<p class="course"><a href="{{ course_url }}" target="_blank">{{ event_info.course.title }}</a></p>
{% elseif history.event_type == 8 %}
<span class="none">N/A</span>
{% endif %}

View File

@ -8,12 +8,12 @@
<div class="layui-col-md3">
<div class="user-card">
<div class="avatar">
<a href="{{ user_url }}" title="{{ item.about }}">
<a href="{{ user_url }}" title="{{ item.about }}" target="_blank">
<img src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</a>
</div>
<div class="name layui-elip">
<a href="{{ user_url }}" title="{{ item.about }}">{{ item.name }}</a>
<a href="{{ user_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a>
</div>
<div class="title layui-elip">{{ item.title }}</div>
<div class="action">

View File

@ -33,7 +33,7 @@
{% set allow_review = item.progress > 30 and item.reviewed == 0 %}
<tr>
<td>
<p>标题:<a href="{{ course_url }}">{{ item.course.title }}</a></p>
<p>标题:<a href="{{ course_url }}" target="_blank">{{ item.course.title }}</a></p>
<p class="meta">
类型:<span class="layui-badge layui-bg-gray">{{ model_info(item.course.model) }}</span>
来源:<span class="layui-badge layui-bg-gray">{{ source_type_info(item.source_type) }}</span>

View File

@ -33,7 +33,7 @@
{% set favorite_url = url({'for':'home.course.favorite','id':item.id}) %}
<tr>
<td>
<a href="{{ course_url }}">{{ item.title }}</a>
<a href="{{ course_url }}" target="_blank">{{ item.title }}</a>
<span class="layui-badge layui-bg-gray">{{ model_info(item.model) }}</span>
</td>
<td>{{ item.user_count }}</td>

View File

@ -26,7 +26,7 @@
<td class="center">
<img class="avatar-sm" src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</td>
<td><a href="{{ show_url }}" title="{{ item.about }}">{{ item.name }}</a></td>
<td><a href="{{ show_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a></td>
<td>{{ type_info(item.type) }}</td>
<td><a href="{{ owner_url }}">{{ item.owner.name }}</a></td>
<td class="center">

View File

@ -27,7 +27,7 @@
<td class="center">
<img class="avatar-sm" src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</td>
<td><a href="{{ show_url }}" title="{{ item.about }}">{{ item.name }}</a></td>
<td><a href="{{ show_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a></td>
<td>{{ type_info(item.type) }}</td>
<td>{{ item.user_count }}</td>
<td>{{ item.msg_count }}</td>

View File

@ -32,7 +32,7 @@
{% set delete_url = url({'for':'home.review.delete','id':item.id}) %}
<tr>
<td>
<p class="title layui-elip">课程:<a href="{{ course_url }}">{{ item.course.title }}</a></p>
<p class="title layui-elip">课程:<a href="{{ course_url }}" target="_blank">{{ item.course.title }}</a></p>
<p class="content layui-elip" title="{{ item.content }}">评价:{{ item.content }}</p>
</td>
<td>

View File

@ -7,16 +7,16 @@
{% set item.title = item.title ? item.title : '暂露头角' %}
{% set item.about = item.about ? item.about : '这个人很懒,什么都没留下' %}
{% set user_url = url({'for':'home.user.show','id':item.id}) %}
{% set avatar_class = item.vip == 1 ? 'avatar vip' : 'avatar' %}
<div class="layui-col-md2">
<div class="user-card">
{{ vip_info(item.vip) }}
<div class="avatar">
<a href="{{ user_url }}" title="{{ item.about }}">
<div class="{{ avatar_class }}">
<a href="{{ user_url }}" title="{{ item.about }}" target="_blank">
<img src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</a>
</div>
<div class="name layui-elip">
<a href="{{ user_url }}" title="{{ item.about }}">{{ item.name }}</a>
<a href="{{ user_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a>
</div>
<div class="title layui-elip">{{ item.title }}</div>
<div class="action">

View File

@ -1,14 +1,14 @@
{{ partial('macros/group') }}
{% if pager.total_pages > 0 %}
<div class="user-list clearfix">
<div class="group-list clearfix">
<div class="layui-row layui-col-space20">
{% for item in pager.items %}
{% set group_url = url({'for':'home.im_group.show','id':item.id}) %}
{% set item.about = item.about ? item.about : '这家伙真懒,什么都没留下!' %}
<div class="layui-col-md3">
<div class="user-card">
{{ type_info(item.type) }}
<span class="type layui-badge layui-bg-green">{{ type_info(item.type) }}</span>
<div class="avatar">
<a href="{{ group_url }}" title="{{ item.about }}">
<img src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">

View File

@ -1,5 +1,3 @@
{{ partial('macros/user') }}
{% if pager.total_pages > 0 %}
<div class="user-list vip-user-list clearfix">
<div class="layui-row layui-col-space20">
@ -9,14 +7,13 @@
{% set item.about = item.about ? item.about : '这个人很懒,什么都没留下' %}
<div class="layui-col-md2">
<div class="user-card">
{{ vip_info(item.vip) }}
<div class="avatar">
<a href="{{ user_url }}" title="{{ item.about }}">
<a href="{{ user_url }}" title="{{ item.about }}" target="_blank">
<img src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</a>
</div>
<div class="name layui-elip">
<a href="{{ user_url }}" title="{{ item.about }}">{{ item.name }}</a>
<a href="{{ user_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a>
</div>
<div class="title layui-elip">{{ item.title }}</div>
</div>

View File

@ -203,6 +203,16 @@ function kg_default_group_avatar_path()
return '/img/default/group_avatar.png';
}
/**
* 获取默认文章封面路径
*
* @return string
*/
function kg_default_article_cover_path()
{
return '/img/default/article_cover.png';
}
/**
* 获取默认课程封面路径
*
@ -303,6 +313,20 @@ function kg_cos_group_avatar_url($path, $style = null)
return kg_cos_img_url($path, $style);
}
/**
* 获取文章封面URL
*
* @param string $path
* @param string $style
* @return string
*/
function kg_cos_article_cover_url($path, $style = null)
{
$path = $path ?: kg_default_article_cover_path();
return kg_cos_img_url($path, $style);
}
/**
* 获取课程封面URL
*
@ -402,6 +426,22 @@ function kg_parse_markdown($content)
return $parser->convertToHtml($content);
}
/**
* 解析内容摘要
*
* @param $content
* @param int $length
* @return string
*/
function kg_parse_summary($content, $length = 100)
{
$content = kg_parse_markdown($content);
$content = strip_tags($content);
return kg_substr($content, 0, $length);
}
/**
* 隐藏部分字符
*

View File

@ -21,6 +21,11 @@ class UserDailyCounter extends Listener
$this->counter->hIncrBy($user->id, 'favorite_count');
}
public function incrCommentCount(PhEvent $event, $source, UserModel $user)
{
$this->counter->hIncrBy($user->id, 'comment_count');
}
public function incrDanmuCount(PhEvent $event, $source, UserModel $user)
{
$this->counter->hIncrBy($user->id, 'danmu_count');
@ -56,4 +61,9 @@ class UserDailyCounter extends Listener
$this->counter->hIncrBy($user->id, 'review_like_count');
}
public function incrArticleLikeCount(PhEvent $event, $source, UserModel $user)
{
$this->counter->hIncrBy($user->id, 'article_like_count');
}
}

268
app/Models/Article.php Normal file
View File

@ -0,0 +1,268 @@
<?php
namespace App\Models;
use App\Caches\MaxArticleId as MaxArticleIdCache;
use App\Services\Sync\ArticleIndex as ArticleIndexSync;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
use Phalcon\Text;
class Article extends Model
{
/**
* 来源类型
*/
const SOURCE_ORIGIN = 1; // 原创
const SOURCE_REPRINT = 2; // 转载
const SOURCE_TRANSLATE = 3; // 翻译
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 标题
*
* @var string
*/
public $title = '';
/**
* 封面
*
* @var string
*/
public $cover = '';
/**
* 简介
*
* @var string
*/
public $summary = '';
/**
* 内容
*
* @var string
*/
public $content = '';
/**
* 标签
*
* @var array|string
*/
public $tags = [];
/**
* 作者编号
*
* @var int
*/
public $owner_id = 0;
/**
* 分类编号
*
* @var int
*/
public $category_id = 0;
/**
* 来源类型
*
* @var int
*/
public $source_type = 0;
/**
* 来源网址
*
* @var string
*/
public $source_url = '';
/**
* 推荐标识
*
* @var int
*/
public $featured = 0;
/**
* 发布标识
*
* @var int
*/
public $published = 0;
/**
* 删除标识
*
* @var int
*/
public $deleted = 0;
/**
* 允许评论
*
* @var int
*/
public $allow_comment = 0;
/**
* 文字数
*
* @var int
*/
public $word_count = 0;
/**
* 浏览数
*
* @var int
*/
public $view_count = 0;
/**
* 评论数
*
* @var int
*/
public $comment_count = 0;
/**
* 点赞数
*
* @var int
*/
public $like_count = 0;
/**
* 收藏数
*
* @var int
*/
public $favorite_count = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_article';
}
public function initialize()
{
parent::initialize();
$this->keepSnapshots(true);
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
if (empty($this->cover)) {
$this->cover = kg_default_article_cover_path();
} elseif (Text::startsWith($this->cover, 'http')) {
$this->cover = self::getCoverPath($this->cover);
}
if (is_array($this->tags)) {
$this->tags = kg_json_encode($this->tags);
}
$this->create_time = time();
}
public function beforeUpdate()
{
if (time() - $this->update_time > 3 * 3600) {
$sync = new ArticleIndexSync();
$sync->addItem($this->id);
}
if (Text::startsWith($this->cover, 'http')) {
$this->cover = self::getCoverPath($this->cover);
}
if (is_array($this->tags)) {
$this->tags = kg_json_encode($this->tags);
}
if ($this->deleted == 1) {
$this->published = 0;
}
$this->update_time = time();
}
public function afterCreate()
{
$cache = new MaxArticleIdCache();
$cache->rebuild();
}
public function afterFetch()
{
if (!Text::startsWith($this->cover, 'http')) {
$this->cover = kg_cos_article_cover_url($this->cover);
}
if (is_string($this->tags)) {
$this->tags = json_decode($this->tags, true);
}
}
public static function getCoverPath($url)
{
if (Text::startsWith($url, 'http')) {
return parse_url($url, PHP_URL_PATH);
}
return $url;
}
public static function sourceTypes()
{
return [
self::SOURCE_ORIGIN => '原创',
self::SOURCE_REPRINT => '转载',
self::SOURCE_TRANSLATE => '翻译',
];
}
public static function sortTypes()
{
return [
'latest' => '最新',
'popular' => '最热',
'featured' => '推荐',
];
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Models;
class ArticleFavorite extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 文章编号
*
* @var int
*/
public $article_id = 0;
/**
* 用户编号
*
* @var int
*/
public $user_id = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
public function getSource(): string
{
return 'kg_article_favorite';
}
public function beforeCreate()
{
$this->create_time = time();
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Models;
class ArticleLike extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 文章编号
*
* @var int
*/
public $article_id = 0;
/**
* 用户编号
*
* @var int
*/
public $user_id = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
public function getSource(): string
{
return 'kg_article_like';
}
public function beforeCreate()
{
$this->create_time = time();
}
}

46
app/Models/ArticleTag.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace App\Models;
class ArticleTag extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 文章编号
*
* @var int
*/
public $article_id = 0;
/**
* 标签编号
*
* @var int
*/
public $tag_id = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
public function getSource(): string
{
return 'kg_article_tag';
}
public function beforeCreate()
{
$this->create_time = time();
}
}

View File

@ -13,6 +13,7 @@ class Category extends Model
*/
const TYPE_COURSE = 1; // 课程
const TYPE_HELP = 2; // 帮助
const TYPE_ARTICLE = 3; // 文章
/**
* 主键编号
@ -141,6 +142,7 @@ class Category extends Model
return [
self::TYPE_COURSE => '课程',
self::TYPE_HELP => '帮助',
self::TYPE_ARTICLE => '专栏',
];
}

View File

@ -170,6 +170,13 @@ class Chapter extends Model
*/
public $consult_count = 0;
/**
* 评论数
*
* @var int
*/
public $comment_count = 0;
/**
* 点赞数
*

View File

@ -33,13 +33,6 @@ class ChapterLike extends Model
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_chapter_like';
@ -50,9 +43,4 @@ class ChapterLike extends Model
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

158
app/Models/Comment.php Normal file
View File

@ -0,0 +1,158 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class Comment extends Model
{
/**
* 条目类型
*/
const ITEM_CHAPTER = 1; // 章节
const ITEM_ARTICLE = 2; // 文章
const ITEM_ANSWER = 3; // 回答
/**
* 主键编号
*
* @var integer
*/
public $id;
/**
* 内容
*
* @var string
*/
public $content;
/**
* 父级编号
*
* @var integer
*/
public $parent_id;
/**
* 作者编号
*
* @var integer
*/
public $owner_id;
/**
* 目标用户
*
* @var integer
*/
public $to_user_id;
/**
* 条目编号
*
* @var integer
*/
public $item_id;
/**
* 条目类型
*
* @var integer
*/
public $item_type;
/**
* 终端类型
*
* @var integer
*/
public $client_type;
/**
* 终端IP
*
* @var integer
*/
public $client_ip;
/**
* 发布标识
*
* @var integer
*/
public $published;
/**
* 删除标识
*
* @var integer
*/
public $deleted;
/**
* 回复数
*
* @var integer
*/
public $reply_count;
/**
* 点赞数
*
* @var integer
*/
public $like_count;
/**
* 创建时间
*
* @var integer
*/
public $create_time;
/**
* 更新时间
*
* @var integer
*/
public $update_time;
public function getSource(): string
{
return 'kg_comment';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
public static function itemTypes()
{
return [
self::ITEM_CHAPTER => '章节',
self::ITEM_ARTICLE => '文章',
self::ITEM_ANSWER => '回答',
];
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Models;
class CommentLike extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 评论编号
*
* @var int
*/
public $comment_id = 0;
/**
* 用户编号
*
* @var int
*/
public $user_id = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
public function getSource(): string
{
return 'kg_comment_like';
}
public function beforeCreate()
{
$this->create_time = time();
}
}

View File

@ -33,13 +33,6 @@ class ConsultLike extends Model
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_consult_like';
@ -50,9 +43,4 @@ class ConsultLike extends Model
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -16,7 +16,7 @@ class Course extends Model
*/
const MODEL_VOD = 1; // 点播
const MODEL_LIVE = 2; // 直播
const MODEL_READ = 3; // 专栏
const MODEL_READ = 3; // 图文
const MODEL_OFFLINE = 4; // 面授
/**
@ -391,7 +391,7 @@ class Course extends Model
return [
self::MODEL_VOD => '点播',
self::MODEL_LIVE => '直播',
self::MODEL_READ => '专栏',
self::MODEL_READ => '图文',
self::MODEL_OFFLINE => '面授',
];
}

View File

@ -33,13 +33,6 @@ class CourseFavorite extends Model
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_course_favorite';
@ -50,9 +43,4 @@ class CourseFavorite extends Model
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -33,13 +33,6 @@ class ReviewLike extends Model
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_review_like';
@ -50,9 +43,4 @@ class ReviewLike extends Model
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

Some files were not shown because too many files have changed in this diff Show More