1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-26 12:23:06 +08:00

Merge branch 'koogua/I3CC5N' into demo

This commit is contained in:
koogua 2021-03-26 15:15:59 +08:00
commit 2310c1d3ca
82 changed files with 1623 additions and 593 deletions

View File

@ -1,3 +1,13 @@
### [v1.3.0](https://gitee.com/koogua/course-tencent-cloud/releases/v1.3.0)(2021-03-26)
### 更新
- 课程增加面授模型
- 重构前台群组成员管理
- 后台增加群组成员管理
- 重构订单存储商品详情数据结构
- 调整用户和群组列表等UI
### [v1.2.9](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.9)(2021-03-22)
### 更新

View File

@ -14,12 +14,12 @@
### 系统功能
实现了点播、直播、专栏、会员、微聊等,是一个完整的产品,具体功能我也不想写一大堆,自己体验吧!
实现了点播、直播、专栏、面授、会员、群组、积分商城、秒杀等全功能无阉割100%真开源在线教育解决方案。具体功能我也不想写一大堆,自己体验吧!
友情提示:
- 系统配置低1核 1G 1M 跑多个容器),切莫压测
- 课程数据来源于网络(无实质内容)切莫购买
- 演示系统配置低1Core1G1M 跑多个容器)切莫压测
- 课程数据来源于网络(无实质内容)切莫购买
- 管理后台已禁止数据提交,私密配置已过滤
桌面端演示:
@ -27,13 +27,13 @@
- [前台演示](https://ctc.koogua.com)
- [后台演示](https://ctc.koogua.com/admin)
演示100015@163.com / 123456 (前后台通用)
演示100015@163.com / 123456 (前后台通用)
移动端演示:
![移动端二维码](https://images.gitee.com/uploads/images/2020/1127/093203_265221a2_23592.png)
演示13507083515 / 123456
演示13507083515 / 123456
支付流程演示:
@ -42,10 +42,10 @@
- [数据库与中间件的基础必修课0.02元)](https://ctc.koogua.com/order/confirm?item_id=80&item_type=2)
Tips: 测试支付请用手机号注册一个新账户,以便接收订单通知,以及避免课程无法购买
即时通讯演示:
请使用以下两个号在不同终端或者浏览器登录,打开微聊界面
请使用以下两个号在不同终端或者浏览器登录,打开微聊界面
- 帐号A100015@163.com / 123456
- 帐号B100065@163.com / 123456

View File

@ -8,6 +8,17 @@ use App\Repos\User as UserRepo;
class ImGroupList extends Builder
{
public function handleGroups(array $groups)
{
$baseUrl = kg_cos_url();
foreach ($groups as $key => $group) {
$groups[$key]['avatar'] = $baseUrl . $group['avatar'];
}
return $groups;
}
public function handleCourses(array $groups)
{
$courses = $this->getCourses($groups);

View File

@ -2,6 +2,7 @@
namespace App\Console\Tasks;
use App\Models\Course as CourseModel;
use App\Models\CourseUser as CourseUserModel;
use App\Models\ImGroupUser as ImGroupUserModel;
use App\Models\Order as OrderModel;
@ -105,13 +106,19 @@ class DeliverTask extends Task
protected function handleCourseOrder(OrderModel $order)
{
$itemInfo = $order->item_info;
$course = $order->item_info['course'];
if ($course['model'] == CourseModel::MODEL_OFFLINE) {
$expiryTime = strtotime($course['attrs']['end_date']);
} else {
$expiryTime = $course['study_expiry_time'];
}
$courseUser = new CourseUserModel();
$courseUser->user_id = $order->owner_id;
$courseUser->course_id = $order->item_id;
$courseUser->expiry_time = $itemInfo['course']['study_expiry_time'];
$courseUser->expiry_time = $expiryTime;
$courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->source_type = CourseUserModel::SOURCE_CHARGE;

View File

@ -80,7 +80,7 @@ class UpgradeTask extends Task
$redis->del($statsKey);
}
echo "end reset metadata..." . PHP_EOL;
echo "------ end reset metadata ------" . PHP_EOL;
}
/**

View File

@ -79,10 +79,17 @@ class ChapterController extends Controller
$chapter = $chapterService->createChapter();
$location = $this->url->get([
'for' => 'admin.course.chapters',
'id' => $chapter->course_id,
]);
if ($chapter->parent_id > 0) {
$location = $this->url->get([
'for' => 'admin.chapter.lessons',
'id' => $chapter->parent_id,
]);
} else {
$location = $this->url->get([
'for' => 'admin.course.chapters',
'id' => $chapter->course_id,
]);
}
$content = [
'location' => $location,
@ -131,6 +138,10 @@ class ChapterController extends Controller
$read = $contentService->getChapterRead($chapter->id);
$this->view->setVar('read', $read);
break;
case CourseModel::MODEL_OFFLINE:
$offline = $contentService->getChapterOffline($chapter->id);
$this->view->setVar('offline', $offline);
break;
}
}

View File

@ -10,6 +10,22 @@ use App\Http\Admin\Services\ImGroup as ImGroupService;
class ImGroupController extends Controller
{
/**
* @Get("/{id:[0-9]+}/users", name="admin.im_group.users")
*/
public function usersAction($id)
{
$service = new ImGroupService();
$group = $service->getGroup($id);
$pager = $service->getGroupUsers($id);
$this->view->pick('im/group/users');
$this->view->setVar('group', $group);
$this->view->setVar('pager', $pager);
}
/**
* @Get("/list", name="admin.im_group.list")
*/

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\ImGroupUser as ImGroupUserService;
/**
* @RoutePrefix("/admin/im/group/user")
*/
class ImGroupUserController extends Controller
{
/**
* @Post("/delete", name="admin.im_group_user.delete")
*/
public function deleteAction()
{
$groupService = new ImGroupUserService();
$groupService->deleteGroupUser();
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除成员成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -381,6 +381,18 @@ class AuthNode extends Service
'type' => 'button',
'route' => 'admin.im_group.delete',
],
[
'id' => '2-4-6',
'title' => '群员列表',
'type' => 'button',
'route' => 'admin.im_group.users',
],
[
'id' => '2-4-7',
'title' => '删除群员',
'type' => 'button',
'route' => 'admin.im_group_user.delete',
],
],
],
[

View File

@ -7,6 +7,7 @@ use App\Caches\Chapter as ChapterCache;
use App\Caches\CourseChapterList as CatalogCache;
use App\Models\Chapter as ChapterModel;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Models\ChapterOffline as ChapterOfflineModel;
use App\Models\ChapterRead as ChapterReadModel;
use App\Models\ChapterVod as ChapterVodModel;
use App\Models\Course as CourseModel;
@ -25,9 +26,7 @@ class Chapter extends Service
$resources = $resourceRepo->findByChapterId($id);
if ($resources->count() == 0) {
return [];
}
if ($resources->count() == 0) return [];
$builder = new ResourceListBuilder();
@ -118,6 +117,10 @@ class Chapter extends Service
$chapterRead = new ChapterReadModel();
$attrs = $chapterRead->create($data);
break;
case CourseModel::MODEL_OFFLINE:
$chapterOffline = new ChapterOfflineModel();
$attrs = $chapterOffline->create($data);
break;
}
if ($attrs === false) {
@ -137,10 +140,11 @@ class Chapter extends Service
$this->db->rollback();
$logger = $this->getLogger();
$logger = $this->getLogger('http');
$logger->error('Create Chapter Error ' . kg_json_encode([
'code' => $e->getCode(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'message' => $e->getMessage(),
]));
@ -176,7 +180,7 @@ class Chapter extends Service
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
if ($chapter->published == 0 && $post['published'] == 1) {
if ($post['published'] == 1) {
$validator->checkPublishAbility($chapter);
}
}
@ -259,6 +263,8 @@ class Chapter extends Service
$courseStats->updateLiveAttrs($course->id);
} elseif ($course->model == CourseModel::MODEL_READ) {
$courseStats->updateReadAttrs($course->id);
} elseif ($course->model == CourseModel::MODEL_OFFLINE) {
$courseStats->updateOfflineAttrs($course->id);
}
}

View File

@ -12,6 +12,7 @@ use App\Services\ChapterVod as ChapterVodService;
use App\Services\CourseStat as CourseStatService;
use App\Services\Vod as VodService;
use App\Validators\ChapterLive as ChapterLiveValidator;
use App\Validators\ChapterOffline as ChapterOfflineValidator;
use App\Validators\ChapterRead as ChapterReadValidator;
use App\Validators\ChapterVod as ChapterVodValidator;
@ -39,6 +40,13 @@ class ChapterContent extends Service
return $chapterRepo->findChapterRead($chapterId);
}
public function getChapterOffline($chapterId)
{
$chapterRepo = new ChapterRepo();
return $chapterRepo->findChapterOffline($chapterId);
}
public function getPlayUrls($chapterId)
{
$service = new ChapterVodService();
@ -64,6 +72,9 @@ class ChapterContent extends Service
case CourseModel::MODEL_READ:
$this->updateChapterRead($chapter);
break;
case CourseModel::MODEL_OFFLINE:
$this->updateChapterOffline($chapter);
break;
}
$this->rebuildCatalogCache($chapter);
@ -84,9 +95,7 @@ class ChapterContent extends Service
/**
* 无新文件上传
*/
if ($fileId == $vod->file_id) {
return;
}
if ($fileId == $vod->file_id) return;
/**
* 删除旧文件
@ -95,21 +104,17 @@ class ChapterContent extends Service
$this->deleteVodFile($vod->file_id);
}
$vod->update([
'file_id' => $fileId,
'file_transcode' => '',
]);
$vod->file_id = $fileId;
$vod->file_transcode = [];
$vod->update();
/**
* @var array $attrs
*/
$attrs = $chapter->attrs;
$attrs['duration'] = 0;
$attrs['file']['status'] = ChapterModel::FS_UPLOADED;
$chapter->attrs = $attrs;
$chapter->update(['attrs' => $attrs]);
$chapter->update();
$this->updateCourseVodAttrs($vod->course_id);
}
@ -129,20 +134,17 @@ class ChapterContent extends Service
$validator->checkTimeRange($startTime, $endTime);
$live->update([
'start_time' => $startTime,
'end_time' => $endTime,
]);
$live->start_time = $startTime;
$live->end_time = $endTime;
$live->update();
/**
* @var array $attrs
*/
$attrs = $chapter->attrs;
$attrs['start_time'] = $startTime;
$attrs['end_time'] = $endTime;
$chapter->attrs = $attrs;
$chapter->update(['attrs' => $attrs]);
$chapter->update();
$this->updateCourseLiveAttrs($live->course_id);
}
@ -161,9 +163,6 @@ class ChapterContent extends Service
$read->update(['content' => $content]);
/**
* @var array $attrs
*/
$attrs = $chapter->attrs;
$attrs['word_count'] = WordUtil::getWordCount($content);
@ -174,6 +173,36 @@ class ChapterContent extends Service
$this->updateCourseReadAttrs($read->course_id);
}
protected function updateChapterOffline(ChapterModel $chapter)
{
$post = $this->request->getPost();
$chapterRepo = new ChapterRepo();
$offline = $chapterRepo->findChapterOffline($chapter->id);
$validator = new ChapterOfflineValidator();
$startTime = $validator->checkStartTime($post['start_time']);
$endTime = $validator->checkEndTime($post['end_time']);
$validator->checkTimeRange($startTime, $endTime);
$offline->start_time = $startTime;
$offline->end_time = $endTime;
$offline->update();
$attrs = $chapter->attrs;
$attrs['start_time'] = $startTime;
$attrs['end_time'] = $endTime;
$chapter->attrs = $attrs;
$chapter->update();
$this->updateCourseOfflineAttrs($offline->course_id);
}
protected function updateCourseVodAttrs($courseId)
{
$statService = new CourseStatService();
@ -195,6 +224,13 @@ class ChapterContent extends Service
$statService->updateReadAttrs($courseId);
}
protected function updateCourseOfflineAttrs($courseId)
{
$statService = new CourseStatService();
$statService->updateOfflineAttrs($courseId);
}
protected function deleteVodFile($fileId)
{
$vodService = new VodService();

View File

@ -26,6 +26,7 @@ use App\Repos\ImGroup as ImGroupRepo;
use App\Repos\User as UserRepo;
use App\Services\Sync\CourseIndex as CourseIndexSync;
use App\Validators\Course as CourseValidator;
use App\Validators\CourseOffline as CourseOfflineValidator;
class Course extends Service
{
@ -114,7 +115,7 @@ class Course extends Service
$this->db->rollback();
$logger = $this->getLogger();
$logger = $this->getLogger('http');
$logger->error('Create Course Error ' . kg_json_encode([
'code' => $e->getCode(),
@ -159,17 +160,24 @@ class Course extends Service
$data['level'] = $validator->checkLevel($post['level']);
}
if (isset($post['price_mode'])) {
if ($post['price_mode'] == 'free') {
$data['market_price'] = 0;
$data['vip_price'] = 0;
} else {
$data['origin_price'] = $validator->checkOriginPrice($post['origin_price']);
$data['market_price'] = $validator->checkMarketPrice($post['market_price']);
$data['vip_price'] = $validator->checkVipPrice($post['vip_price']);
$data['study_expiry'] = $validator->checkStudyExpiry($post['study_expiry']);
$data['refund_expiry'] = $validator->checkRefundExpiry($post['refund_expiry']);
}
if (isset($post['study_expiry'])) {
$data['study_expiry'] = $validator->checkStudyExpiry($post['study_expiry']);
}
if (isset($post['refund_expiry'])) {
$data['refund_expiry'] = $validator->checkRefundExpiry($post['refund_expiry']);
}
if (isset($post['origin_price'])) {
$data['origin_price'] = $validator->checkOriginPrice($post['origin_price']);
}
if (isset($post['market_price'])) {
$data['market_price'] = $validator->checkMarketPrice($post['market_price']);
}
if (isset($post['vip_price'])) {
$data['vip_price'] = $validator->checkVipPrice($post['vip_price']);
}
if (isset($post['featured'])) {
@ -195,6 +203,28 @@ class Course extends Service
$this->saveRelatedCourses($course, $post['xm_course_ids']);
}
if ($course->model == CourseModel::MODEL_OFFLINE) {
$validator = new CourseOfflineValidator();
$data['study_expiry'] = 0;
$data['refund_expiry'] = 0;
if (isset($post['attrs']['start_date']) && isset($post['attrs']['end_date'])) {
$data['attrs']['start_date'] = $validator->checkStartDate($post['attrs']['start_date']);
$data['attrs']['end_date'] = $validator->checkEndDate($post['attrs']['end_date']);
$validator->checkDateRange($data['attrs']['start_date'], $data['attrs']['end_date']);
}
if (isset($post['attrs']['user_limit'])) {
$data['attrs']['user_limit'] = $validator->checkUserLimit($post['attrs']['user_limit']);
}
if (isset($post['attrs']['location'])) {
$data['attrs']['location'] = $validator->checkLocation($post['attrs']['location']);
}
}
$course->update($data);
$this->updateImGroup($course);

View File

@ -3,13 +3,15 @@
namespace App\Http\Admin\Services;
use App\Builders\ImGroupList as ImGroupListBuilder;
use App\Builders\ImGroupUserList as ImGroupUserListBuilder;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\ImGroup as ImGroupModel;
use App\Models\ImGroupUser as ImGroupUserModel;
use App\Models\User as UserModel;
use App\Models\ImUser as ImUserModel;
use App\Repos\ImGroup as ImGroupRepo;
use App\Repos\ImGroupUser as ImGroupUserRepo;
use App\Validators\ImGroup as ImGroupValidator;
use App\Validators\ImGroupUser as ImGroupUserValidator;
class ImGroup extends Service
{
@ -89,9 +91,10 @@ class ImGroup extends Service
}
if (isset($post['owner_id'])) {
$owner = $validator->checkGroupOwner($post['owner_id']);
$data['owner_id'] = $owner->id;
$this->handleGroupOwner($group, $owner);
$validator = new ImGroupUserValidator();
$user = $validator->checkUser($post['owner_id']);
$data['owner_id'] = $user->id;
$this->handleGroupOwner($group, $user);
}
$group->update($data);
@ -121,7 +124,42 @@ class ImGroup extends Service
return $group;
}
protected function handleGroupOwner(ImGroupModel $group, UserModel $user)
public function getGroupUsers($id)
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['group_id'] = $id;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$groupUserRepo = new ImGroupUserRepo();
$pager = $groupUserRepo->paginate($params, $sort, $page, $limit);
return $this->handleGroupUsers($pager);
}
protected function handleGroupUsers($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$builder = new ImGroupUserListBuilder();
$stepA = $pager->items->toArray();
$stepB = $builder->handleUsers($stepA);
$pager->items = $stepB;
return $pager;
}
protected function handleGroupOwner(ImGroupModel $group, ImUserModel $user)
{
$repo = new ImGroupUserRepo();
@ -130,21 +168,40 @@ class ImGroup extends Service
if ($groupUser) return;
$groupUser = new ImGroupUserModel();
$groupUser->group_id = $group->id;
$groupUser->user_id = $user->id;
$groupUser->create();
$this->incrGroupUserCount($group);
$this->incrUserGroupCount($user);
}
protected function incrGroupUserCount(ImGroupModel $group)
{
$group->user_count += 1;
$group->update();
}
protected function incrUserGroupCount(ImUserModel $user)
{
$user->group_count += 1;
$user->update();
}
protected function handleGroups($pager)
{
if ($pager->total_items > 0) {
$builder = new ImGroupListBuilder();
$pipeA = $pager->items->toArray();
$items = $pager->items->toArray();
$pipeA = $builder->handleGroups($items);
$pipeB = $builder->handleUsers($pipeA);
$pipeC = $builder->objects($pipeB);

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\ImGroup as ImGroupModel;
use App\Models\ImUser as ImUserModel;
use App\Validators\ImGroupUser as ImGroupUserValidator;
class ImGroupUser extends Service
{
public function deleteGroupUser()
{
$groupId = $this->request->getQuery('group_id', 'int', 0);
$userId = $this->request->getQuery('user_id', 'int', 0);
$validator = new ImGroupUserValidator();
$group = $validator->checkGroup($groupId);
$user = $validator->checkUser($userId);
$validator->checkIfAllowDelete($groupId, $userId);
$groupUser = $this->findOrFail($groupId, $userId);
$groupUser->delete();
$this->decrGroupUserCount($group);
$this->decrUserGroupCount($user);
}
protected function decrGroupUserCount(ImGroupModel $group)
{
if ($group->user_count > 0) {
$group->user_count -= 1;
$group->update();
}
}
protected function decrUserGroupCount(ImUserModel $user)
{
if ($user->group_count > 0) {
$user->group_count -= 1;
$user->update();
}
}
protected function findOrFail($groupId, $userId)
{
$validator = new ImGroupUserValidator();
return $validator->checkGroupUser($groupId, $userId);
}
}

View File

@ -6,6 +6,7 @@ use App\Caches\CoursePackageList as CoursePackageListCache;
use App\Caches\Package as PackageCache;
use App\Caches\PackageCourseList as PackageCourseListCache;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Course as CourseModel;
use App\Models\CoursePackage as CoursePackageModel;
use App\Models\Package as PackageModel;
use App\Repos\Course as CourseRepo;
@ -32,7 +33,20 @@ class Package extends Service
$courseRepo = new CourseRepo();
$items = $courseRepo->findAll(['free' => 0, 'published' => 1]);
/**
* 面授课程不参与套餐计划,因为无法进行退款计算
*/
$model = [
CourseModel::MODEL_VOD,
CourseModel::MODEL_LIVE,
CourseModel::MODEL_READ,
];
$items = $courseRepo->findAll([
'model' => $model,
'free' => 0,
'published' => 1,
]);
if ($items->count() == 0) return [];

View File

@ -267,7 +267,9 @@ class User extends Service
$builder = new UserListBuilder();
$pipeA = $pager->items->toArray();
$items = $pager->items->toArray();
$pipeA = $builder->handleUsers($items);
$pipeB = $builder->handleAdminRoles($pipeA);
$pipeC = $builder->handleEduRoles($pipeB);
$pipeD = $builder->objects($pipeC);

View File

@ -9,6 +9,8 @@
直播信息
{% elseif model == '3' %}
图文信息
{% elseif model == '4' %}
面授信息
{% endif %}
{%- endmacro %}
@ -33,6 +35,8 @@
{{ partial('chapter/edit_lesson_live') }}
{% elseif course.model == 3 %}
{{ partial('chapter/edit_lesson_read') }}
{% elseif course.model == 4 %}
{{ partial('chapter/edit_lesson_offline') }}
{% endif %}
</div>
<div class="layui-tab-item">

View File

@ -0,0 +1,24 @@
{% set offline.start_time = offline.start_time > 0 ? date('Y-m-d H:i:s',offline.start_time) : '' %}
{% set offline.end_time = offline.end_time > 0 ? date('Y-m-d H:i:s',offline.end_time) : '' %}
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开始时间</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="start_time" autocomplete="off" value="{{ offline.start_time }}" 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="end_time" autocomplete="off" value="{{ offline.end_time }}" lay-verify="required">
</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>

View File

@ -27,6 +27,8 @@
{{ partial('chapter/lessons_live') }}
{% elseif course.model == 3 %}
{{ partial('chapter/lessons_read') }}
{% elseif course.model == 4 %}
{{ partial('chapter/lessons_offline') }}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,64 @@
{%- macro offline_time_info(attrs) %}
{% if attrs['start_time'] > 0 %}
<p>开始:{{ date('Y-m-d H:i',attrs['start_time']) }}</p>
<p>结束:{{ date('Y-m-d H:i',attrs['end_time']) }}</p>
{% else %}
N/A
{% endif %}
{%- endmacro %}
<table class="layui-table kg-table layui-form">
<colgroup>
<col>
<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 lessons %}
{% set edit_url = url({'for':'admin.chapter.edit','id':item.id}) %}
{% set update_url = url({'for':'admin.chapter.update','id':item.id}) %}
{% set delete_url = url({'for':'admin.chapter.delete','id':item.id}) %}
{% set restore_url = url({'for':'admin.chapter.restore','id':item.id}) %}
<tr>
<td>{{ item.id }}</td>
<td>
<span><a href="{{ edit_url }}">{{ item.title }}</a></span>
<span class="layui-badge layui-bg-green">课</span>
</td>
<td>{{ offline_time_info(item.attrs) }}</td>
<td><input class="layui-input kg-priority" type="text" name="priority" title="数值越小排序越靠前" value="{{ item.priority }}" data-url="{{ update_url }}"></td>
<td><input type="checkbox" name="free" value="1" lay-skin="switch" lay-text="是|否" lay-filter="free" data-url="{{ update_url }}" {% if item.free == 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="{{ 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="{{ delete_url }}">还原</a></li>
{% endif %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -50,7 +50,8 @@
var modelTips = {
'1': '通过音视频呈现课程内容,内容可视化,有图像有声音,适合大部分场景',
'2': '通过直播呈现课程内容,交互性强,适合需要交互反馈、情绪表达的场景',
'3': '通过图文呈现课程内容,简单直接,适合撰写文档、书籍、教程的场景'
'3': '通过图文呈现课程内容,简单直接,适合撰写文档、书籍、教程的场景',
'4': '面对面讲授课程内容,传统教学,适合有条件开展线下教学的场景',
};
var modelTipsBlock = $('#model-tips');

View File

@ -9,6 +9,9 @@
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">基本信息</li>
{% if course.model == 4 %}
<li>面授信息</li>
{% endif %}
<li>课程介绍</li>
<li>营销设置</li>
<li>相关课程</li>
@ -17,6 +20,11 @@
<div class="layui-tab-item layui-show">
{{ partial('course/edit_basic') }}
</div>
{% if course.model == 4 %}
<div class="layui-tab-item">
{{ partial('course/edit_offline') }}
</div>
{% endif %}
<div class="layui-tab-item">
{{ partial('course/edit_desc') }}
</div>
@ -53,23 +61,16 @@
xmSelect.render({
el: '#xm-category-ids',
name: 'xm_category_ids',
filterable: true,
max: 5,
prop: {
name: 'name',
value: 'id'
},
data: {{ xm_categories|json_encode }}
});
xmSelect.render({
el: '#xm-teacher-ids',
name: 'xm_teacher_ids',
paging: true,
filterable: true,
max: 5,
prop: {
name: 'name',
value: 'id'
},
data: {{ xm_teachers|json_encode }}
});
@ -86,19 +87,20 @@
<script>
layui.use(['jquery', 'form', 'layer'], function () {
layui.use(['jquery', 'form', 'layer', 'laydate'], function () {
var $ = layui.jquery;
var form = layui.form;
var layer = layui.layer;
var laydate = layui.laydate;
form.on('radio(price_mode)', function (data) {
var priceBlock = $('#price-block');
if (data.value === 'free') {
priceBlock.hide();
} else {
priceBlock.show();
}
laydate.render({
elem: 'input[name="attrs[start_date]"]',
type: 'date'
});
laydate.render({
elem: 'input[name="attrs[end_date]"]',
type: 'date'
});
$('.kg-submit').on('click', function () {

View File

@ -0,0 +1,33 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.course.update','id':course.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开始日期</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="attrs[start_date]" autocomplete="off" value="{{ course.attrs['start_date'] }}" 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="attrs[end_date]" autocomplete="off" value="{{ course.attrs['end_date'] }}" 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="attrs[location]" value="{{ course.attrs['location'] }}" placeholder="可以用于导航的地理位置" 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="attrs[user_limit]" value="{{ course.attrs['user_limit'] }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button id="sale-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>

View File

@ -1,42 +1,32 @@
{% set free = course.market_price == 0 %}
{% set price_display = course.market_price > 0 ? 'display:block' : 'display:none' %}
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.course.update','id':course.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">模式</label>
<div class="layui-input-block">
<input type="radio" name="price_mode" value="free" title="免费" lay-filter="price_mode" {% if free %}checked="checked"{% endif %}>
<input type="radio" name="price_mode" value="charge" title="收费" lay-filter="price_mode" {% if not free %}checked="checked"{% endif %}>
<div class="layui-inline">
<label class="layui-form-label">原始价格</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="origin_price" value="{{ course.origin_price }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">元</div>
</div>
</div>
<div id="price-block" style="{{ price_display }}">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">原始价格</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="origin_price" value="{{ course.origin_price }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">元</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">市场价格</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="market_price" value="{{ course.market_price }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">元</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">市场价格</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="market_price" value="{{ course.market_price }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">元</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">会员价格</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="vip_price" value="{{ course.vip_price }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">元</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">会员价格</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="vip_price" value="{{ course.vip_price }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">元</div>
</div>
</div>
{% if course.model in [1,2,3] %}
<div class="layui-form-item">
<label class="layui-form-label">学习期限</label>
<div class="layui-input-block">
@ -53,7 +43,7 @@
{% endfor %}
</div>
</div>
</div>
{% endif %}
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">

View File

@ -1,31 +0,0 @@
{%- macro study_expiry_info(value) %}
{% if value == 1 %}
1个月
{% elseif value == 3 %}
3个月
{% elseif value == 6 %}
6个月
{% elseif value == 12 %}
12个月
{% elseif value == 36 %}
36个月
{% else %}
N/A
{% endif %}
{%- endmacro %}
{%- macro refund_expiry_info(value) %}
{% if value == 7 %}
7天
{% elseif value == 14 %}
14天
{% elseif value == 30 %}
30天
{% elseif value == 90 %}
90天
{% elseif value == 180 %}
180天
{% else %}
N/A
{% endif %}
{%- endmacro %}

View File

@ -2,32 +2,7 @@
{% block content %}
{%- macro model_info(value) %}
{% if value == 1 %}
点播
{% elseif value == 2 %}
直播
{% elseif value == 3 %}
专栏
{% else %}
未知
{% endif %}
{%- endmacro %}
{%- macro level_info(value) %}
{% if value == 1 %}
入门
{% elseif value == 2 %}
初级
{% elseif value == 3 %}
中级
{% elseif value == 4 %}
高级
{% else %}
未知
{% endif %}
</span>
{%- endmacro %}
{{ partial('macros/course') }}
{% set add_url = url({'for':'admin.course.add'}) %}
{% set search_url = url({'for':'admin.course.search'}) %}

View File

@ -2,26 +2,7 @@
{% block content %}
{%- macro type_info(value) %}
{% if value == 1 %}
课程
{% elseif value == 2 %}
水吧
{% elseif value == 3 %}
职工
{% else %}
未知
{% endif %}
{%- endmacro %}
{%- macro owner_info(owner) %}
{% if owner.id is defined %}
{% set filter_url = url({'for':'admin.im_group.list'},{'owner_id':owner.id}) %}
<a href="{{ filter_url }}">{{ owner.name }}</a>{{ owner.id }}
{% else %}
未设置
{% endif %}
{%- endmacro %}
{{ partial('macros/group') }}
{% set add_url = url({'for':'admin.im_group.add'}) %}
{% set search_url = url({'for':'admin.im_group.search'}) %}
@ -44,7 +25,7 @@
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col width="10%">
<col>
<col>
<col>
@ -54,10 +35,10 @@
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>头像</th>
<th>名称</th>
<th>类型</th>
<th>群主</th>
<th>类型</th>
<th>成员</th>
<th>发布</th>
<th>操作</th>
@ -70,18 +51,28 @@
{% set update_url = url({'for':'admin.im_group.update','id':item.id}) %}
{% set delete_url = url({'for':'admin.im_group.delete','id':item.id}) %}
{% set restore_url = url({'for':'admin.im_group.restore','id':item.id}) %}
{% set users_url = url({'for':'admin.im_group.users','id':item.id}) %}
<tr>
<td>{{ item.id }}</td>
<td><a href="{{ edit_url }}">{{ item.name }}</a></td>
<td class="center">
<img class="avatar-sm" src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</td>
<td><a href="{{ preview_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a>{{ item.id }}</td>
<td>
{% if item.owner.id is defined %}
<a href="{{ url({'for':'home.user.show','id':item.owner.id}) }}" target="_blank">{{ item.owner.name }}</a>{{ item.owner.id }}
{% else %}
N/A
{% endif %}
</td>
<td> {{ type_info(item.type) }}</td>
<td>{{ owner_info(item.owner) }}</td>
<td><span class="layui-badge layui-bg-gray">{{ item.user_count }}</span></td>
<td><a href="{{ users_url }}" class="layui-badge layui-bg-green">{{ item.user_count }}</a></td>
<td><input type="checkbox" name="published" value="1" lay-filter="published" lay-skin="switch" lay-text="是|否" 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="{{ users_url }}">成员</a></li>
<li><a href="{{ edit_url }}">编辑</a></li>
{% if item.deleted == 0 %}
<li><a href="javascript:" class="kg-delete" data-url="{{ delete_url }}">删除</a></li>

View File

@ -0,0 +1,70 @@
{% extends 'templates/main.volt' %}
{% block content %}
{{ partial('macros/user') }}
{% set back_url = url({'for':'admin.im_group.list'}) %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a class="kg-back" href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a>
<a href="{{ back_url }}"><cite>群组列表</cite></a>
<a><cite>{{ group.name }}</cite></a>
<a><cite>成员管理</cite></a>
</span>
</div>
</div>
<table class="kg-table layui-table">
<colgroup>
<col width="10%">
<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>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
{% set user_url = url({'for':'home.user.show','id':item.user.id}) %}
{% set delete_url = url({'for':'admin.im_group_user.delete'},{'group_id':item.group_id,'user_id':item.user_id}) %}
{% set is_owner = item.user.id == group.owner_id ? 1 : 0 %}
{% set role_type = is_owner == 1 ? '群主' : '成员' %}
<tr>
<td class="center">
<img class="avatar-sm" src="{{ item.user.avatar }}!avatar_160" alt="{{ item.user.name }}">
</td>
<td><a href="{{ user_url }}" title="{{ item.user.about }}" target="_blank">{{ item.user.name }}</a>{{ item.user.id }}</td>
<td>{{ item.user.area }}</td>
<td>{{ gender_info(item.user.gender) }}</td>
<td>{{ role_type }}</td>
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
<td class="center">
{% if is_owner == 0 %}
<button class="layui-btn layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</button>
{% else %}
<button class="layui-btn layui-btn-disabled">删除</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endblock %}

View File

@ -0,0 +1,27 @@
{%- macro model_info(value) %}
{% if value == 1 %}
点播
{% elseif value == 2 %}
直播
{% elseif value == 3 %}
专栏
{% elseif value == 4 %}
面授
{% else %}
未知
{% endif %}
{%- endmacro %}
{%- macro level_info(value) %}
{% if value == 1 %}
入门
{% elseif value == 2 %}
初级
{% elseif value == 3 %}
中级
{% elseif value == 4 %}
高级
{% else %}
未知
{% endif %}
{%- endmacro %}

View File

@ -0,0 +1,11 @@
{%- macro type_info(value) %}
{% if value == 1 %}
课程
{% elseif value == 2 %}
水吧
{% elseif value == 3 %}
职工
{% else %}
未知
{% endif %}
{%- endmacro %}

View File

@ -0,0 +1,34 @@
{%- macro gender_info(value) %}
{% if value == 1 %}
{% elseif value == 2 %}
{% elseif value == 3 %}
{% endif %}
{%- endmacro %}
{%- macro edu_role_info(role) %}
{% if role.id == 1 %}
学员
{% elseif role.id == 2 %}
讲师
{% endif %}
{%- endmacro %}
{%- macro admin_role_info(role) %}
{% if role.id > 0 %}
{{ role.name }}
{% else %}
N/A
{% endif %}
{%- endmacro %}
{%- macro status_info(user) %}
{% if user.vip == 1 %}
<span class="layui-badge layui-bg-orange" title="期限:{{ date('Y-m-d H:i:s',user.vip_expiry_time) }}">会员</span>
{% endif %}
{% if user.locked == 1 %}
<span class="layui-badge" title="期限:{{ date('Y-m-d H:i:s',user.lock_expiry_time) }}">锁定</span>
{% endif %}
{%- endmacro %}

View File

@ -26,12 +26,14 @@
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>商品信息</th>
<th>买家信息</th>
<th>促销类型</th>
<th>订单金额</th>
<th>订单状态</th>
<th>创建时间</th>
@ -44,17 +46,13 @@
<tr>
<td>
<p>名称:{{ item.subject }}</p>
<p class="meta">
<span>单号:{{ item.sn }}</span>
{% if item.promotion_type > 0 %}
<span>促销:{{ promotion_type(item.promotion_type) }}</span>
{% endif %}
</p>
<p>单号:{{ item.sn }}</p>
</td>
<td>
<p>昵称:{{ item.owner.name }}</p>
<p>编号:{{ item.owner.id }}</p>
</td>
<td>{{ promotion_type(item.promotion_type) }}</td>
<td>{{ '¥%0.2f'|format(item.amount) }}</td>
<td>{{ order_status(item.status) }}</td>
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>

View File

@ -3,16 +3,35 @@
{% set course = order.item_info['course'] %}
<div class="kg-order-item">
<p>课程名称:{{ course['title'] }}</p>
<p>市场价格:{{ '¥%0.2f'|format(course['market_price']) }},会员价格:{{ '¥%0.2f'|format(course['vip_price']) }}</p>
<p>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }},退款期限:{% if course['refund_expiry'] > 0 %}{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}{% else %}不支持{% endif %}</p>
<p>
<span>市场价格:{{ '¥%0.2f'|format(course['market_price']) }}</span>
<span>会员价格:{{ '¥%0.2f'|format(course['vip_price']) }}</span>
</p>
{% if course['model'] in [1,2,3] %}
<p>
<span>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }}</span>
<span>退款期限:{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}</span>
</p>
{% elseif course['model'] == 4 %}
<p>上课时间:{{ course['attrs']['start_date'] }} ~ {{ course['attrs']['end_date'] }}</p>
<p>上课地点:{{ course['attrs']['location'] }}</p>
{% endif %}
</div>
{% elseif order.item_type == 2 %}
{% set courses = order.item_info['courses'] %}
{% for course in courses %}
<div class="kg-order-item">
<p>课程名称:{{ course['title'] }}</p>
<p>市场价格:{{ '¥%0.2f'|format(course['market_price']) }},会员价格:{{ '¥%0.2f'|format(course['vip_price']) }}</p>
<p>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }},退款期限:{% if course['refund_expiry'] > 0 %}{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}{% else %}不支持{% endif %}</p>
<p>
<span>市场价格:{{ '¥%0.2f'|format(course['market_price']) }}</span>
<span>会员价格:{{ '¥%0.2f'|format(course['vip_price']) }}</span>
</p>
<p>
{% if course['model'] in [1,2,3] %}
<span>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }}</span>
<span>退款期限:{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}</span>
{% endif %}
</p>
</div>
{% endfor %}
{% elseif order.item_type == 3 %}
@ -65,11 +84,11 @@
{%- endmacro %}
{%- macro promotion_type(value) %}
{% if value == 0 %}
N/A
{% elseif value == 1 %}
{% if value == 1 %}
秒杀
{% elseif value == 2 %}
折扣
{% else %}
N/A
{% endif %}
{%- endmacro %}

View File

@ -2,38 +2,7 @@
{% block content %}
{%- macro gender_info(value) %}
{% if value == 1 %}
{% elseif value == 2 %}
{% elseif value == 3 %}
{% endif %}
{%- endmacro %}
{%- macro edu_role_info(role) %}
{% if role.id == 1 %}
学员
{% elseif role.id == 2 %}
讲师
{% endif %}
{%- endmacro %}
{%- macro admin_role_info(role) %}
{% if role.id > 0 %}
{{ role.name }}
{% endif %}
{%- endmacro %}
{%- macro status_info(user) %}
{% if user.vip == 1 %}
<span class="layui-badge layui-bg-orange" title="期限:{{ date('Y-m-d H:i:s',user.vip_expiry_time) }}">会员</span>
{% endif %}
{% if user.locked == 1 %}
<span class="layui-badge" title="期限:{{ date('Y-m-d H:i:s',user.lock_expiry_time) }}">锁定</span>
{% endif %}
{%- endmacro %}
{{ partial('macros/user') }}
{% set add_url = url({'for':'admin.user.add'}) %}
{% set search_url = url({'for':'admin.user.search'}) %}
@ -56,22 +25,22 @@
<table class="layui-table kg-table">
<colgroup>
<col width="10%">
<col>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="12%">
<col width="10%">
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>昵称</th>
<th>性别</th>
<th>教学角色</th>
<th>后台角色</th>
<th>用户头像</th>
<th>用户昵称</th>
<th>所在地区</th>
<th>用户性别</th>
<th>用户角色</th>
<th>活跃时间</th>
<th>注册时间</th>
<th>操作</th>
@ -82,11 +51,16 @@
{% set preview_url = url({'for':'home.user.show','id':item.id}) %}
{% set edit_url = url({'for':'admin.user.edit','id':item.id}) %}
<tr>
<td>{{ item.id }}</td>
<td><a href="{{ edit_url }}" title="{{ item.about }}">{{ item.name }}</a>{{ status_info(item) }}</td>
<td class="center">
<img class="avatar-sm" src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</td>
<td><a href="{{ preview_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a>{{ item.id }}{{ status_info(item) }}</td>
<td>{{ item.area }}</td>
<td>{{ gender_info(item.gender) }}</td>
<td>{{ edu_role_info(item.edu_role) }}</td>
<td>{{ admin_role_info(item.admin_role) }}</td>
<td>
<p>教学:{{ edu_role_info(item.edu_role) }}</p>
<p>后台:{{ admin_role_info(item.admin_role) }}</p>
</td>
<td>{{ date('Y-m-d H:i:s',item.active_time) }}</td>
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
<td class="center">

View File

@ -68,6 +68,19 @@ class ImGroupController extends Controller
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id:[0-9]+}/edit", name="home.im_group.edit")
*/
public function editAction($id)
{
$service = new ImGroupService();
$group = $service->getGroup($id);
$this->view->pick('im/group/edit');
$this->view->setVar('group', $group);
}
/**
* @Get("/{id:[0-9]+}/users/active", name="home.im_group.active_users")
*/
@ -82,4 +95,31 @@ class ImGroupController extends Controller
$this->view->setVar('users', $users);
}
/**
* @Get("/{id:[0-9]+}/users/manage", name="home.im_group.manage_users")
*/
public function manageUsersAction($id)
{
$service = new ImGroupService();
$group = $service->getGroup($id);
$pager = $service->getGroupUsers($id);
$this->view->pick('im/group/manage_users');
$this->view->setVar('group', $group);
$this->view->setVar('pager', $pager);
}
/**
* @Post("/{id:[0-9]+}/update", name="home.im_group.update")
*/
public function updateAction($id)
{
$service = new ImGroupService();
$service->updateGroup($id);
return $this->jsonSuccess(['msg' => '更新群组成功']);
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace App\Http\Home\Controllers;
use App\Http\Home\Services\ImGroup as ImGroupService;
/**
* @RoutePrefix("/igm")
*/
class ImGroupManageController extends Controller
{
/**
* @Get("/{id:[0-9]+}/users", name="home.igm.users")
*/
public function usersAction($id)
{
$service = new ImGroupService();
$group = $service->getGroup($id);
$pager = $service->getGroupUsers($id);
$this->view->pick('im/group/manage/users');
$this->view->setVar('group', $group);
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id:[0-9]+}/edit", name="home.igm.edit")
*/
public function editAction($id)
{
$service = new ImGroupService();
$group = $service->getGroup($id);
$this->view->pick('im/group/manage/edit');
$this->view->setVar('group', $group);
}
/**
* @Post("/{id:[0-9]+}/update", name="home.igm.update")
*/
public function updateAction($id)
{
$service = new ImGroupService();
$service->updateGroup($id);
return $this->jsonSuccess(['msg' => '更新群组成功']);
}
/**
* @Post("/{gid:[0-9]+}/user/{uid:[0-9]+}/delete", name="home.igm.delete_user")
*/
public function deleteGroupUserAction($gid, $uid)
{
$service = new ImGroupService();
$service->deleteGroupUser($gid, $uid);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '移除用户成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Home\Controllers;
use App\Http\Home\Services\ImGroupUser as ImGroupUserService;
/**
* @RoutePrefix("/im/group/user")
*/
class ImGroupUserController extends Controller
{
/**
* @Post("/delete", name="home.im_group_user.delete")
*/
public function deleteAction()
{
$groupService = new ImGroupUserService();
$groupService->deleteGroupUser();
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除成员成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -7,12 +7,10 @@ use App\Builders\ImGroupUserList as ImGroupUserListBuilder;
use App\Caches\ImGroupActiveUserList as ImGroupActiveUserListCache;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\ImGroup as ImGroupModel;
use App\Models\ImUser as ImUserModel;
use App\Repos\ImGroup as ImGroupRepo;
use App\Repos\ImGroupUser as ImGroupUserRepo;
use App\Repos\User as UserRepo;
use App\Validators\ImGroup as ImGroupValidator;
use App\Validators\ImGroupUser as ImGroupUserValidator;
class ImGroup extends Service
{
@ -131,27 +129,6 @@ class ImGroup extends Service
return $group;
}
public function deleteGroupUser($groupId, $userId)
{
$loginUser = $this->getLoginUser();
$validator = new ImGroupUserValidator();
$group = $validator->checkGroup($groupId);
$user = $validator->checkUser($userId);
$validator->checkOwner($loginUser->id, $group->owner_id);
$groupUser = $validator->checkGroupUser($groupId, $userId);
$groupUser->delete();
$this->decrGroupUserCount($group);
$this->decrUserGroupCount($user);
}
protected function handleGroupUsers($pager)
{
if ($pager->total_items == 0) {
@ -160,18 +137,10 @@ class ImGroup extends Service
$builder = new ImGroupUserListBuilder();
$relations = $pager->items->toArray();
$stepA = $pager->items->toArray();
$stepB = $builder->handleUsers($stepA);
$users = $builder->getUsers($relations);
$items = [];
foreach ($relations as $relation) {
$user = $users[$relation['user_id']] ?? new \stdClass();
$items[] = $user;
}
$pager->items = $items;
$pager->items = $stepB;
return $pager;
}
@ -214,20 +183,4 @@ class ImGroup extends Service
return $pager;
}
protected function decrGroupUserCount(ImGroupModel $group)
{
if ($group->user_count > 0) {
$group->user_count -= 1;
$group->update();
}
}
protected function decrUserGroupCount(ImUserModel $user)
{
if ($user->group_count > 0) {
$user->group_count -= 1;
$user->update();
}
}
}

View File

@ -151,6 +151,8 @@ Trait ImGroupTrait
$groupUser = $validator->checkGroupUser($group->id, $user->id);
$validator->checkIfAllowDelete($group->id, $user->id);
$groupUser->delete();
$this->decrGroupUserCount($group);

View File

@ -0,0 +1,58 @@
<?php
namespace App\Http\Home\Services;
use App\Models\ImGroup as ImGroupModel;
use App\Models\ImUser as ImUserModel;
use App\Validators\ImGroupUser as ImGroupUserValidator;
class ImGroupUser extends Service
{
public function deleteGroupUser()
{
$groupId = $this->request->getQuery('group_id', 'int', 0);
$userId = $this->request->getQuery('user_id', 'int', 0);
$validator = new ImGroupUserValidator();
$group = $validator->checkGroup($groupId);
$user = $validator->checkUser($userId);
$loginUser = $this->getLoginUser();
$validator->checkOwner($loginUser->id, $group->owner_id);
$validator->checkIfAllowDelete($groupId, $userId);
$groupUser = $this->findOrFail($groupId, $userId);
$groupUser->delete();
$this->decrGroupUserCount($group);
$this->decrUserGroupCount($user);
}
protected function decrGroupUserCount(ImGroupModel $group)
{
if ($group->user_count > 0) {
$group->user_count -= 1;
$group->update();
}
}
protected function decrUserGroupCount(ImUserModel $user)
{
if ($user->group_count > 0) {
$user->group_count -= 1;
$user->update();
}
}
protected function findOrFail($groupId, $userId)
{
$validator = new ImGroupUserValidator();
return $validator->checkGroupUser($groupId, $userId);
}
}

View File

@ -45,6 +45,17 @@
</a>
{%- endmacro %}
{%- macro offline_lesson_info(lesson) %}
<a class="deny view-lesson" href="javascript:">
<i class="layui-icon layui-icon-user"></i>
<span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="layui-badge free-badge">免费</span>
{% endif %}
<span class="live" title="{{ date('Y-m-d H:i',lesson.attrs.start_time) }}">{{ offline_status_info(lesson) }}</span>
</a>
{%- endmacro %}
{%- macro live_status_info(lesson) %}
{% if lesson.attrs.stream.status == 'active' %}
<span class="active">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 直播中</span>
@ -55,6 +66,16 @@
{% endif %}
{%- endmacro %}
{%- macro offline_status_info(lesson) %}
{% if lesson.attrs.start_time < time() and lesson.attrs.end_time > time() %}
<span class="active">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 授课中</span>
{% elseif lesson.attrs.start_time > time() %}
<span class="pending">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 未开始</span>
{% elseif lesson.attrs.end_time < time() %}
<span class="finished">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 已结束</span>
{% endif %}
{%- endmacro %}
{% if chapters %}
<div class="layui-collapse">
{% for chapter in chapters %}
@ -69,6 +90,8 @@
<li class="lesson-item clearfix">{{ live_lesson_info(lesson) }}</li>
{% elseif lesson.model == 3 %}
<li class="lesson-item clearfix">{{ read_lesson_info(lesson) }}</li>
{% elseif lesson.model == 4 %}
<li class="lesson-item clearfix">{{ offline_lesson_info(lesson) }}</li>
{% endif %}
{% endfor %}
</ul>

View File

@ -1,6 +1,7 @@
{%- macro vod_meta_info(course) %}
<p class="item">
<span class="key">课程时长</span><span class="value">{{ course.attrs.duration|duration }}</span>
<span class="key">课程时长</span>
<span class="value">{{ course.attrs.duration|duration }}</span>
</p>
{{ meta_expiry_info(course) }}
{{ meta_price_info(course) }}
@ -9,7 +10,8 @@
{%- macro live_meta_info(course) %}
<p class="item">
<span class="key">直播时间</span><span class="value">{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span>
<span class="key">直播时间</span>
<span class="value">{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span>
</p>
{{ meta_expiry_info(course) }}
{{ meta_price_info(course) }}
@ -18,20 +20,41 @@
{%- macro read_meta_info(course) %}
<p class="item">
<span class="key">课程时长</span><span class="value">{{ course.attrs.duration|duration }}</span>
<span class="key">课程时长</span>
<span class="value">{{ course.attrs.duration|duration }}</span>
</p>
{{ meta_expiry_info(course) }}
{{ meta_price_info(course) }}
{{ meta_stats_info(course) }}
{%- endmacro %}
{%- macro offline_meta_info(course) %}
{% set search_url = "https://map.baidu.com/search/%s?querytype=s&wd=%s"|format(course.attrs.location,course.attrs.location) %}
<p class="item">
<span class="key">上课时间</span>
<span class="value">{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span>
</p>
<p class="item">
<span class="key">上课地点</span>
<span class="value">{{ course.attrs.location }}</span>
<a class="value" href="{{ search_url }}" title="查看地理位置" target="_blank">
<i class="layui-icon layui-icon-location"></i>
</a>
</p>
{{ meta_price_info(course) }}
{{ meta_stats_info(course) }}
{%- endmacro %}
{%- macro meta_expiry_info(course) %}
<p class="item">
<span class="key">学习期限</span><span class="value">{{ course.study_expiry }}个月</span>
<span class="key">学习期限</span>
<span class="value">{{ course.study_expiry }}个月</span>
{% if course.refund_expiry > 0 %}
<span class="key">退款期限</span><span class="value">{{ course.refund_expiry }}天</span>
<span class="key">退款期限</span>
<span class="value">{{ course.refund_expiry }}天</span>
{% else %}
<span class="key">退款期限</span><span class="value">不支持</span>
<span class="key">退款期限</span>
<span class="value">不支持</span>
{% endif %}
</p>
{%- endmacro %}
@ -39,26 +62,34 @@
{%- macro meta_price_info(course) %}
<p class="item">
{% if course.origin_price > 0 %}
<span class="key">原始价格</span><span class="value origin-price">{{ '¥%0.2f'|format(course.origin_price) }}</span>
<span class="key">原始价格</span>
<span class="value origin-price">{{ '¥%0.2f'|format(course.origin_price) }}</span>
{% endif %}
{% if course.market_price > 0 %}
<span class="key">优惠价格</span><span class="value price">{{ '¥%0.2f'|format(course.market_price) }}</span>
<span class="key">优惠价格</span>
<span class="value price">{{ '¥%0.2f'|format(course.market_price) }}</span>
{% else %}
<span class="key">优惠价格</span><span class="value free">免费</span>
<span class="key">优惠价格</span>
<span class="value free">免费</span>
{% endif %}
{% if course.vip_price > 0 %}
<span class="key">会员价格</span><span class="value price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
<span class="key">会员价格</span>
<span class="value price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
{% else %}
<span class="key">会员价格</span><span class="value free">免费</span>
<span class="key">会员价格</span>
<span class="value free">免费</span>
{% endif %}
</p>
{%- endmacro %}
{%- macro meta_stats_info(course) %}
<p class="item">
<span class="key">难度级别</span><span class="value">{{ level_info(course.level) }}</span>
<span class="key">学习人次</span><span class="value">{{ course.user_count }}</span>
<span class="key">综合评分</span><span class="value">{{ "%0.1f"|format(course.ratings.rating) }}</span>
<span class="key">难度级别</span>
<span class="value">{{ level_info(course.level) }}</span>
<span class="key">学习人次</span>
<span class="value">{{ course.user_count }}</span>
<span class="key">综合评分</span>
<span class="value">{{ "%0.1f"|format(course.ratings.rating) }}</span>
</p>
{%- endmacro %}
@ -73,6 +104,8 @@
{{ live_meta_info(course) }}
{% elseif course.model == 3 %}
{{ read_meta_info(course) }}
{% elseif course.model == 4 %}
{{ offline_meta_info(course) }}
{% endif %}
</div>
<div class="rating">

View File

@ -2,6 +2,16 @@
{% block content %}
{%- macro sale_item_type(value) %}
{% if value == 1 %}
课程
{% elseif value == 2 %}
套餐
{% elseif value == 3 %}
会员
{% endif %}
{% endmacro %}
{%- macro sale_status(value) %}
{% if value == 'active' %}
进行中
@ -26,6 +36,7 @@
{% set course = sale.item_info.course %}
{% set course_url = url({'for':'home.course.show','id':course.id}) %}
<div class="course-card">
<span class="model layui-badge layui-bg-green">{{ sale_item_type(sale.item_type) }}</span>
<div class="cover">
<a href="{{ course_url }}" target="_blank">
<img src="{{ course.cover }}!cover_270" alt="{{ course.title }}" title="{{ course.title }}">
@ -52,6 +63,7 @@
{% set package = sale.item_info.package %}
{% set link_url = url({'for':'home.package.courses','id':package.id}) %}
<div class="course-card">
<span class="model layui-badge layui-bg-green">{{ sale_item_type(sale.item_type) }}</span>
<div class="cover">
<a class="package-link" href="javascript:" data-url="{{ link_url }}">
<img src="{{ package.cover }}!cover_270" alt="{{ package.title }}" title="{{ package.title }}">
@ -78,6 +90,7 @@
{% set vip = sale.item_info.vip %}
{% set vip.title = "会员服务(%s"|format(vip.title) %}
<div class="course-card">
<span class="model layui-badge layui-bg-green">{{ sale_item_type(sale.item_type) }}</span>
<div class="cover">
<img src="{{ vip.cover }}!cover_270" alt="{{ vip.title }}" title="{{ vip.title }}">
</div>

View File

@ -1,32 +0,0 @@
{% extends 'templates/layer.volt' %}
{% block content %}
{% if pager.total_items > 0 %}
<div class="bg-wrap">
<div class="im-user-list clearfix">
<div class="layui-row layui-col-space20">
{% for item in pager.items %}
{% set delete_url = url({'for':'home.igm.delete_user','gid':group.id,'uid':item.id}) %}
<div class="layui-col-md2">
<div class="user-card">
<div class="avatar">
<a href="javascript:" title="{{ item.about }}">
<img src="{{ item.avatar }}" alt="{{ item.name }}">
</a>
</div>
<div class="name layui-elip" title="{{ item.name }}">{{ item.name }}</div>
<div class="action">
<button class="layui-btn kg-delete" data-tips="你确定要移除该用户吗?" data-url="{{ delete_url }}">移除</button>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{{ partial('partials/pager') }}
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,55 @@
{% extends 'templates/layer.volt' %}
{% block content %}
{{ partial('macros/user') }}
<table class="layui-table mt0">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>头像</th>
<th>名称</th>
<th>地区</th>
<th>性别</th>
<th>加入时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
{% set user_url = url({'for':'home.user.show','id':item.user.id}) %}
{% set delete_url = url({'for':'home.im_group_user.delete'},{'group_id':group.id,'user_id':item.user.id}) %}
{% set is_owner = item.user.id == group.owner.id ? 1 : 0 %}
<tr>
<td class="center">
<img class="avatar-sm" src="{{ item.user.avatar }}!avatar_160" alt="{{ item.user.name }}">
</td>
<td><a href="{{ user_url }}" title="{{ item.user.about }}" target="_blank">{{ item.user.name }}</a>{{ item.user.id }}</td>
<td>{{ item.user.area }}</td>
<td>{{ gender_info(item.user.gender) }}</td>
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
<td class="center">
{% if is_owner == 0 %}
<button class="layui-btn layui-btn-sm layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</button>
{% else %}
<button class="layui-btn layui-btn-sm layui-btn-disabled">删除</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endblock %}

View File

@ -2,21 +2,21 @@
<div class="layui-row layui-col-space20">
{% for item in pager.items %}
{% set user_url = url({'for':'home.user.show','id':item.id}) %}
{% set item.title = item.title ? item.title : '暂露头角' %}
{% set avatar_class = item.vip == 1 ? 'avatar vip' : 'avatar' %}
{% set item.user.title = item.user.title ? item.user.title : '暂露头角' %}
{% set avatar_class = item.user.vip == 1 ? 'avatar vip' : 'avatar' %}
<div class="layui-col-md3">
<div class="user-card">
<div class="{{ avatar_class }}">
<a href="{{ user_url }}" title="{{ item.about }}">
<img src="{{ item.avatar }}" alt="{{ item.name }}">
<a href="{{ user_url }}" title="{{ item.user.about }}">
<img src="{{ item.user.avatar }}" alt="{{ item.user.name }}">
</a>
</div>
<div class="name layui-elip">
<a href="{{ user_url }}" title="{{ item.about }}">{{ item.name }}</a>
<a href="{{ user_url }}" title="{{ item.user.about }}">{{ item.user.name }}</a>
</div>
<div class="title layui-elip">{{ item.title }}</div>
<div class="title layui-elip">{{ item.user.title }}</div>
<div class="action">
<span class="layui-btn apply-friend" data-id="{{ item.id }}" data-name="{{ item.name }}" data-avatar="{{ item.avatar }}">添加好友</span>
<span class="layui-btn apply-friend" data-id="{{ item.user.id }}" data-name="{{ item.user.name }}" data-avatar="{{ item.user.avatar }}">添加好友</span>
</div>
</div>
</div>

View File

@ -1,21 +1,23 @@
{%- macro model_info(value) %}
{% if value == '1' %}
{% if value == 1 %}
点播
{% elseif value == '2' %}
{% elseif value == 2 %}
直播
{% elseif value == '3' %}
{% elseif value == 3 %}
专栏
{% elseif value == 4 %}
面授
{% endif %}
{%- endmacro %}
{%- macro level_info(value) %}
{% if value == '1' %}
{% if value == 1 %}
入门
{% elseif value == '2' %}
{% elseif value == 2 %}
初级
{% elseif value == '3' %}
{% elseif value == 3 %}
中级
{% elseif value == '4' %}
{% elseif value == 4 %}
高级
{% endif %}
{%- endmacro %}

View File

@ -2,36 +2,55 @@
{% if order.item_type == 1 %}
{% set course = order.item_info.course %}
<div class="order-item">
<p>课程名称:<span>{{ course.title }}</span></p>
<p>优惠价格:<span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>会员价格:<span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span></p>
<p>学习期限:<span>{{ date('Y-m-d H:i:s',course.study_expiry_time) }}</span>退款期限:<span>{% if course.refund_expiry > 0 %}{{ date('Y-m-d H:i:s',course.refund_expiry_time) }}{% else %}不支持{% endif %}</span></p>
<p>课程名称:{{ course.title }}</p>
<p>
<span>优惠价格:<em class="price">{{ '¥%0.2f'|format(course.market_price) }}</em></span>
<span>会员价格:<em class="price">{{ '¥%0.2f'|format(course.vip_price) }}</em></span>
</p>
{% if course.model in [1,2,3] %}
<p>
<span>学习期限:{{ date('Y-m-d',course.study_expiry_time) }}</span>
<span>退款期限:{{ date('Y-m-d',course.refund_expiry_time) }}</span>
</p>
{% elseif course.model == 4 %}
<p>上课时间:{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</p>
<p>上课地点:{{ course.attrs.location }}</p>
{% endif %}
</div>
{% elseif order.item_type == 2 %}
{% set courses = order.item_info.courses %}
{% for course in courses %}
<div class="order-item">
<p>课程名称:<span>{{ course.title }}</span></p>
<p>优惠价格:<span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>会员价格:<span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span></p>
<p>学习期限:<span>{{ date('Y-m-d H:i:s',course.study_expiry_time) }}</span>退款期限:<span>{% if course.refund_expiry > 0 %}{{ date('Y-m-d H:i:s',course.refund_expiry_time) }}{% else %}不支持{% endif %}</span></p>
<p>课程名称:{{ course.title }}</p>
<p>
<span>优惠价格:{{ '¥%0.2f'|format(course.market_price) }}</span>
<span>会员价格:<em class="price">{{ '¥%0.2f'|format(course.vip_price) }}</em></span>
</p>
{% if course.model in [1,2,3] %}
<p>
<span>学习期限:{{ date('Y-m-d',course.study_expiry_time) }}</span>
<span>退款期限:{{ date('Y-m-d',course.refund_expiry_time) }}</span>
</p>
{% endif %}
</div>
{% endfor %}
{% elseif order.item_type == 3 %}
{% set course = order.item_info.course %}
{% set reward = order.item_info.reward %}
<div class="order-item">
<p>课程名称:<span>{{ course.title }}</span></p>
<p>赞赏金额:<span class="price">{{ '¥%0.2f'|format(reward.price) }}</span></p>
<p>课程名称:{{ course.title }}</p>
<p>赞赏金额:<em class="price">{{ '¥%0.2f'|format(reward.price) }}</em></p>
</div>
{% elseif order.item_type == 4 %}
{% set vip = order.item_info.vip %}
<div class="order-item">
<p>商品名称:<span>{{ order.subject }}</span></p>
<p>商品价格:<span class="price">{{ '¥%0.2f'|format(order.amount) }}</span></p>
<p>商品名称:{{ order.subject }}</p>
<p>商品价格:<em class="price">{{ '¥%0.2f'|format(order.amount) }}</em></p>
</div>
{% elseif order.item_type == 99 %}
<div class="order-item">
<p>商品名称:<span>{{ order.subject }}</span></p>
<p>商品价格:<span class="price">{{ '¥%0.2f'|format(order.amount) }}</span></p>
<p>商品名称:{{ order.subject }}</p>
<p>商品价格:<em class="price">{{ '¥%0.2f'|format(order.amount) }}</em></p>
</div>
{% endif %}
{%- endmacro %}
@ -67,11 +86,11 @@
{%- endmacro %}
{%- macro promotion_type(value) %}
{% if value == 0 %}
N/A
{% elseif value == 1 %}
{% if value == 1 %}
秒杀
{% elseif value == 2 %}
折扣
{% else %}
N/A
{% endif %}
{%- endmacro %}

View File

@ -13,18 +13,33 @@
<div class="info">
<p><a href="{{ course_url }}" target="_blank">{{ course.title }}</a></p>
<p>
<span class="key">原始价格</span><span class="value origin-price">{{ '¥%0.2f'|format(course.origin_price) }}</span>
<span class="key">优惠价格</span><span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>
<span class="key">会员价格</span><span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
</p>
<p>
<span class="key">学习期限</span><span class="value">{{ course.study_expiry }}个月</span>
{% if course.refund_expiry > 0 %}
<span class="key">退款期限</span><span class="value">{{ course.refund_expiry }}天</span>
{% else %}
<span class="key">退款期限</span><span class="value">不支持</span>
{% endif %}
<span class="key">原始价格</span>
<span class="value origin-price">{{ '¥%0.2f'|format(course.origin_price) }}</span>
<span class="key">优惠价格</span>
<span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>
<span class="key">会员价格</span>
<span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
</p>
{% if course.model in [1,2,3] %}
<p>
<span class="key">学习期限</span>
<span class="value">{{ course.study_expiry }}个月</span>
{% if course.refund_expiry > 0 %}
<span class="key">退款期限</span>
<span class="value">{{ course.refund_expiry }}天</span>
{% else %}
<span class="key">退款期限</span>
<span class="value">不支持</span>
{% endif %}
</p>
{% elseif course.model == 4 %}
<p>
<span class="key">上课时间</span>
<span class="value">{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span>
<span class="key">上课地点</span>
<span class="value">{{ course.attrs.location }}</span>
</p>
{% endif %}
</div>
</div>
{%- endmacro %}
@ -39,11 +54,17 @@
</div>
<div class="info">
<p><a href="{{ course_url }}" target="_blank">{{ course.title }}</a></p>
<p>赞赏金额 <span class="price">{{ '¥%0.2f'|format(reward.price) }}</span></p>
<p>
难度 <span>{{ level_info(course.level) }}</span>
课时 <span>{{ course.lesson_count }}</span>
学员 <span>{{ course.user_count }}</span>
<span class="key">赞赏金额</span>
<span class="price">{{ '¥%0.2f'|format(reward.price) }}</span>
</p>
<p>
<span class="key">难度</span>
<span class="value">{{ level_info(course.level) }}</span>
<span class="key">课时</span>
<span class="value">{{ course.lesson_count }}</span>
<span class="key">学员</span>
<span class="value">{{ course.user_count }}</span>
</p>
</div>
</div>
@ -57,8 +78,14 @@
</div>
<div class="info">
<p>会员服务</p>
<p>价格 <span class="price">{{ '¥%0.2f'|format(vip.price) }}</span></p>
<p>期限 <span class="expiry">{{ vip.expiry }}个月</span></p>
<p>
<span class="key">价格</span>
<span class="price">{{ '¥%0.2f'|format(vip.price) }}</span>
</p>
<p>
<span class="key">期限</span>
<span class="expiry">{{ vip.expiry }}个月</span>
</p>
</div>
</div>
{%- endmacro %}

View File

@ -10,8 +10,8 @@
<table class="layui-table order-table">
<tr>
<td colspan="2">
订单金额:<span class="price">{{ '¥%0.2f'|format(order.amount) }}</span>
订单状态:<span class="status">{{ order_status(order.status) }}</span>
<span>订单金额:<em class="price">{{ '¥%0.2f'|format(order.amount) }}</em></span>
<span>订单状态:{{ order_status(order.status) }}</span>
</td>
</tr>
<tr>
@ -21,10 +21,10 @@
</table>
<br>
<div class="center">
{% if order.status == 1 %}
{% if order.me.allow_pay == 1 %}
<a class="layui-btn layui-bg-blue" href="{{ order_pay_url }}" target="_top">立即支付</a>
{% endif %}
{% if (order.item_type in [1,2]) and (order.status == 3) %}
{% if order.me.allow_refund == 1 %}
<a class="layui-btn layui-bg-blue" href="{{ refund_confirm_url }}">申请退款</a>
{% endif %}
</div>

View File

@ -2,31 +2,29 @@
{% block content %}
<div>
<table class="layui-table">
<colgroup>
<col>
<col>
<col>
</colgroup>
<thead>
<table class="layui-table mt0">
<colgroup>
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th>课程标题</th>
<th>销售价格</th>
<th>购买人次</th>
</tr>
</thead>
<tbody>
{% for item in courses %}
{% set course_url = url({'for':'home.course.show','id':item.id}) %}
<tr>
<th>标题</th>
<th>学员数</th>
<th>价格</th>
<td><a href="{{ course_url }}" target="_blank">{{ item.title }}</a></td>
<td class="red">{{ '¥%0.2f'|format(item.market_price) }}</td>
<td>{{ item.user_count }}</td>
</tr>
</thead>
<tbody>
{% for item in courses %}
{% set course_url = url({'for':'home.course.show','id':item.id}) %}
<tr>
<td><a href="{{ course_url }}" target="_blank">{{ item.title }}</a></td>
<td>{{ item.user_count }}</td>
<td class="red">{{ '¥%0.2f'|format(item.market_price) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -12,20 +12,22 @@
<span class="title">我的好友</span>
</div>
{% if pager.total_pages > 0 %}
<table class="layui-table" lay-size="lg">
<table class="layui-table">
<colgroup>
<col>
<col>
<col>
<col>
<col width="10%">
<col>
<col width="12%">
</colgroup>
<thead>
<tr>
<th>头像</th>
<th>昵称</th>
<th>性别</th>
<th>地区</th>
<th>最后活跃</th>
<th>活跃</th>
<th>操作</th>
</tr>
</thead>
@ -34,11 +36,14 @@
{% set user_url = url({'for':'home.user.show','id':item.id}) %}
{% set delete_url = url({'for':'home.im.quit_friend','id':item.id}) %}
<tr>
<td><a href="{{ user_url }}" title="{{ item.about }}">{{ item.name }}</a></td>
<td class="center">
<img class="avatar-sm" src="{{ item.avatar }}!avatar_160" alt="{{ item.name }}">
</td>
<td><a href="{{ user_url }}" title="{{ item.about }}" target="_blank">{{ item.name }}</a></td>
<td>{{ gender_info(item.gender) }}</td>
<td>{{ item.area }}</td>
<td>{{ item.active_time|time_ago }}</td>
<td>
<td class="center">
<button class="layui-btn layui-btn-sm kg-delete" data-url="{{ delete_url }}">删除</button>
</td>
</tr>

View File

@ -1,5 +1,5 @@
{% if pager.total_pages > 0 %}
<table class="layui-table" lay-size="lg">
<table class="layui-table">
<colgroup>
<col>
<col>
@ -9,10 +9,10 @@
</colgroup>
<thead>
<tr>
<th>头像</th>
<th>名称</th>
<th>类型</th>
<th>组长</th>
<th>成员</th>
<th>操作</th>
</tr>
</thead>
@ -21,13 +21,20 @@
{% set show_url = url({'for':'home.im_group.show','id':item.id}) %}
{% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %}
{% set delete_url = url({'for':'home.im.quit_group','id':item.id}) %}
{% set is_owner = auth_user.id == item.owner.id ? 1 : 0 %}
<tr>
<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>{{ type_info(item.type) }}</td>
<td><a href="{{ owner_url }}">{{ item.owner.name }}</a></td>
<td>{{ item.user_count }}</td>
<td>
<button class="layui-btn layui-btn-sm kg-delete" data-tips="确定要退出吗?" data-url="{{ delete_url }}">退出</button>
<td class="center">
{% if is_owner == 0 %}
<button class="layui-btn layui-btn-sm layui-bg-red kg-delete" data-tips="确定要退出吗?" data-url="{{ delete_url }}">退出</button>
{% else %}
<button class="layui-btn layui-btn-sm layui-btn-disabled">退出</button>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@ -1,14 +1,16 @@
{% if pager.total_pages > 0 %}
<table class="layui-table" lay-size="lg">
<table class="layui-table">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col width="18%">
</colgroup>
<thead>
<tr>
<th>头像</th>
<th>名称</th>
<th>类型</th>
<th>成员</th>
@ -18,14 +20,18 @@
</thead>
<tbody>
{% for item in pager.items %}
{% set edit_url = url({'for':'home.igm.edit','id':item.id}) %}
{% set users_url = url({'for':'home.igm.users','id':item.id}) %}
{% set show_url = url({'for':'home.im_group.show','id':item.id}) %}
{% set edit_url = url({'for':'home.im_group.edit','id':item.id}) %}
{% set users_url = url({'for':'home.im_group.manage_users','id':item.id},{'limit':10}) %}
<tr>
<td><span title="{{ item.about }}">{{ item.name }}</span></td>
<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>{{ type_info(item.type) }}</td>
<td>{{ item.user_count }}</td>
<td>{{ item.msg_count }}</td>
<td>
<td class="center">
<span class="layui-btn layui-btn-xs layui-bg-blue btn-group-user" data-url="{{ users_url }}">成员</span>
<span class="layui-btn layui-btn-xs btn-edit-group" data-url="{{ edit_url }}">编辑</span>
</td>

View File

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

View File

@ -55,6 +55,16 @@ class Chapter extends Model
'word_count' => 0,
];
/**
* @var array
*
* 面授扩展属性
*/
protected $_offline_attrs = [
'start_time' => 0,
'end_time' => 0,
];
/**
* 主键编号
*
@ -212,11 +222,14 @@ class Chapter extends Model
$this->attrs = $this->_live_attrs;
} elseif ($this->model == Course::MODEL_READ) {
$this->attrs = $this->_read_attrs;
} elseif ($this->model == Course::MODEL_OFFLINE) {
$this->attrs = $this->_offline_attrs;
}
}
if (is_array($this->attrs)) {
$this->attrs = kg_json_encode($this->attrs);
}
}
if (is_array($this->attrs)) {
$this->attrs = kg_json_encode($this->attrs);
}
$this->create_time = time();

View File

@ -0,0 +1,72 @@
<?php
namespace App\Models;
class ChapterOffline extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 课程编号
*
* @var int
*/
public $course_id = 0;
/**
* 章节编号
*
* @var int
*/
public $chapter_id = 0;
/**
* 开始时间
*
* @var int
*/
public $start_time = 0;
/**
* 结束时间
*
* @var int
*/
public $end_time = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_chapter_offline';
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -16,7 +16,8 @@ class Course extends Model
*/
const MODEL_VOD = 1; // 点播
const MODEL_LIVE = 2; // 直播
const MODEL_READ = 3; // 图文
const MODEL_READ = 3; // 专栏
const MODEL_OFFLINE = 4; // 面授
/**
* 级别
@ -31,21 +32,41 @@ class Course extends Model
*
* 点播扩展属性
*/
protected $_vod_attrs = ['duration' => 0];
protected $_vod_attrs = [
'duration' => 0,
];
/**
* @var array
*
* 直播扩展属性
*/
protected $_live_attrs = ['start_date' => '', 'end_date' => ''];
protected $_live_attrs = [
'start_date' => '',
'end_date' => '',
];
/**
* @var array
*
* 图文扩展属性
*/
protected $_read_attrs = ['duration' => 0, 'word_count' => 0];
protected $_read_attrs = [
'duration' => 0,
'word_count' => 0,
];
/**
* @var array
*
* 面授扩展属性
*/
protected $_offline_attrs = [
'start_date' => '',
'end_date' => '',
'user_limit' => 30,
'location' => '',
];
/**
* 主键编号
@ -129,14 +150,14 @@ class Course extends Model
*
* @var int
*/
public $study_expiry = 0;
public $study_expiry = 12;
/**
* 退款期限(天)
*
* @var int
*/
public $refund_expiry = 0;
public $refund_expiry = 7;
/**
* 用户评价
@ -157,14 +178,14 @@ class Course extends Model
*
* @var int
*/
public $model = 0;
public $model = self::MODEL_VOD;
/**
* 难度级别
*
* @var int
*/
public $level = 0;
public $level = self::LEVEL_JUNIOR;
/**
* 扩展属性
@ -285,6 +306,8 @@ class Course extends Model
$this->attrs = $this->_live_attrs;
} elseif ($this->model == self::MODEL_READ) {
$this->attrs = $this->_read_attrs;
} elseif ($this->model == self::MODEL_OFFLINE) {
$this->attrs = $this->_offline_attrs;
}
}
@ -369,6 +392,7 @@ class Course extends Model
self::MODEL_VOD => '点播',
self::MODEL_LIVE => '直播',
self::MODEL_READ => '专栏',
self::MODEL_OFFLINE => '面授',
];
}

View File

@ -128,7 +128,7 @@ class ImGroup extends Model
public function beforeCreate()
{
if (empty($this->avatar)) {
$this->avatar = kg_default_avatar_path();
$this->avatar = kg_default_user_avatar_path();
} elseif (Text::startsWith($this->avatar, 'http')) {
$this->avatar = self::getAvatarPath($this->avatar);
}

View File

@ -113,7 +113,7 @@ class ImUser extends Model
public function beforeCreate()
{
if (empty($this->avatar)) {
$this->avatar = kg_default_avatar_path();
$this->avatar = kg_default_group_avatar_path();
} elseif (Text::startsWith($this->avatar, 'http')) {
$this->avatar = self::getAvatarPath($this->avatar);
}

View File

@ -5,6 +5,7 @@ namespace App\Repos;
use App\Models\Chapter as ChapterModel;
use App\Models\ChapterLike as ChapterLikeModel;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Models\ChapterOffline as ChapterOfflineModel;
use App\Models\ChapterRead as ChapterReadModel;
use App\Models\ChapterUser as ChapterUserModel;
use App\Models\ChapterVod as ChapterVodModel;
@ -121,6 +122,18 @@ class Chapter extends Repository
]);
}
/**
* @param int $chapterId
* @return ChapterOfflineModel|Model|bool
*/
public function findChapterOffline($chapterId)
{
return ChapterOfflineModel::findFirst([
'conditions' => 'chapter_id = :chapter_id:',
'bind' => ['chapter_id' => $chapterId],
]);
}
public function maxChapterPriority($courseId)
{
return (int)ChapterModel::maximum([

View File

@ -52,11 +52,19 @@ class Course extends Repository
}
if (!empty($where['model'])) {
$builder->andWhere('model = :model:', ['model' => $where['model']]);
if (is_array($where['model'])) {
$builder->inWhere('model', $where['model']);
} else {
$builder->andWhere('model = :model:', ['model' => $where['model']]);
}
}
if (!empty($where['level'])) {
$builder->andWhere('level = :level:', ['level' => $where['level']]);
if (is_array($where['level'])) {
$builder->inWhere('level', $where['model']);
} else {
$builder->andWhere('level = :level:', ['level' => $where['level']]);
}
}
if (isset($where['free'])) {

View File

@ -82,9 +82,7 @@ class CourseStat extends Service
$lessons = $courseRepo->findLessons($courseId);
if ($lessons->count() == 0) {
return;
}
if ($lessons->count() == 0) return;
$wordCount = 0;
@ -92,9 +90,6 @@ class CourseStat extends Service
foreach ($lessons as $lesson) {
/**
* @var array $attrs
*/
$attrs = $lesson->attrs;
if (isset($attrs['word_count'])) {
@ -106,9 +101,6 @@ class CourseStat extends Service
}
}
/**
* @var array $attrs
*/
$attrs = $course->attrs;
$attrs['word_count'] = $wordCount;
@ -125,17 +117,12 @@ class CourseStat extends Service
$lessons = $courseRepo->findLessons($course->id);
if ($lessons->count() == 0) {
return;
}
if ($lessons->count() == 0) return;
$scopes = [];
foreach ($lessons as $lesson) {
/**
* @var array $attrs
*/
$attrs = $lesson->attrs;
if (isset($attrs['start_time'])) {
@ -145,9 +132,6 @@ class CourseStat extends Service
if (!$scopes) return;
/**
* @var array $attrs
*/
$attrs = $course->attrs;
$attrs['start_date'] = date('Y-m-d', min($scopes));
@ -164,17 +148,12 @@ class CourseStat extends Service
$lessons = $courseRepo->findChapters($course->id);
if ($lessons->count() == 0) {
return;
}
if ($lessons->count() == 0) return;
$duration = 0;
foreach ($lessons as $lesson) {
/**
* @var array $attrs
*/
$attrs = $lesson->attrs;
if (isset($attrs['duration'])) {
@ -182,9 +161,6 @@ class CourseStat extends Service
}
}
/**
* @var array $attrs
*/
$attrs = $course->attrs;
$attrs['duration'] = $duration;
@ -192,6 +168,11 @@ class CourseStat extends Service
$course->update(['attrs' => $attrs]);
}
public function updateOfflineAttrs($courseId)
{
}
protected function calculateFreeCourseScore(CourseModel $course)
{
$weight = [

View File

@ -31,9 +31,7 @@ class ChapterList extends Service
$chapters = $cache->get($course->id);
if (count($chapters) == 0) {
return [];
}
if (count($chapters) == 0) return [];
if ($user->id > 0 && $this->courseUser) {
$mapping = $this->getLearningMapping($course->id, $user->id, $this->courseUser->plan_id);
@ -67,9 +65,7 @@ class ChapterList extends Service
$userLearnings = $courseRepo->findUserLearnings($courseId, $userId, $planId);
if ($userLearnings->count() == 0) {
return [];
}
if ($userLearnings->count() == 0) return [];
$mapping = [];

View File

@ -135,6 +135,7 @@ class OrderConfirm extends Service
'cover' => $course->cover,
'model' => $course->model,
'level' => $course->level,
'attrs' => $course->attrs,
'user_count' => $course->user_count,
'lesson_count' => $course->lesson_count,
'study_expiry' => $course->study_expiry,

View File

@ -246,6 +246,8 @@ class OrderCreate extends Service
'id' => $course->id,
'title' => $course->title,
'cover' => $course->cover,
'model' => $course->model,
'attrs' => $course->attrs,
'market_price' => $course->market_price,
'vip_price' => $course->vip_price,
'study_expiry' => $course->study_expiry,

View File

@ -2,6 +2,7 @@
namespace App\Services\Logic\Order;
use App\Models\Course as CourseModel;
use App\Models\Order as OrderModel;
use App\Repos\Order as OrderRepo;
use App\Services\Logic\Service;
@ -25,7 +26,10 @@ class OrderInfo extends Service
$statusHistory = $this->handleStatusHistory($order->id);
$me = $this->handleMeInfo($order);
return [
'me' => $me,
'sn' => $order->sn,
'subject' => $order->subject,
'amount' => $order->amount,
@ -48,9 +52,7 @@ class OrderInfo extends Service
$records = $orderRepo->findStatusHistory($orderId);
if ($records->count() == 0) {
return [];
}
if ($records->count() == 0) return [];
$result = [];
@ -64,11 +66,37 @@ class OrderInfo extends Service
return $result;
}
protected function handleMeInfo(OrderModel $order)
{
$result = [
'allow_pay' => 0,
'allow_refund' => 0,
];
if ($order->status == OrderModel::STATUS_PENDING) {
$result['allow_pay'] = 1;
}
if ($order->status == OrderModel::STATUS_FINISHED) {
/**
* 只允许线上课程退款,因为线下课程无法进行退款计算
*/
if ($order->item_type == OrderModel::ITEM_COURSE) {
$result['allow_refund'] = 1;
$course = $order->item_info['course'];
if (isset($course['model']) && $course['model'] == CourseModel::MODEL_OFFLINE) {
$result['allow_refund'] = 0;
}
} elseif ($order->item_type == OrderModel::ITEM_PACKAGE) {
$result['allow_refund'] = $order->status == OrderModel::STATUS_FINISHED ? 1 : 0;
}
}
return $result;
}
protected function handleItemInfo(OrderModel $order)
{
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
$result = [];

View File

@ -200,6 +200,10 @@ class Chapter extends Validator
if ($attrs['word_count'] == 0) {
throw new BadRequestException('chapter.read_not_ready');
}
} elseif ($chapter->model == CourseModel::MODEL_READ) {
if ($attrs['start_time'] == 0) {
throw new BadRequestException('chapter.offline_time_empty');
}
}
}

View File

@ -28,14 +28,6 @@ class ChapterLive extends Validator
public function checkTimeRange($startTime, $endTime)
{
if ($startTime < time()) {
throw new BadRequestException('chapter_live.start_lt_now');
}
if ($startTime < time()) {
throw new BadRequestException('chapter_live.end_lt_now');
}
if ($startTime >= $endTime) {
throw new BadRequestException('chapter_live.start_gt_end');
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Library\Validators\Common as CommonValidator;
class ChapterOffline extends Validator
{
public function checkStartTime($startTime)
{
if (!CommonValidator::date($startTime, 'Y-m-d H:i:s')) {
throw new BadRequestException('chapter_offline.invalid_start_time');
}
return strtotime($startTime);
}
public function checkEndTime($endTime)
{
if (!CommonValidator::date($endTime, 'Y-m-d H:i:s')) {
throw new BadRequestException('chapter_offline.invalid_end_time');
}
return strtotime($endTime);
}
public function checkTimeRange($startTime, $endTime)
{
if ($startTime >= $endTime) {
throw new BadRequestException('chapter_offline.start_gt_end');
}
}
}

View File

@ -165,7 +165,7 @@ class Course extends Validator
{
$value = $this->filter->sanitize($price, ['trim', 'float']);
if ($value < 0 || $value > 10000) {
if ($value < 0 || $value > 999999) {
throw new BadRequestException('course.invalid_origin_price');
}
@ -176,7 +176,7 @@ class Course extends Validator
{
$value = $this->filter->sanitize($price, ['trim', 'float']);
if ($value < 0 || $value > 10000) {
if ($value < 0 || $value > 999999) {
throw new BadRequestException('course.invalid_market_price');
}
@ -187,7 +187,7 @@ class Course extends Validator
{
$value = $this->filter->sanitize($price, ['trim', 'float']);
if ($value < 0 || $value > 10000) {
if ($value < 0 || $value > 999999) {
throw new BadRequestException('course.invalid_vip_price');
}
@ -236,6 +236,8 @@ class Course extends Validator
public function checkPublishAbility(CourseModel $course)
{
if ($course->model == CourseModel::MODEL_OFFLINE) return true;
$courseRepo = new CourseRepo();
$chapters = $courseRepo->findChapters($course->id);

View File

@ -0,0 +1,60 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Library\Validators\Common as CommonValidator;
class CourseOffline extends Validator
{
public function checkStartDate($startDate)
{
if (!CommonValidator::date($startDate)) {
throw new BadRequestException('course_offline.invalid_start_date');
}
return $startDate;
}
public function checkEndDate($endDate)
{
if (!CommonValidator::date($endDate)) {
throw new BadRequestException('course_offline.invalid_end_date');
}
return $endDate;
}
public function checkDateRange($startDate, $endDate)
{
if (strtotime($startDate) >= strtotime($endDate)) {
throw new BadRequestException('course_offline.start_gt_end');
}
}
public function checkUserLimit($limit)
{
$value = $this->filter->sanitize($limit, ['trim', 'int']);
if ($value < 1 || $value > 999) {
throw new BadRequestException('course_offline.invalid_user_limit');
}
return (int)$value;
}
public function checkLocation($location)
{
$value = $this->filter->sanitize($location, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 1 || $length > 50) {
throw new BadRequestException('course_offline.invalid_location');
}
return $value;
}
}

View File

@ -100,4 +100,13 @@ class ImGroupUser extends Validator
}
}
public function checkIfAllowDelete($groupId, $userId)
{
$group = $this->checkGroup($groupId);
if ($group->owner_id == $userId) {
throw new BadRequestException('im_group_user.delete_owner_not_allowed');
}
}
}

View File

@ -109,9 +109,9 @@ $error['course.details_too_long'] = '详情太长多于5000个字符';
$error['course.invalid_model'] = '无效的模型类别';
$error['course.invalid_level'] = '无效的难度级别';
$error['course.invalid_cover'] = '无效的封面';
$error['course.invalid_origin_price'] = '无效的原始价格范围0-10000';
$error['course.invalid_market_price'] = '无效的优惠价格范围0-10000';
$error['course.invalid_vip_price'] = '无效的会员价格范围0-10000';
$error['course.invalid_origin_price'] = '无效的原始价格范围0-999999';
$error['course.invalid_market_price'] = '无效的优惠价格范围0-999999';
$error['course.invalid_vip_price'] = '无效的会员价格范围0-999999';
$error['course.invalid_study_expiry'] = '无效的学习期限';
$error['course.invalid_refund_expiry'] = '无效的退款期限';
$error['course.invalid_feature_status'] = '无效的推荐状态';
@ -119,6 +119,15 @@ $error['course.invalid_publish_status'] = '无效的发布状态';
$error['course.pub_chapter_not_found'] = '尚未发现已发布的课时';
$error['course.pub_chapter_not_enough'] = '已发布的课时太少小于30%';
/**
* 面授课程相关
*/
$error['course_offline.invalid_start_date'] = '无效的开始日期';
$error['course_offline.invalid_end_date'] = '无效的结束日期';
$error['course_offline.start_gt_end'] = '开始日期大于结束日期';
$error['course_offline.invalid_user_limit'] = '无效的用户限额范围1-999';
$error['course_offline.invalid_location'] = '无效的上课地点范围10-50字符';
/**
* 话题相关
*/
@ -181,6 +190,7 @@ $error['chapter.vod_not_ready'] = '点播资源尚未就绪';
$error['chapter.read_not_ready'] = '文章内容尚未就绪';
$error['chapter.live_not_start'] = '直播尚未开始';
$error['chapter.live_time_empty'] = '直播时间尚未设置';
$error['chapter.offline_time_empty'] = '面授时间尚未设置';
$error['chapter.child_existed'] = '不允许相关操作(存在子章节)';
/**
@ -205,6 +215,13 @@ $error['chapter_read.not_found'] = '文章不存在';
$error['chapter_read.content_too_short'] = '文章内容太短少于10个字符';
$error['chapter_read.content_too_long'] = '文章内容太长多于60000个字符';
/**
* 面授相关
*/
$error['chapter_offline.invalid_start_time'] = '无效的开始时间';
$error['chapter_offline.invalid_end_time'] = '无效的结束时间';
$error['chapter_offline.start_gt_end'] = '开始时间大于结束时间';
/**
* 评价相关
*/
@ -361,6 +378,7 @@ $error['im_group_user.not_found'] = '群组关系不存在';
$error['im_group_user.remark_too_long'] = '验证信息太长超过30字符';
$error['im_group_user.has_joined'] = '已经加入过群组';
$error['im_group_user.join_not_allowed'] = '当前不允许加入群组';
$error['im_group_user.delete_owner_not_allowed'] = '当前不允许删除群主';
$error['im_friend_user.not_found'] = '好友关系不存在';
$error['im_friend_user.remark_too_long'] = '验证信息太长超过30字符';

View File

@ -0,0 +1,86 @@
<?php
use Phinx\Migration\AbstractMigration;
final class V20210324064239 extends AbstractMigration
{
public function up()
{
$orders = $this->findOrders();
if ($orders->count() == 0) return;
foreach ($orders as $order) {
if ($order['item_type'] == 1) {
$this->handleCourseOrder($order);
} elseif ($order['item_type'] == 2) {
$this->handlePackageOrder($order);
}
}
}
/**
* 课程订单补充信息
*
* @param array $order
*/
protected function handleCourseOrder($order)
{
$itemInfo = json_decode($order['item_info'], true);
$course = $this->findCourseById($itemInfo['course']['id']);
$itemInfo['course']['model'] = $course['model'];
$itemInfo['course']['attrs'] = json_decode($course['attrs'], JSON_UNESCAPED_UNICODE);
$this->updateOrderItemInfo($order['id'], $itemInfo);
}
/**
* 套餐订单补充信息
*
* @param array $order
*/
protected function handlePackageOrder($order)
{
$itemInfo = json_decode($order['item_info'], true);
foreach ($itemInfo['courses'] as &$pkgCourse) {
$course = $this->findCourseById($pkgCourse['id']);
$pkgCourse['model'] = $course['model'];
$pkgCourse['attrs'] = json_decode($course['attrs'], JSON_UNESCAPED_UNICODE);
}
$this->updateOrderItemInfo($order['id'], $itemInfo);
}
protected function updateOrderItemInfo($id, $itemInfo)
{
$itemInfo = json_encode($itemInfo, JSON_UNESCAPED_UNICODE);
$this->getQueryBuilder()
->update('kg_order')
->set('item_info', $itemInfo)
->where(['id' => $id])
->execute();
}
protected function findCourseById($id)
{
return $this->getQueryBuilder()
->select('*')
->from('kg_course')
->where(['id' => $id])
->execute()->fetch('assoc');
}
protected function findOrders()
{
return $this->getQueryBuilder()
->select('*')
->from('kg_order')
->execute();
}
}

View File

@ -54,6 +54,12 @@
color: gray;
}
.avatar-sm {
width: 64px;
height: 64px;
border-radius: 100%;
}
.loading {
padding: 30px;
text-align: center;
@ -203,6 +209,10 @@
text-align: center;
}
.kg-table span {
margin-right: 10px;
}
.kg-item-elip {
width: 400px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -6,10 +6,6 @@
padding: 20px;
}
.layer .layui-table {
margin: 0;
}
.clearfix {
zoom: 1
}
@ -30,6 +26,10 @@
float: right;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
@ -54,6 +54,12 @@
cursor: pointer;
}
.avatar-sm {
width: 48px;
height: 48px;
border-radius: 100%;
}
.layui-table .meta span {
margin-right: 10px;
}
@ -402,7 +408,7 @@
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.course-card .model {
.course-card .model, .course-card .type {
position: absolute;
top: 15px;
right: 15px;
@ -511,7 +517,7 @@
.course-meta .info {
float: left;
width: 400px;
width: 560px;
}
.course-meta .rating {
@ -1455,7 +1461,17 @@
.group-user-list .user-card {
box-shadow: none;
height: 220px;
height: 200px;
}
.group-user-list .avatar {
margin: 15px 0;
}
.group-user-list .avatar img {
width: 64px;
height: 64px;
border-radius: 100%;
}
.group-list {
@ -1684,6 +1700,10 @@
margin-right: 10px;
}
.order-table em {
font-style: normal;
}
.order-table .price {
color: red;
}

View File

@ -14,7 +14,7 @@ layui.use(['jquery', 'layer', 'helper'], function () {
type: 2,
title: '套餐课程',
content: url,
area: '800px'
area: ['800px', '280px']
});
});

View File

@ -119,8 +119,8 @@ layui.use(['jquery', 'layer', 'helper'], function () {
title: '成员管理',
maxmin: true,
resize: false,
content: [url, 'no'],
area: ['1000px', '510px']
content: [url],
area: ['1000px', '600px']
});
});