1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-22 03:32:47 +08:00

教学中心完善

This commit is contained in:
xiaochong0302 2020-08-03 20:47:35 +08:00
parent 8c99e5accd
commit c9bf425fc6
50 changed files with 694 additions and 209 deletions

View File

@ -40,7 +40,7 @@ class CourseUserList extends Builder
'id', 'title', 'cover',
'market_price', 'vip_price',
'rating', 'model', 'level', 'attrs',
'user_count', 'lesson_count',
'user_count', 'lesson_count', 'review_count', 'favorite_count',
];
$courses = $courseRepo->findByIds($ids, $columns);

View File

@ -74,19 +74,20 @@ class TestController extends Controller
$pushUrl = $liveService->getPushUrl($streamName);
$codeUrl = $this->url->get(
['for' => 'web.qrcode_img'],
$qrcode = $this->url->get(
['for' => 'web.qrcode'],
['text' => urlencode($pushUrl)]
);
$obs = [];
$pos = strrpos($pushUrl, '/');
$obs['fms_url'] = substr($pushUrl, 0, $pos + 1);
$obs['stream_code'] = substr($pushUrl, $pos + 1);
$obs = [
'fms_url' => substr($pushUrl, 0, $pos + 1),
'stream_code' => substr($pushUrl, $pos + 1),
];
$this->view->pick('setting/live_push_test');
$this->view->setVar('code_url', $codeUrl);
$this->view->setVar('qrcode', $qrcode);
$this->view->setVar('obs', $obs);
}

View File

@ -23,7 +23,7 @@ class AlipayTest extends PayTest
if ($code) {
$codeUrl = $this->url->get(
['for' => 'web.qrcode_img'],
['for' => 'web.qrcode'],
['text' => urlencode($code)]
);
}

View File

@ -79,8 +79,7 @@
layer.open({
type: 2,
title: '推流测试',
resize: false,
area: ['680px', '380px'],
area: ['680px', '450px'],
content: [url, 'no']
});
});

View File

@ -93,8 +93,7 @@
layer.open({
type: 2,
title: '推流测试',
resize: false,
area: ['680px', '380px'],
area: ['680px', '450px'],
content: [url, 'no']
});
});

View File

@ -1,16 +1,23 @@
<form class="layui-form kg-form">
<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-inline" style="width:150px;margin-left:110px;">
<img class="kg-qrcode" src="{{ code_url }}" alt="二维码图片">
<div class="kg-text-center">
<img class="kg-qrcode" src="{{ qrcode }}" alt="二维码图片">
</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>OBS推流</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">OBS推流地址</label>
<label class="layui-form-label">推流地址</label>
<div class="layui-input-inline" style="width:350px;">
<input id="tc1" class="layui-input" type="text" name="obs.fms_url" value="{{ obs.fms_url }}" readonly="true">
<input id="tc1" class="layui-input" type="text" name="obs.fms_url" value="{{ obs.fms_url }}" readonly="readonly">
</div>
<div class="layui-input-inline" style="width:100px;">
<span class="kg-copy layui-btn" data-clipboard-target="#tc1">复制</span>
@ -18,9 +25,9 @@
</div>
<div class="layui-form-item">
<label class="layui-form-label">OBS推流名称</label>
<label class="layui-form-label">推流名称</label>
<div class="layui-input-inline" style="width:350px;">
<input id="tc2" class="layui-input" type="text" name="obs_stream_code" value="{{ obs.stream_code }}" readonly="true">
<input id="tc2" class="layui-input" type="text" name="obs_stream_code" value="{{ obs.stream_code }}" readonly="readonly">
</div>
<div class="layui-input-inline" style="width:100px;">
<span class="kg-copy layui-btn" data-clipboard-target="#tc2">复制</span>

View File

@ -6,9 +6,8 @@ use App\Services\Frontend\Consult\ConsultCreate as ConsultCreateService;
use App\Services\Frontend\Consult\ConsultDelete as ConsultDeleteService;
use App\Services\Frontend\Consult\ConsultInfo as ConsultInfoService;
use App\Services\Frontend\Consult\ConsultLike as ConsultLikeService;
use App\Services\Frontend\Consult\ConsultRating as ConsultRatingService;
use App\Services\Frontend\Consult\ConsultReply as ConsultReplyService;
use App\Services\Frontend\Consult\ConsultUpdate as ConsultUpdateService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/consult")
@ -21,20 +20,7 @@ class ConsultController extends Controller
*/
public function addAction()
{
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
}
/**
* @Get("/{id:[0-9]+}/edit", name="web.consult.edit")
*/
public function editAction($id)
{
$service = new ConsultInfoService();
$consult = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->setVar('consult', $consult);
}
/**
@ -46,10 +32,46 @@ class ConsultController extends Controller
$consult = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->setVar('consult', $consult);
}
/**
* @Get("/{id:[0-9]+}/edit", name="web.consult.edit")
*/
public function editAction($id)
{
$service = new ConsultInfoService();
$consult = $service->handle($id);
$this->view->setVar('consult', $consult);
}
/**
* @Route("/{id:[0-9]+}/reply", name="web.consult.reply")
*/
public function replyAction($id)
{
if ($this->request->isPost()) {
$service = new ConsultReplyService();
$service->handle($id);
$content = ['msg' => '回复咨询成功'];
return $this->jsonSuccess($content);
} else {
$service = new ConsultInfoService();
$consult = $service->handle($id);
$this->view->setVar('consult', $consult);
}
}
/**
* @Post("/create", name="web.consult.create")
*/
@ -61,12 +83,9 @@ class ConsultController extends Controller
$service = new ConsultInfoService();
$consult = $service->handle($consult->id);
$service->handle($consult->id);
$content = [
'consult' => $consult,
'msg' => '提交咨询成功',
];
$content = ['msg' => '提交咨询成功'];
return $this->jsonSuccess($content);
}
@ -78,12 +97,9 @@ class ConsultController extends Controller
{
$service = new ConsultUpdateService();
$consult = $service->handle($id);
$service->handle($id);
$content = [
'consult' => $consult,
'msg' => '更新咨询成功',
];
$content = ['msg' => '更新咨询成功'];
return $this->jsonSuccess($content);
}
@ -118,18 +134,4 @@ class ConsultController extends Controller
return $this->jsonSuccess($content);
}
/**
* @Post("/{id:[0-9]+}/rating", name="web.consult.rating")
*/
public function ratingAction($id)
{
$service = new ConsultRatingService();
$service->handle($id);
$content = ['msg' => '评价成功'];
return $this->jsonSuccess($content);
}
}

View File

@ -30,14 +30,11 @@ class MyController extends Controller
}
/**
* @Get("/home", name="web.my.home")
* @Get("/", name="web.my.index")
*/
public function homeAction()
public function indexAction()
{
$this->response->redirect([
'for' => 'web.user.show',
'id' => $this->authUser->id,
]);
return $this->dispatcher->forward(['action' => 'courses']);
}
/**

View File

@ -39,9 +39,9 @@ class PublicController extends \Phalcon\Mvc\Controller
}
/**
* @Get("/qrcode/img", name="web.qrcode_img")
* @Get("/qrcode", name="web.qrcode")
*/
public function qrcodeImageAction()
public function qrcodeAction()
{
$text = $this->request->getQuery('text');
$level = $this->request->getQuery('level', 'int', 0);

View File

@ -3,8 +3,10 @@
namespace App\Http\Web\Controllers;
use App\Services\Frontend\Teaching\ConsultList as TclService;
use App\Services\Frontend\Teaching\LiveList as TllService;
use App\Services\Frontend\Teaching\ConsultList as ConsultListService;
use App\Services\Frontend\Teaching\CourseList as CourseListService;
use App\Services\Frontend\Teaching\LiveList as LiveListService;
use App\Services\Frontend\Teaching\LivePushUrl as LivePushUrlService;
/**
@ -13,12 +15,34 @@ use App\Services\Frontend\Teaching\LiveList as TllService;
class TeachingController extends Controller
{
/**
* @Get("/", name="web.teaching.index")
*/
public function indexAction()
{
$this->dispatcher->forward(['action' => 'courses']);
}
/**
* @Get("/courses", name="web.teaching.courses")
*/
public function coursesAction()
{
$service = new CourseListService();
$pager = $service->handle();
$pager->items = kg_array_object($pager->items);
$this->view->setVar('pager', $pager);
}
/**
* @Get("/lives", name="web.teaching.lives")
*/
public function livesAction()
{
$service = new TllService();
$service = new LiveListService();
$pager = $service->handle();
@ -32,7 +56,7 @@ class TeachingController extends Controller
*/
public function consultsAction()
{
$service = new TclService();
$service = new ConsultListService();
$pager = $service->handle();
@ -41,4 +65,30 @@ class TeachingController extends Controller
$this->view->setVar('pager', $pager);
}
/**
* @Get("/live/push", name="web.teaching.live_push")
*/
public function livePushAction()
{
$service = new LivePushUrlService();
$pushUrl = $service->handle();
$qrcode = $this->url->get(
['for' => 'web.qrcode'],
['text' => urlencode($pushUrl)]
);
$pos = strrpos($pushUrl, '/');
$obs = [
'fms_url' => substr($pushUrl, 0, $pos + 1),
'stream_code' => substr($pushUrl, $pos + 1),
];
$this->view->pick('teaching/live_push');
$this->view->setVar('qrcode', $qrcode);
$this->view->setVar('obs', $obs);
}
}

View File

@ -62,7 +62,7 @@ class Trade extends Service
if ($text) {
$qrCodeUrl = $this->url->get(
['for' => 'web.qrcode_img'],
['for' => 'web.qrcode'],
['text' => urlencode($text)]
);
}

View File

@ -10,7 +10,7 @@
{% set send_msg_url = url({'for':'web.live.send_msg','id':chapter.id}) %}
{% set bind_user_url = url({'for':'web.live.bind_user','id':chapter.id}) %}
{% set like_url = url({'for':'web.chapter.like','id':chapter.id}) %}
{% set qrcode_url = url({'for':'web.qrcode_img'},{'text':chapter_full_url}) %}
{% set qrcode_url = url({'for':'web.qrcode'},{'text':chapter_full_url}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">

View File

@ -7,7 +7,7 @@
{% set learning_url = url({'for':'web.chapter.learning','id':chapter.id}) %}
{% set like_url = url({'for':'web.chapter.like','id':chapter.id}) %}
{% set consult_url = url({'for':'web.consult.add'},{'chapter_id':chapter.id}) %}
{% set qrcode_url = url({'for':'web.qrcode_img'},{'text':chapter_full_url}) %}
{% set qrcode_url = url({'for':'web.qrcode'},{'text':chapter_full_url}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">

View File

@ -7,7 +7,7 @@
{% set learning_url = url({'for':'web.chapter.learning','id':chapter.id}) %}
{% set danmu_url = url({'for':'web.chapter.danmu','id':chapter.id}) %}
{% set like_url = url({'for':'web.chapter.like','id':chapter.id}) %}
{% set qrcode_url = url({'for':'web.qrcode_img'},{'text':chapter_full_url}) %}
{% set qrcode_url = url({'for':'web.qrcode'},{'text':chapter_full_url}) %}
{% set consult_url = url({'for':'web.consult.add'},{'chapter_id':chapter.id}) %}
{% set liked_class = chapter.me.liked ? 'active' : '' %}

View File

@ -14,9 +14,9 @@
<div class="layui-form-mid">{{ consult.chapter.title }}</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">问题</label>
<label class="layui-form-label" for="answer">问题</label>
<div class="layui-input-block">
<textarea name="question" class="layui-textarea" lay-verify="required">{{ consult.question }}</textarea>
<textarea id="answer" class="layui-textarea" name="question" lay-verify="required">{{ consult.question }}</textarea>
</div>
</div>
<div class="layui-form-item">
@ -30,7 +30,6 @@
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>

View File

@ -0,0 +1,34 @@
{% extends 'templates/layer.volt' %}
{% block content %}
{% set update_url = url({'for':'web.consult.reply','id':consult.id}) %}
<form class="layui-form" method="post" action="{{ update_url }}">
<div class="layui-form-item mb0">
<label class="layui-form-label">课程</label>
<div class="layui-form-mid">{{ consult.course.title }}</div>
</div>
<div class="layui-form-item mb0">
<label class="layui-form-label">章节</label>
<div class="layui-form-mid">{{ consult.chapter.title }}</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">问题</label>
<div class="layui-form-mid">{{ consult.question }}</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" for="answer">回复</label>
<div class="layui-input-block">
<textarea id="answer" class="layui-textarea" name="answer" lay-verify="required">{{ consult.answer }}</textarea>
</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>
</div>
</div>
</form>
{% endblock %}

View File

@ -2,7 +2,6 @@
{% block content %}
{% set rating_url = url({'for':'web.consult.rating','id':consult.id}) %}
{% set answer = consult.answer ? consult.answer : '<span class="gray">稍安勿燥,请耐心等待回复吧</span>' %}
<form class="layui-form review-form">
@ -22,39 +21,6 @@
<label class="layui-form-label">回复:</label>
<div class="layui-form-mid">{{ answer }}</div>
</div>
{% if consult.answer %}
<div class="layui-form-item">
<label class="layui-form-label">评分:</label>
<div class="layui-input-block">
<input type="hidden" name="rating" value="{{ consult.rating }}">
<input type="hidden" name="rating_url" value="{{ rating_url }}">
<div id="rating"></div>
</div>
</div>
{% endif %}
</form>
{% endblock %}
{% block inline_js %}
<script>
layui.use(['jquery', 'layer', 'rate'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var rate = layui.rate;
var rating = $('input[name=rating]').val();
var ratingUrl = $('input[name=rating_url]').val();
rate.render({
elem: '#rating',
value: rating,
choose: function (value) {
$.post(ratingUrl, {rating: value}, function () {
layer.msg('评价成功');
});
}
});
});
</script>
{% endblock %}

View File

@ -1,5 +1,3 @@
{{ partial('partials/macro_course') }}
{% if pager.total_pages > 0 %}
<div class="review-list">
{% for item in pager.items %}
@ -13,7 +11,6 @@
</a>
</div>
<div class="info">
<div class="rating">{{ star_info(item.rating) }}</div>
<div class="title">{{ item.question }}</div>
<div class="content">{{ item.answer }}</div>
<div class="footer">

View File

@ -8,7 +8,7 @@
{% set favorite_star = course.me.favorited ? 'layui-icon-star-fill' : 'layui-icon-star' %}
{% set full_course_url = full_url({'for':'web.course.show','id':course.id}) %}
{% set favorite_url = url({'for':'web.course.favorite','id':course.id}) %}
{% set qrcode_url = url({'for':'web.qrcode_img'},{'text':full_course_url}) %}
{% set qrcode_url = url({'for':'web.qrcode'},{'text':full_course_url}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">

View File

@ -29,6 +29,7 @@
{% for item in pager.items %}
{% set course_url = url({'for':'web.course.show','id':item.course.id}) %}
{% set review_url = url({'for':'web.review.add'},{'id':item.course.id}) %}
{% set allow_review = item.progress > 30 and item.reviewed == 0 %}
<tr>
<td>
<p>标题:<a href="{{ course_url }}">{{ item.course.title }}</a> {{ model_info(item.course.model) }}</p>
@ -39,7 +40,11 @@
<p>进度:{{ item.progress }}%</p>
</td>
<td align="center">
<button class="layui-btn layui-btn-xs btn-add-review" data-url="{{ review_url }}">评价</button>
{% if allow_review %}
<button class="layui-btn layui-btn-sm btn-add-review" data-url="{{ review_url }}">评价</button>
{% else %}
<button class="layui-btn layui-btn-sm layui-btn-disabled" title="学习进度过30%才允许评价">评价</button>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@ -14,16 +14,6 @@
<div class="name">{{ auth_user.name }} {{ vip_info(auth_user) }}</div>
</div>
<div class="layui-card">
<div class="layui-card-header">教学中心</div>
<div class="layui-card-body">
<ul class="my-menu">
<li><a href="{{ url({'for':'web.my.refunds'}) }}">我的直播</a></li>
<li><a href="{{ url({'for':'web.my.orders'}) }}">用户咨询</a></li>
</ul>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">课程中心</div>
<div class="layui-card-body">

View File

@ -30,8 +30,11 @@
<li class="layui-nav-item">
<a href="javascript:">{{ auth_user.name }}</a>
<dl class="layui-nav-child">
<dd><a href="{{ url({'for':'web.my.home'}) }}">我的主页</a></dd>
<dd><a href="{{ url({'for':'web.my.profile'}) }}">个人设置</a></dd>
<dd><a href="{{ url({'for':'web.user.show','id':auth_user.id}) }}">我的主页</a></dd>
{% if auth_user.edu_role == 2 %}
<dd><a href="{{ url({'for':'web.teaching.index'}) }}">教学中心</a></dd>
{% endif %}
<dd><a href="{{ url({'for':'web.my.index'}) }}">用户中心</a></dd>
<dd><a href="{{ url({'for':'web.account.logout'}) }}">退出登录</a></dd>
</dl>
</li>

View File

@ -0,0 +1,69 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set status_types = {'all':'全部','pending':'待回复','replied':'已回复'} %}
{% set status = request.get('status','trim','all') %}
<div class="layout-main">
<div class="my-sidebar">{{ partial('teaching/menu') }}</div>
<div class="my-content">
<div class="wrap">
<div class="my-nav">
<span class="title">用户咨询</span>
{% for key,value in status_types %}
{% set class = (status == key) ? 'layui-btn layui-btn-xs' : 'none' %}
{% set url = url({'for':'web.teaching.consults'},{'status':key}) %}
<a class="{{ class }}" href="{{ url }}">{{ value }}</a>
{% endfor %}
</div>
{% if pager.total_pages > 0 %}
<table class="layui-table consult-table">
<colgroup>
<col>
<col>
<col width="20%">
</colgroup>
<thead>
<tr>
<th>内容</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
{% set answer = item.answer ? item.answer : '<span class="gray">等待回复ing...</span>' %}
{% set course_url = url({'for':'web.course.show','id':item.course.id}) %}
{% set show_url = url({'for':'web.consult.show','id':item.id}) %}
{% set reply_url = url({'for':'web.consult.reply','id':item.id}) %}
{% set delete_url = url({'for':'web.consult.delete','id':item.id}) %}
<tr>
<td>
<p>课程:<a href="{{ course_url }}" target="_blank">{{ item.course.title }}</a></p>
<p class="question layui-elip" title="{{ item.question }}">提问:{{ item.question }}</p>
<p class="answer layui-elip" title="{{ item.answer }}">回复:{{ answer }}</p>
</td>
<td>{{ date('Y-m-d',item.create_time) }}</td>
<td>
<button class="layui-btn layui-btn-xs layui-bg-green btn-show-consult" data-url="{{ show_url }}">详情</button>
<button class="layui-btn layui-btn-xs layui-bg-blue btn-reply-consult" data-url="{{ reply_url }}">回复</button>
<button class="layui-btn layui-btn-xs layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('web/js/teaching.js') }}
{% endblock %}

View File

@ -0,0 +1,51 @@
{% extends 'templates/main.volt' %}
{% block content %}
{{ partial('partials/macro_course') }}
<div class="layout-main">
<div class="my-sidebar">{{ partial('teaching/menu') }}</div>
<div class="my-content">
<div class="wrap">
<div class="my-nav">
<span class="title">我的课程</span>
</div>
{% if pager.total_pages > 0 %}
<table class="layui-table" lay-size="lg">
<colgroup>
<col>
<col>
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th>名称</th>
<th>课时</th>
<th>学员</th>
<th>收藏</th>
<th>评分</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
{% set course_url = url({'for':'web.course.show','id':item.id}) %}
<tr>
<td><a href="{{ course_url }}">{{ item.title }}</a> {{ model_info(item.model) }}</td>
<td><span class="layui-badge-rim">{{ item.lesson_count }}</span></td>
<td><span class="layui-badge-rim">{{ item.user_count }}</span></td>
<td><span class="layui-badge-rim">{{ item.favorite_count }}</span></td>
<td><span class="layui-badge-rim">{{ item.rating }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,44 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<form class="layui-form">
<fieldset class="layui-elem-field layui-field-title">
<legend>手机推流</legend>
</fieldset>
<div class="layui-form-item">
<div class="text-center">
<div class="qrcode-sm"><img src="{{ qrcode }}" alt="二维码图片"></div>
</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>OBS推流</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">推流地址</label>
<div class="layui-input-inline" style="width:350px;">
<input id="tc1" class="layui-input" type="text" name="obs.fms_url" value="{{ obs.fms_url }}" readonly="readonly">
</div>
<div class="layui-input-inline" style="width:100px;">
<span class="kg-copy layui-btn" data-clipboard-target="#tc1">复制</span>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">推流名称</label>
<div class="layui-input-inline" style="width:350px;">
<input id="tc2" class="layui-input" type="text" name="obs_stream_code" value="{{ obs.stream_code }}" readonly="readonly">
</div>
<div class="layui-input-inline" style="width:100px;">
<span class="kg-copy layui-btn" data-clipboard-target="#tc2">复制</span>
</div>
</div>
</form>
{% endblock %}
{% block include_js %}
{{ js_include('lib/clipboard.min.js') }}
{{ js_include('web/js/copy.js') }}
{% endblock %}

View File

@ -0,0 +1,63 @@
{% extends 'templates/main.volt' %}
{% block content %}
{{ partial('partials/macro_course') }}
<div class="layout-main">
<div class="my-sidebar">{{ partial('teaching/menu') }}</div>
<div class="my-content">
<div class="wrap">
<div class="my-nav">
<span class="title">课程直播</span>
</div>
{% if pager.total_pages > 0 %}
<table class="layui-table">
<colgroup>
<col>
<col>
<col width="12%">
</colgroup>
<thead>
<tr>
<th>课程/章节</th>
<th>直播时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
{% set course_url = url({'for':'web.course.show','id':item.course.id}) %}
{% set chapter_url = url({'for':'web.chapter.show','id':item.chapter.id}) %}
{% set live_push_url = url({'for':'web.teaching.live_push','id':item.chapter.id}) %}
{% set allow_push = (item.start_time - 1800 < time()) and (time() < item.start_time + 1800) %}
<tr>
<td>
<p>课程:<a href="{{ course_url }}" target="_blank">{{ item.course.title }}</a></p>
<p>章节:<a href="{{ chapter_url }}" target="_blank">{{ item.chapter.title }}</a></p>
</td>
<td>{{ date('m-d',item.start_time) }} {{ date('H:i',item.start_time) }} ~ {{ date('H:i',item.end_time) }}</td>
<td align="center">
{% if allow_push %}
<button class="layui-btn layui-btn-sm btn-live-push" data-url="{{ live_push_url }}">推流</button>
{% else %}
<button class="layui-btn layui-btn-sm layui-btn-disabled" title="开播前后半小时之内才允许推流">推流</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('web/js/teaching.js') }}
{% endblock %}

View File

@ -0,0 +1,10 @@
<div class="layui-card">
<div class="layui-card-header">教学中心</div>
<div class="layui-card-body">
<ul class="my-menu">
<li><a href="{{ url({'for':'web.teaching.courses'}) }}">我的课程</a></li>
<li><a href="{{ url({'for':'web.teaching.lives'}) }}">我的直播</a></li>
<li><a href="{{ url({'for':'web.teaching.consults'}) }}">课程咨询</a></li>
</ul>
</div>
</div>

View File

@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="csrf-token" content="{{ csrfToken.getToken() }}">
<title>{{ site_seo.getTitle() }}</title>
{{ icon_link('favicon.ico') }}
{{ css_link('lib/layui/css/layui.css') }}
{{ css_link('web/css/common.css') }}

View File

@ -56,13 +56,6 @@ class Consult extends Model
*/
public $answer;
/**
* 评分
*
* @var int
*/
public $rating;
/**
* 赞成数
*
@ -103,7 +96,7 @@ class Consult extends Model
*
* @var int
*/
public $answer_time;
public $reply_time;
/**
* 创建时间
@ -145,10 +138,6 @@ class Consult extends Model
{
$this->update_time = time();
if (!empty($this->answer) && $this->answer_time == 0) {
$this->answer_time = time();
}
if ($this->deleted == 1) {
$this->published = 0;
}

View File

@ -348,7 +348,7 @@ class Course extends Repository
$result = [];
if ($rows->count() == 0) {
if ($rows->count() > 0) {
$result = kg_array_column($rows->toArray(), 'course_id');
}

View File

@ -12,6 +12,8 @@ use WhichBrowser\Parser as BrowserParser;
trait ChapterBasicInfoTrait
{
use ChapterLiveTrait;
protected function handleBasicInfo(ChapterModel $chapter)
{
$result = [];
@ -65,7 +67,7 @@ trait ChapterBasicInfoTrait
$liveService = new LiveService();
$stream = "chapter-{$chapter->id}";
$stream = $this->getLiveStreamName($chapter->id);
$format = $browserParser->isType('desktop') ? 'flv' : 'hls';

View File

@ -115,7 +115,7 @@ class ChapterInfo extends FrontendService
$this->incrCourseUserCount($course);
$this->incrUserCourseCount($course);
$this->incrUserCourseCount($user);
}
protected function handleChapterUser(ChapterModel $chapter, UserModel $user)
@ -162,9 +162,4 @@ class ChapterInfo extends FrontendService
$chapter->update();
}
protected function getLiveStreamName($id)
{
return "chapter_{$id}";
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Services\Frontend\Chapter;
trait ChapterLiveTrait
{
protected function getLiveStreamName($id)
{
return "chapter_{$id}";
}
}

View File

@ -6,7 +6,7 @@ use App\Services\Frontend\ConsultTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Validators\Consult as ConsultValidator;
class ConsultRating extends FrontendService
class ConsultReply extends FrontendService
{
use ConsultTrait;
@ -21,14 +21,10 @@ class ConsultRating extends FrontendService
$validator = new ConsultValidator();
$validator->checkOwner($user->id, $consult->user_id);
$validator->checkIfAllowRate($consult);
$rating = $validator->checkRating($post['rating']);
$consult->rating = $rating;
$validator->checkTeacher($consult, $user);
$consult->answer = $validator->checkAnswer($post['answer']);
$consult->reply_time = time();
$consult->update();
return $consult;

View File

@ -23,13 +23,19 @@ class ConsultUpdate extends FrontendService
$validator->checkOwner($user->id, $consult->owner_id);
$validator->checkIfAllowEdit($consult);
$validator->checkConsultEdit($consult);
$question = $validator->checkQuestion($post['question']);
$data = [];
$consult->question = $question;
if (isset($post['question'])) {
$data['question'] = $validator->checkQuestion($post['question']);
}
$consult->update();
if (isset($post['private'])) {
$data['private'] = $validator->checkPrivateStatus($post['private']);
}
$consult->update($data);
return $consult;
}

View File

@ -58,8 +58,8 @@ class ConsultList extends FrontendService
'id' => $consult['id'],
'question' => $consult['question'],
'answer' => $consult['answer'],
'rating' => $consult['rating'],
'like_count' => $consult['like_count'],
'reply_time' => $consult['reply_time'],
'create_time' => $consult['create_time'],
'update_time' => $consult['update_time'],
'owner' => $owner,

View File

@ -74,6 +74,8 @@ class CourseList extends FrontendService
'level' => $course['level'],
'user_count' => $course['user_count'],
'lesson_count' => $course['lesson_count'],
'review_count' => $course['review_count'],
'favorite_count' => $course['favorite_count'],
];
}

View File

@ -18,7 +18,7 @@ class ConsultList extends FrontendService
$params = $pagerQuery->getParams();
$params['user_id'] = $user->id;
$params['owner_id'] = $user->id;
$params['published'] = 1;
$sort = $pagerQuery->getSort();
@ -56,8 +56,8 @@ class ConsultList extends FrontendService
'id' => $consult['id'],
'question' => $consult['question'],
'answer' => $consult['answer'],
'rating' => $consult['rating'],
'like_count' => $consult['like_count'],
'reply_time' => $consult['reply_time'],
'create_time' => $consult['create_time'],
'update_time' => $consult['update_time'],
'course' => $course,

View File

@ -53,6 +53,8 @@ class CourseList extends FrontendService
'level' => $course['level'],
'user_count' => (int)$course['user_count'],
'lesson_count' => (int)$course['lesson_count'],
'review_count' => (int)$course['review_count'],
'favorite_count' => (int)$course['favorite_count'],
'teacher' => json_decode($course['teacher']),
'category' => json_decode($course['category']),
];

View File

@ -21,6 +21,7 @@ class ConsultList extends FrontendService
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
@ -30,9 +31,17 @@ class ConsultList extends FrontendService
return [];
}
$courseIds = kg_array_column($courses->toArray(), 'id');
$params['status'] = $params['status'] ?? null;
$pager = $this->paginate($courseIds, $page, $limit);
if ($params['status'] == 'pending') {
$params['replied'] = 0;
} elseif ($params['status'] == 'replied') {
$params['replied'] = 1;
}
$params['course_id'] = kg_array_column($courses->toArray(), 'id');
$pager = $this->paginate($params, $page, $limit);
return $this->handleConsults($pager);
}
@ -63,8 +72,8 @@ class ConsultList extends FrontendService
'id' => $consult['id'],
'question' => $consult['question'],
'answer' => $consult['answer'],
'rating' => $consult['rating'],
'like_count' => $consult['like_count'],
'reply_time' => $consult['reply_time'],
'create_time' => $consult['create_time'],
'update_time' => $consult['update_time'],
'course' => $course,
@ -78,13 +87,27 @@ class ConsultList extends FrontendService
return $pager;
}
protected function paginate($courseIds, $page = 1, $limit = 15)
protected function paginate($where, $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder()
->from(ConsultModel::class)
->inWhere('course_id', $courseIds)
->andWhere('published = 1')
->orderBy('priority ASC');
$builder = $this->modelsManager->createBuilder();
$builder->from(ConsultModel::class);
$builder->where('published = 1');
if (!empty($where['course_id'])) {
$builder->inWhere('course_id', $where['course_id']);
}
if (isset($where['replied'])) {
if ($where['replied'] == 1) {
$builder->andWhere('reply_time > 0');
} else {
$builder->andWhere('reply_time = 0');
}
}
$builder->orderBy('id DESC');
$pager = new PagerQueryBuilder([
'builder' => $builder,

View File

@ -0,0 +1,84 @@
<?php
namespace App\Services\Frontend\Teaching;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Course as CourseModel;
use App\Models\CourseUser as CourseUserModel;
use App\Services\Frontend\Service as FrontendService;
class CourseList extends FrontendService
{
public function handle()
{
$user = $this->getLoginUser();
$pagerQuery = new PagerQuery();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$pager = $this->paginate($user->id, $page, $limit);
return $this->handleCourses($pager);
}
protected function handleCourses($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$items = [];
$baseUrl = kg_ci_base_url();
foreach ($pager->items->toArray() as $course) {
$course['cover'] = $baseUrl . $course['cover'];
$items[] = [
'id' => $course['id'],
'title' => $course['title'],
'cover' => $course['cover'],
'market_price' => (float)$course['market_price'],
'vip_price' => (float)$course['vip_price'],
'rating' => (float)$course['rating'],
'model' => $course['model'],
'level' => $course['level'],
'user_count' => $course['user_count'],
'lesson_count' => $course['lesson_count'],
'review_count' => $course['review_count'],
'favorite_count' => $course['favorite_count'],
];
}
$pager->items = $items;
return $pager;
}
protected function paginate($userId, $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder();
$builder->columns('c.*');
$builder->addFrom(CourseModel::class, 'c');
$builder->join(CourseUserModel::class, 'c.id = cu.course_id', 'cu');
$builder->where('cu.user_id = :user_id:', ['user_id' => $userId]);
$builder->andWhere('cu.role_type = :role_type:', ['role_type' => CourseUserModel::ROLE_TEACHER]);
$builder->andWhere('c.published = 1');
$builder->orderBy('c.id DESC');
$pager = new PagerQueryBuilder([
'builder' => $builder,
'page' => $page,
'limit' => $limit,
]);
return $pager->paginate();
}
}

View File

@ -68,12 +68,15 @@ class LiveList extends FrontendService
protected function paginate($courseIds, $page = 1, $limit = 15)
{
$startTime = strtotime('today');
$builder = $this->modelsManager->createBuilder()
->columns(['c.id', 'c.title', 'c.course_id', 'cl.start_time', 'cl.end_time'])
->addFrom(ChapterModel::class, 'c')
->join(ChapterLiveModel::class, 'c.id = cl.chapter_id', 'cl')
->inWhere('cl.course_id', $courseIds)
->orderBy('cl.start_time DESC');
->andWhere('cl.start_time > :start_time:', ['start_time' => $startTime])
->orderBy('cl.start_time ASC');
$pager = new PagerQueryBuilder([
'builder' => $builder,

View File

@ -0,0 +1,29 @@
<?php
namespace App\Services\Frontend\Teaching;
use App\Services\Frontend\Chapter\ChapterLiveTrait;
use App\Services\Frontend\ChapterTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Services\Live as LiveService;
class LivePushUrl extends FrontendService
{
use ChapterTrait;
use ChapterLiveTrait;
public function handle()
{
$chapterId = $this->request->getQuery('chapter_id');
$chapter = $this->checkChapter($chapterId);
$service = new LiveService();
$steamName = $this->getLiveStreamName($chapter->id);
return $service->getPushUrl($steamName);
}
}

View File

@ -73,6 +73,7 @@ class Live extends Service
} catch (TencentCloudSDKException $e) {
$this->logger->error('Describe Live Stream State Exception ' . kg_json_encode([
'line' => $e->getLine(),
'code' => $e->getErrorCode(),
'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),
@ -118,6 +119,7 @@ class Live extends Service
} catch (TencentCloudSDKException $e) {
$this->logger->error('Forbid Live Stream Exception ' . kg_json_encode([
'line' => $e->getLine(),
'code' => $e->getErrorCode(),
'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),
@ -161,6 +163,7 @@ class Live extends Service
} catch (TencentCloudSDKException $e) {
$this->logger->error('Resume Live Stream Exception ' . kg_json_encode([
'line' => $e->getLine(),
'code' => $e->getErrorCode(),
'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),

View File

@ -3,9 +3,13 @@
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Exceptions\Forbidden as ForbiddenException;
use App\Models\Consult as ConsultModel;
use App\Models\CourseUser as CourseUserModel;
use App\Models\User as UserModel;
use App\Repos\Consult as ConsultRepo;
use App\Repos\ConsultLike as ConsultLikeRepo;
use App\Repos\CourseUser as CourseUserRepo;
class Consult extends Validator
{
@ -71,15 +75,6 @@ class Consult extends Validator
return $value;
}
public function checkRating($rating)
{
if (!in_array($rating, [1, 2, 3, 4, 5])) {
throw new BadRequestException('consult.invalid_rating');
}
return $rating;
}
public function checkPrivateStatus($status)
{
if (!in_array($status, [0, 1])) {
@ -98,13 +93,26 @@ class Consult extends Validator
return $status;
}
public function checkIfAllowEdit(ConsultModel $consult)
public function checkTeacher(ConsultModel $consult, UserModel $user)
{
$repo = new CourseUserRepo();
$record = $repo->findCourseUser($consult->course_id, $user->id);
$privOk = $record && $record->role_type == CourseUserModel::ROLE_TEACHER;
if (!$privOk) {
throw new ForbiddenException('sys.forbidden');
}
}
public function checkConsultEdit(ConsultModel $consult)
{
/**
* (1)已回复不允许修改提问
* (2)发表三天以后不能修改提问
*/
$case1 = !empty($consult->answer);
$case1 = $consult->reply_time > 0;
$case2 = time() - $consult->create_time > 3 * 86400;
if ($case1 || $case2) {
@ -112,26 +120,6 @@ class Consult extends Validator
}
}
public function checkIfAllowRate(ConsultModel $consult)
{
/**
* 未回复不允许评价
*/
if (empty($consult->answer)) {
throw new BadRequestException('consult.rate_not_allowed');
}
/**
* 已评价,三天后不能更改评价
*/
$case1 = $consult->rating > 0;
$case2 = time() - $consult->answer_time > 3 * 86400;
if ($case1 && $case2) {
throw new BadRequestException('consult.rate_not_allowed');
}
}
public function checkIfDuplicated($question, $chapterId, $userId)
{
$repo = new ConsultRepo();

View File

@ -210,7 +210,6 @@ $error['consult.question_too_long'] = '问题内容太长多于1000个字符
$error['consult.answer_too_short'] = '回复内容太短少于15个字符';
$error['consult.answer_too_long'] = '回复内容太长多于1000个字符';
$error['consult.edit_not_allowed'] = '当前不允许修改操作';
$error['consult.rate_not_allowed'] = '当前不允许评价';
$error['consult.has_liked'] = '你已经点过赞啦';
/**

View File

@ -159,8 +159,8 @@ img.kg-avatar {
}
img.kg-qrcode {
width: 140px;
height: 140px;
width: 100px;
height: 100px;
border: 3px dashed #ccc;
}

View File

@ -48,6 +48,12 @@
border: 3px dashed #ccc;
}
.qrcode-sm img {
width: 100px;
height: 100px;
border: 3px dashed #ccc;
}
.breadcrumb {
position: relative;
margin-bottom: 20px;

View File

@ -0,0 +1,10 @@
layui.use(['layer'], function () {
var layer = layui.layer;
var clipboard = new ClipboardJS('.kg-copy');
clipboard.on('success', function (e) {
layer.msg('内容已经复制到剪贴板');
});
});

View File

@ -0,0 +1,48 @@
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
/**
* 查看咨询
*/
$('.btn-show-consult').on('click', function () {
var url = $(this).data('url');
layer.open({
type: 2,
title: '咨询详情',
content: url,
area: ['720px', '480px']
});
});
/**
* 回复咨询
*/
$('.btn-reply-consult').on('click', function () {
var url = $(this).data('url');
layer.open({
type: 2,
title: '回复咨询',
content: [url, 'no'],
area: ['720px', '400px'],
cancel: function () {
parent.location.reload();
}
});
});
/**
* 直播推流
*/
$('.btn-live-push').on('click', function () {
var url = $(this).data('url');
layer.open({
type: 2,
title: '直播推流',
content: [url, 'no'],
area: ['640px', '420px']
});
});
});