1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-08-02 04:42:41 +08:00

1.更改模块继承基类

2.升级layui到2.6.8
3.升级腾讯云播放器
4.点播增加外链支持
This commit is contained in:
koogua 2021-06-12 12:34:40 +08:00
parent 72c5b7e77c
commit 1d2f1dd266
196 changed files with 839 additions and 441 deletions

View File

@ -2,9 +2,9 @@
namespace App\Builders;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class Builder extends Component
class Builder extends Injectable
{
public function objects(array $items)

View File

@ -3,9 +3,9 @@
namespace App\Caches;
use Phalcon\Cache\Backend\Redis as RedisCache;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
abstract class Cache extends Component
abstract class Cache extends Injectable
{
/**

View File

@ -3,9 +3,9 @@
namespace App\Caches;
use App\Library\Cache\Backend\Redis as RedisCache;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
abstract class Counter extends Component
abstract class Counter extends Injectable
{
/**

View File

@ -63,7 +63,6 @@ class VodEventTask extends Task
* 获取不到时长视为失败
*/
if ($duration == 0) {
$attrs['file']['id'] = $fileId;
$attrs['file']['status'] = ChapterModel::FS_FAILED;
$chapter->update(['attrs' => $attrs]);
return;
@ -77,7 +76,6 @@ class VodEventTask extends Task
$vodService->createTransVideoTask($fileId);
}
$attrs['file']['id'] = $fileId;
$attrs['file']['status'] = ChapterModel::FS_TRANSLATING;
$attrs['duration'] = (int)$duration;
@ -106,7 +104,6 @@ class VodEventTask extends Task
* 获取不到处理结果视为失败
*/
if (empty($processResult)) {
$attrs['file']['id'] = $fileId;
$attrs['file']['status'] = ChapterModel::FS_FAILED;
$chapter->update(['attrs' => $attrs]);
return;

View File

@ -124,9 +124,13 @@ class ChapterController extends Controller
switch ($course->model) {
case CourseModel::MODEL_VOD:
$vod = $contentService->getChapterVod($chapter->id);
$playUrls = $contentService->getPlayUrls($chapter->id);
$cosPlayUrls = $contentService->getCosPlayUrls($chapter->id);
$remotePlayUrls = $contentService->getRemotePlayUrls($chapter->id);
$remoteDuration = $contentService->getRemoteDuration($chapter->id);
$this->view->setVar('vod', $vod);
$this->view->setVar('play_urls', $playUrls);
$this->view->setVar('cos_play_urls', $cosPlayUrls);
$this->view->setVar('remote_play_urls', $remotePlayUrls);
$this->view->setVar('remote_duration', $remoteDuration);
break;
case CourseModel::MODEL_LIVE:
$live = $contentService->getChapterLive($chapter->id);

View File

@ -10,14 +10,14 @@ use Phalcon\Mvc\ModuleDefinitionInterface;
class Module implements ModuleDefinitionInterface
{
public function registerAutoLoaders(DiInterface $di = null)
public function registerAutoLoaders(DiInterface $dependencyInjector = null)
{
}
public function registerServices(DiInterface $di)
public function registerServices(DiInterface $dependencyInjector)
{
$di->setShared('view', function () {
$dependencyInjector->setShared('view', function () {
$view = new MyView();
$view->setViewsDir(__DIR__ . '/Views');
$view->registerEngines([
@ -26,7 +26,7 @@ class Module implements ModuleDefinitionInterface
return $view;
});
$di->setShared('auth', function () {
$dependencyInjector->setShared('auth', function () {
return new AdminAuth();
});
}

View File

@ -3,9 +3,9 @@
namespace App\Http\Admin\Services;
use App\Services\Auth\Admin as AdminAuth;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class AuthMenu extends Component
class AuthMenu extends Injectable
{
protected $authInfo;

View File

@ -47,11 +47,37 @@ class ChapterContent extends Service
return $chapterRepo->findChapterOffline($chapterId);
}
public function getPlayUrls($chapterId)
public function getCosPlayUrls($chapterId)
{
$service = new ChapterVodService();
return $service->getPlayUrls($chapterId);
return $service->getCosPlayUrls($chapterId);
}
public function getRemotePlayUrls($chapterId)
{
$service = new ChapterVodService();
return $service->getRemotePlayUrls($chapterId);
}
public function getRemoteDuration($chapterId)
{
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterId);
$duration = $chapter->attrs['duration'] ?? 0;
$result = ['hours' => 0, 'minutes' => 0, 'seconds' => 0];
if ($duration == 0) return $result;
$result['hours'] = floor($duration / 3600);
$result['minutes'] = floor(($duration - $result['hours'] * 3600) / 60);
$result['seconds'] = $duration % 60;
return $result;
}
public function updateChapterContent($chapterId)
@ -84,6 +110,17 @@ class ChapterContent extends Service
{
$post = $this->request->getPost();
if (isset($post['file_id'])) {
$this->updateCosChapterVod($chapter);
} elseif (isset($post['file_remote'])) {
$this->updateRemoteChapterVod($chapter);
}
}
protected function updateCosChapterVod(ChapterModel $chapter)
{
$post = $this->request->getPost();
$validator = new ChapterVodValidator();
$fileId = $validator->checkFileId($post['file_id']);
@ -119,6 +156,54 @@ class ChapterContent extends Service
$this->updateCourseVodAttrs($vod->course_id);
}
protected function updateRemoteChapterVod(ChapterModel $chapter)
{
$post = $this->request->getPost();
$validator = new ChapterVodValidator();
$hours = $post['file_remote']['duration']['hours'] ?? 0;
$minutes = $post['file_remote']['duration']['minutes'] ?? 0;
$seconds = $post['file_remote']['duration']['seconds'] ?? 0;
$duration = 3600 * $hours + 60 * $minutes + $seconds;
$validator->checkDuration($duration);
$odUrl = $post['file_remote']['od']['url'] ?? '';
$hdUrl = $post['file_remote']['hd']['url'] ?? '';
$sdUrl = $post['file_remote']['sd']['url'] ?? '';
$fileRemote = [];
$attrs = $chapter->attrs;
if (!empty($odUrl)) {
$fileRemote['od']['url'] = $validator->checkFileUrl($odUrl);
$attrs['file']['status'] = ChapterModel::FS_UPLOADED;
$attrs['duration'] = $duration;
}
if (!empty($hdUrl)) {
$fileRemote['hd']['url'] = $validator->checkFileUrl($hdUrl);
}
if (!empty($sdUrl)) {
$fileRemote['sd']['url'] = $validator->checkFileUrl($sdUrl);
}
$chapterRepo = new ChapterRepo();
$vod = $chapterRepo->findChapterVod($chapter->id);
$vod->file_remote = $fileRemote;
$vod->update();
$chapter->attrs = $attrs;
$chapter->update();
$this->updateCourseVodAttrs($vod->course_id);
}
protected function updateChapterLive(ChapterModel $chapter)
{
$post = $this->request->getPost();

View File

@ -40,7 +40,7 @@
<td>{{ item.like_count }}</td>
<td>{{ publish_status(item.published) }}</td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
<li><a href="{{ answer_url }}" target="_blank">预览回答</a></li>

View File

@ -83,7 +83,7 @@
<td><input type="checkbox" name="featured" value="1" lay-skin="switch" lay-text="是|否" lay-filter="featured" data-url="{{ update_url }}" {% if item.featured == 1 %}checked="checked"{% endif %}></td>
<td><input type="checkbox" name="comment" value="1" lay-skin="switch" lay-text="是|否" lay-filter="closed" data-url="{{ update_url }}" {% if item.closed == 1 %}checked="checked"{% endif %}></td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
{% if item.published == 1 %}

View File

@ -67,7 +67,7 @@
<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="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">
<div class="kg-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>

View File

@ -1,66 +1,159 @@
{% set file_id = vod ? vod.file_id : '' %}
{% set action_url = url({'for':'admin.chapter.content','id':chapter.id}) %}
{% if play_urls %}
<fieldset class="layui-elem-field layui-field-title">
<legend>视频信息</legend>
</fieldset>
<table class="kg-table layui-table">
<tr>
<th>格式</th>
<th>时长</th>
<th>分辨率</th>
<th>码率</th>
<th>大小</th>
<th width="16%">操作</th>
</tr>
{% for item in play_urls %}
<tr>
<td>{{ item.format }}</td>
<td>{{ item.duration|duration }}</td>
<td>{{ item.width }} x {{ item.height }}</td>
<td>{{ item.rate }}kbps</td>
<td>{{ item.size }}M</td>
<td>
<span class="layui-btn layui-btn-sm kg-preview" data-chapter-id="{{ chapter.id }}" data-play-url="{{ item.url|url_encode }}">预览</span>
<span class="layui-btn layui-btn-sm kg-copy" data-clipboard-text="{{ item.url }}">复制</span>
</td>
</tr>
{% endfor %}
</table>
<br>
{% if vod.file_id is defined %}
{% set file_id = vod.file_id %}
{% else %}
{% set file_id = '' %}
{% endif %}
<fieldset class="layui-elem-field layui-field-title">
<legend>上传视频</legend>
</fieldset>
<form class="layui-form kg-form" id="vod-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}">
<div class="layui-form-item" id="upload-block">
<label class="layui-form-label">视频文件</label>
<div class="layui-input-block">
<span class="layui-btn" id="upload-btn">选择视频</span>
<input class="layui-hide" type="file" name="file" accept="video/*,audio/*">
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">腾讯云点播</li>
<li>外链云点播</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{% if cos_play_urls %}
<fieldset class="layui-elem-field layui-field-title">
<legend>视频信息</legend>
</fieldset>
<table class="kg-table layui-table">
<tr>
<th>格式</th>
<th>时长</th>
<th>分辨率</th>
<th>码率</th>
<th>大小</th>
<th width="16%">操作</th>
</tr>
{% for item in cos_play_urls %}
<tr>
<td>{{ item.format }}</td>
<td>{{ item.duration|duration }}</td>
<td>{{ item.width }} x {{ item.height }}</td>
<td>{{ item.rate }}kbps</td>
<td>{{ item.size }}M</td>
<td>
<span class="layui-btn kg-preview" data-chapter-id="{{ chapter.id }}" data-play-url="{{ item.url|url_encode }}">预览</span>
<span class="layui-btn layui-btn-primary kg-copy" data-clipboard-text="{{ item.url }}">复制</span>
</td>
</tr>
{% endfor %}
</table>
<br>
{% endif %}
<form class="layui-form kg-form" id="vod-form" method="POST" action="{{ action_url }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>上传视频</legend>
</fieldset>
<div class="layui-form-item" id="upload-block">
<label class="layui-form-label">视频文件</label>
<div class="layui-input-block">
<span class="layui-btn" id="upload-btn">选择视频</span>
<input class="layui-hide" type="file" name="file" accept="video/*,audio/*">
</div>
</div>
<div class="layui-form-item layui-hide" id="upload-progress-block">
<label class="layui-form-label">上传进度</label>
<div class="layui-input-block">
<div class="layui-progress layui-progress-big" lay-showpercent="yes" lay-filter="upload-progress" style="top:10px;">
<div class="layui-progress-bar" lay-percent="0%"></div>
</div>
</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="file_id" value="{{ file_id }}" readonly="readonly" 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>
</div>
<div class="layui-tab-item">
<form class="layui-form kg-form" method="POST" action="{{ action_url }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>外链视频</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">视频时长</label>
<div class="layui-input-block">
<div class="layui-inline">
<select name="file_remote[duration][hours]">
{% for value in 0..10 %}
{% set selected = value == remote_duration.hours ? 'selected="selected"' : '' %}
<option value="{{ value }}" {{ selected }}>{{ value }}小时</option>
{% endfor %}
</select>
</div>
<div class="layui-inline">
<select name="file_remote[duration][minutes]">
{% for value in 0..59 %}
{% set selected = value == remote_duration.minutes ? 'selected="selected"' : '' %}
<option value="{{ value }}" {{ selected }}>{{ value }}分钟</option>
{% endfor %}
</select>
</div>
<div class="layui-inline">
<select name="file_remote[duration][seconds]">
{% for value in 0..59 %}
{% set selected = value == remote_duration.seconds ? 'selected="selected"' : '' %}
<option value="{{ value }}" {{ selected }}>{{ value }}秒</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">原画地址</label>
<div class="layui-inline" style="width:55%;">
<input id="tc-od-url" class="layui-input" type="text" name="file_remote[od][url]" value="{{ remote_play_urls.od.url }}" lay-verify="required">
</div>
{% if remote_play_urls.od.url %}
<div class="layui-inline">
<span class="layui-btn kg-preview" data-chapter-id="{{ chapter.id }}" data-play-url="{{ remote_play_urls.hd.url }}">预览</span>
<span class="layui-btn layui-btn-primary kg-copy" data-clipboard-target="#tc-od-url">复制</span>
</div>
{% endif %}
</div>
<div class="layui-form-item">
<label class="layui-form-label">高清地址</label>
<div class="layui-inline" style="width:55%;">
<input id="tc-hd-url" class="layui-input" type="text" name="file_remote[hd][url]" value="{{ remote_play_urls.hd.url }}">
</div>
{% if remote_play_urls.hd.url %}
<div class="layui-inline">
<span class="layui-btn kg-preview" data-chapter-id="{{ chapter.id }}" data-play-url="{{ remote_play_urls.hd.url }}">预览</span>
<span class="layui-btn layui-btn-primary kg-copy" data-clipboard-target="#tc-hd-url">复制</span>
</div>
{% endif %}
</div>
<div class="layui-form-item">
<label class="layui-form-label">标清地址</label>
<div class="layui-inline" style="width:55%;">
<input id="tc-sd-url" class="layui-input" type="text" name="file_remote[sd][url]" value="{{ remote_play_urls.sd.url }}">
</div>
{% if remote_play_urls.hd.url %}
<div class="layui-inline">
<span class="layui-btn kg-preview" data-chapter-id="{{ chapter.id }}" data-play-url="{{ remote_play_urls.hd.url }}">预览</span>
<span class="layui-btn layui-btn-primary kg-copy" data-clipboard-target="#tc-sd-url">复制</span>
</div>
{% endif %}
</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>
</div>
</div>
<div class="layui-form-item layui-hide" id="upload-progress-block">
<label class="layui-form-label">上传进度</label>
<div class="layui-input-block">
<div class="layui-progress layui-progress-big" lay-showpercent="yes" lay-filter="upload-progress" style="top:10px;">
<div class="layui-progress-bar" lay-percent="0%"></div>
</div>
</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="file_id" value="{{ file_id }}" readonly="readonly" 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>
</div>

View File

@ -68,7 +68,7 @@
<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">
<div class="kg-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>

View File

@ -46,7 +46,7 @@
<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">
<div class="kg-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>

View File

@ -46,7 +46,7 @@
<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">
<div class="kg-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>

View File

@ -68,7 +68,7 @@
<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">
<div class="kg-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>

View File

@ -73,7 +73,7 @@
</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">
<div class="kg-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>

View File

@ -65,7 +65,7 @@
<td>{{ item.comment_count }}</td>
<td><input class="layui-input kg-priority" type="text" name="priority" value="{{ item.priority }}" data-url="{{ update_url }}"></td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-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>

View File

@ -95,7 +95,7 @@
<td><input type="checkbox" name="featured" value="1" lay-skin="switch" lay-text="是|否" lay-filter="featured" data-url="{{ update_url }}" {% if item.featured == 1 %}checked="checked"{% endif %}></td>
<td><input type="checkbox" name="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">
<div class="kg-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>

View File

@ -61,7 +61,7 @@
<td>{{ schedules_info(item.schedules) }}</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">
<div class="kg-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>

View File

@ -51,7 +51,7 @@
<td class="center"><input class="layui-input kg-priority" type="text" name="priority" title="数值越小排序越靠前" value="{{ item.priority }}" data-url="{{ update_url }}"></td>
<td class="center"><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">
<div class="kg-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>

View File

@ -74,7 +74,7 @@
<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">
<div class="kg-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>

View File

@ -84,7 +84,7 @@
<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="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">
<div class="kg-dropdown">
<button class="layui-btn layui-btn-sm">操作 <span class="layui-icon layui-icon-triangle-d"></span></button>
<ul>
<li><a href="{{ edit_url }}">编辑</a></li>

View File

@ -56,7 +56,7 @@
<td>{{ '¥%0.2f'|format(item.vip_price) }}</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">
<div class="kg-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>

View File

@ -50,7 +50,7 @@
<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">
<div class="kg-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>

View File

@ -66,7 +66,7 @@
<td><a class="layui-badge layui-bg-green" href="{{ redeem_url }}">{{ item.redeem_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">
<div class="kg-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>

View File

@ -16,9 +16,13 @@
{% endblock %}
{% block inline_js %}
{% block include_js %}
<script src="https://imgcache.qq.com/open/qcloud/video/vcplayer/TcPlayer-2.3.3.js"></script>
{{ js_include('lib/tc-player-2.4.0.js') }}
{% endblock %}
{% block inline_js %}
<script>

View File

@ -4,6 +4,10 @@
<div id="player"></div>
<div class="layui-hide">
<input type="hidden" name="play_url" value="{{ play_url }}">
</div>
{% endblock %}
{% block inline_css %}
@ -16,16 +20,24 @@
{% endblock %}
{% block inline_js %}
{% block include_js %}
<script src="https://imgcache.qq.com/open/qcloud/video/vcplayer/TcPlayer-2.3.3.js"></script>
{{ js_include('lib/tc-player-2.4.0.js') }}
{% endblock %}
{% block inline_js %}
<script>
layui.use(['jquery'], function () {
var $ = layui.jquery;
var playUrl = $('input[name=play_url]').val();
new TcPlayer('player', {
m3u8: '{{ play_url }}',
m3u8: playUrl,
autoplay: false,
width: 720,
height: 405

View File

@ -80,7 +80,7 @@
<td>{{ publish_status(item.published) }}</td>
<td><input type="checkbox" name="closed" value="1" lay-skin="switch" lay-text="是|否" lay-filter="closed" data-url="{{ update_url }}" {% if item.closed == 1 %}checked="checked"{% endif %}></td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
{% if item.published == 1 %}

View File

@ -63,7 +63,7 @@
</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">
<div class="kg-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>

View File

@ -62,7 +62,7 @@
</a>
</td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
{% if item.id == 1 %}

View File

@ -32,29 +32,21 @@
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">Logo</label>
<div class="kg-input-inline">
<input class="layui-input" type="text" name="logo" placeholder="请确保存储已正确配置" value="{{ site.logo }}">
</div>
<label class="layui-form-label">Logo</label>
<div class="layui-inline" style="width:40%;">
<input class="layui-input" type="text" name="logo" placeholder="请确保存储已正确配置" value="{{ site.logo }}">
</div>
<div class="layui-inline">
<div class="kg-input-inline">
<button class="layui-btn" type="button" id="upload-logo">上传文件</button>
</div>
<button class="layui-btn" type="button" id="upload-logo">上传文件</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">Favicon</label>
<div class="kg-input-inline">
<input class="layui-input" type="text" name="favicon" placeholder="请确保存储已正确配置" value="{{ site.favicon }}">
</div>
<label class="layui-form-label">Favicon</label>
<div class="layui-inline" style="width:40%;">
<input class="layui-input" type="text" name="favicon" placeholder="请确保存储已正确配置" value="{{ site.favicon }}">
</div>
<div class="layui-inline">
<div class="kg-input-inline">
<button class="layui-btn" type="button" id="upload-favicon">上传文件</button>
</div>
<button class="layui-btn" type="button" id="upload-favicon">上传文件</button>
</div>
</div>
<div class="layui-form-item">
@ -96,7 +88,7 @@
</div>
<div class="layui-inline">
<label class="layui-form-label">备案链接</label>
<div class="kg-input-inline">
<div class="kg-input-inline" style="width:500px;">
<input class="layui-input" type="text" name="icp_link" value="{{ site.icp_link }}">
</div>
</div>
@ -110,7 +102,7 @@
</div>
<div class="layui-inline">
<label class="layui-form-label">备案链接</label>
<div class="kg-input-inline">
<div class="kg-input-inline" style="width:500px;">
<input class="layui-input" type="text" name="police_link" value="{{ site.police_link }}">
</div>
</div>

View File

@ -58,7 +58,7 @@
<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="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">
<div class="kg-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>

View File

@ -83,7 +83,7 @@
{% endif %}
</td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-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>

View File

@ -59,7 +59,7 @@
<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">
<div class="kg-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>

View File

@ -8,7 +8,7 @@
<title>管理后台</title>
{{ icon_link('favicon.ico') }}
{{ css_link('lib/layui/css/layui.css') }}
{{ css_link('lib/layui/extends/dropdown.css') }}
{{ css_link('lib/layui/extends/kg-dropdown.css') }}
{{ css_link('admin/css/common.css') }}
{% block link_css %}{% endblock %}
{% block inline_css %}{% endblock %}

View File

@ -57,7 +57,7 @@
<td>{{ date('Y-m-d H:i',item.update_time) }}</td>
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="published" data-url="{{ update_url }}" {% if item.published == 1 %}checked="checked"{% endif %}></td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-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>

View File

@ -79,7 +79,7 @@
<td>{{ date('Y-m-d',item.active_time) }}</td>
<td>{{ date('Y-m-d',item.create_time) }}</td>
<td class="center">
<div class="layui-dropdown">
<div class="kg-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>

View File

@ -10,20 +10,20 @@ use Phalcon\Mvc\View;
class Module implements ModuleDefinitionInterface
{
public function registerAutoLoaders(DiInterface $di = null)
public function registerAutoLoaders(DiInterface $dependencyInjector = null)
{
}
public function registerServices(DiInterface $di)
public function registerServices(DiInterface $dependencyInjector)
{
$di->setShared('view', function () {
$dependencyInjector->setShared('view', function () {
$view = new View();
$view->disable();
return $view;
});
$di->setShared('auth', function () {
$dependencyInjector->setShared('auth', function () {
return new AppAuth();
});
}

View File

@ -10,14 +10,14 @@ use Phalcon\Mvc\ModuleDefinitionInterface;
class Module implements ModuleDefinitionInterface
{
public function registerAutoLoaders(DiInterface $di = null)
public function registerAutoLoaders(DiInterface $dependencyInjector = null)
{
}
public function registerServices(DiInterface $di)
public function registerServices(DiInterface $dependencyInjector)
{
$di->setShared('view', function () {
$dependencyInjector->setShared('view', function () {
$view = new MyView();
$view->setViewsDir(__DIR__ . '/Views');
$view->registerEngines([
@ -26,7 +26,7 @@ class Module implements ModuleDefinitionInterface
return $view;
});
$di->setShared('auth', function () {
$dependencyInjector->setShared('auth', function () {
return new HomeAuth();
});
}

View File

@ -71,7 +71,7 @@
{% block include_js %}
{{ js_include('https://imgcache.qq.com/open/qcloud/video/vcplayer/TcPlayer-2.3.3.js', false) }}
{{ js_include('lib/tc-player-2.4.0.js') }}
{{ js_include('home/js/chapter.live.player.js') }}
{{ js_include('home/js/chapter.live.chat.js') }}
{{ js_include('home/js/chapter.show.js') }}

View File

@ -57,7 +57,7 @@
{% block include_js %}
{{ js_include('https://imgcache.qq.com/open/qcloud/video/vcplayer/TcPlayer-2.3.3.js', false) }}
{{ js_include('lib/tc-player-2.4.0.js') }}
{{ js_include('home/js/course.share.js') }}
{{ js_include('home/js/chapter.show.js') }}
{{ js_include('home/js/chapter.vod.player.js') }}

View File

@ -4,7 +4,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>聊天记录</title>
<link rel="stylesheet" href="/static/lib/layui/css/layui.css">
{{ css_link('lib/layui/css/layui.css') }}
{{ css_link('lib/layui/extends/layim/assets/layim.css') }}
<style>
body .layim-chat-main {
height: auto;
@ -34,15 +35,15 @@
}); %>
</textarea>
<script src="/static/lib/layui/layui.js"></script>
{{ js_include('lib/layui/layui.js') }}
<script>
layui.use(['jquery', 'layim', 'laytpl', 'laypage'], function () {
layui.use(['jquery', 'laypage'], function () {
var $ = layui.jquery;
var layim = layui.layim;
var laytpl = layui.laytpl;
var laypage = layui.laypage;
var laytpl = parent.layui.laytpl;
laytpl.config({
open: '<%',
@ -97,6 +98,7 @@
}
});
</script>
</body>

View File

@ -24,7 +24,7 @@
<colgroup>
<col>
<col>
<col width="20%">
<col width="15%">
</colgroup>
<thead>
<tr>

View File

@ -54,7 +54,7 @@
<td>{{ item.comment_count }}</td>
<td>{{ item.view_count }}</td>
<td class="center">
<a href="{{ edit_url }}" class="layui-btn layui-btn-xs">编辑</a>
<a href="{{ edit_url }}" class="layui-btn layui-btn-xs layui-bg-blue">编辑</a>
<a href="javascript:" class="layui-btn layui-btn-xs layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</a>
</td>
</tr>

View File

@ -51,7 +51,7 @@
<td>{{ item.answer_count }}</td>
<td>{{ item.view_count }}</td>
<td class="center">
<a href="{{ edit_url }}" class="layui-btn layui-btn-xs">编辑</a>
<a href="{{ edit_url }}" class="layui-btn layui-btn-xs layui-bg-blue">编辑</a>
<a href="javascript:" class="layui-btn layui-btn-xs layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</a>
</td>
</tr>

View File

@ -41,7 +41,7 @@
<p class="rating">逻辑清晰:{{ "%0.1f"|format(item.rating3) }}</p>
</td>
<td>
<button class="layui-btn layui-btn-xs btn-edit-review" data-url="{{ edit_url }}">修改</button>
<button class="layui-btn layui-btn-xs layui-bg-blue btn-edit-review" data-url="{{ edit_url }}">修改</button>
<button class="layui-btn layui-btn-xs layui-bg-red kg-delete" data-url="{{ delete_url }}">删除</button>
</td>
</tr>

View File

@ -31,7 +31,7 @@ class Chapter extends Model
*/
protected $_vod_attrs = [
'duration' => 0,
'file' => ['id' => '', 'status' => self::FS_PENDING],
'file' => ['status' => self::FS_PENDING],
];
/**

View File

@ -42,6 +42,13 @@ class ChapterVod extends Model
*/
public $file_transcode = [];
/**
* 远程资源
*
* @var array|string
*/
public $file_remote = [];
/**
* 创建时间
*
@ -67,6 +74,10 @@ class ChapterVod extends Model
$this->file_transcode = kg_json_encode($this->file_transcode);
}
if (is_array($this->file_remote) || is_object($this->file_remote)) {
$this->file_remote = kg_json_encode($this->file_remote);
}
$this->create_time = time();
}
@ -76,6 +87,10 @@ class ChapterVod extends Model
$this->file_transcode = kg_json_encode($this->file_transcode);
}
if (is_array($this->file_remote) || is_object($this->file_remote)) {
$this->file_remote = kg_json_encode($this->file_remote);
}
$this->update_time = time();
}
@ -85,6 +100,10 @@ class ChapterVod extends Model
$this->file_transcode = json_decode($this->file_transcode, true);
}
if (is_string($this->file_remote)) {
$this->file_remote = json_decode($this->file_remote, true);
}
if (!empty($this->file_id) && empty($this->file_transcode)) {
$this->file_transcode = $this->getFileTranscode($this->file_id);
}

View File

@ -29,161 +29,161 @@ class User extends Model
*
* @var int
*/
public $id;
public $id = 0;
/**
* 名称
*
* @var string
*/
public $name;
public $name = '';
/**
* 头像
*
* @var string
*/
public $avatar;
public $avatar = '';
/**
* 头衔
*
* @var string
*/
public $title;
public $title = '';
/**
* 介绍
*
* @var string
*/
public $about;
public $about = '';
/**
* 地区
*
* @var string
*/
public $area;
public $area = '';
/**
* 性别
*
* @var int
*/
public $gender;
public $gender = self::GENDER_NONE;
/**
* 会员标识
*
* @var int
*/
public $vip;
public $vip = 0;
/**
* 锁定标识
*
* @var int
*/
public $locked;
public $locked = 0;
/**
* 删除标识
*
* @var int
*/
public $deleted;
public $deleted = 0;
/**
* 教学角色
*
* @var int
*/
public $edu_role;
public $edu_role = self::EDU_ROLE_STUDENT;
/**
* 后台角色
*
* @var int
*/
public $admin_role;
public $admin_role = 0;
/**
* 课程数
*
* @var int
*/
public $course_count;
public $course_count = 0;
/**
* 文章数
*
* @var int
*/
public $article_count;
public $article_count = 0;
/**
* 提问数
*
* @var int
*/
public $question_count;
public $question_count = 0;
/**
* 回答数
*
* @var int
*/
public $answer_count;
public $answer_count = 0;
/**
* 评论数
*
* @var int
*/
public $comment_count;
public $comment_count = 0;
/**
* 收藏数
*
* @var int
*/
public $favorite_count;
public $favorite_count = 0;
/**
* 会员期限
*
* @var int
*/
public $vip_expiry_time;
public $vip_expiry_time = 0;
/**
* 锁定期限
*
* @var int
*/
public $lock_expiry_time;
public $lock_expiry_time = 0;
/**
* 活跃时间
*
* @var int
*/
public $active_time;
public $active_time = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time;
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public $update_time = 0;
public function getSource(): string
{

View File

@ -2,10 +2,10 @@
namespace App\Providers;
use Phalcon\Di\Injectable;
use Phalcon\DiInterface;
use Phalcon\Mvc\User\Component;
abstract class Provider extends Component implements ProviderInterface
abstract class Provider extends Injectable implements ProviderInterface
{
/**

View File

@ -2,9 +2,9 @@
namespace App\Repos;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class Repository extends Component
class Repository extends Injectable
{
}

View File

@ -14,6 +14,24 @@ class ChapterVod extends Service
$vod = $chapterRepo->findChapterVod($chapterId);
/**
* 腾讯云点播优先
*/
if ($vod->file_id) {
$result = $this->getCosPlayUrls($chapterId);
} else {
$result = $this->getRemotePlayUrls($chapterId);
}
return $result;
}
public function getCosPlayUrls($chapterId)
{
$chapterRepo = new ChapterRepo();
$vod = $chapterRepo->findChapterVod($chapterId);
if (empty($vod->file_transcode)) return [];
$vodService = new VodService();
@ -21,9 +39,28 @@ class ChapterVod extends Service
$result = [];
foreach ($vod->file_transcode as $key => $file) {
$file['url'] = $vodService->getPlayUrl($file['url']);
$url = $vodService->getPlayUrl($file['url']);
$type = $this->getDefinitionType($file['height']);
$result[$type] = $file;
$result[$type] = ['url' => $url];
}
return $result;
}
public function getRemotePlayUrls($chapterId)
{
$chapterRepo = new ChapterRepo();
$vod = $chapterRepo->findChapterVod($chapterId);
$result = [
'od' => ['url' => ''],
'hd' => ['url' => ''],
'sd' => ['url' => ''],
];
if (!empty($vod->file_remote)) {
$result = $vod->file_remote;
}
return $result;

View File

@ -5,9 +5,9 @@ namespace App\Services\Search;
use App\Models\Article as ArticleModel;
use App\Repos\Category as CategoryRepo;
use App\Repos\User as UserRepo;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class ArticleDocument extends Component
class ArticleDocument extends Injectable
{
/**

View File

@ -5,9 +5,9 @@ namespace App\Services\Search;
use App\Models\Category as CategoryModel;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class CourseDocument extends Component
class CourseDocument extends Injectable
{
/**

View File

@ -4,9 +4,9 @@ namespace App\Services\Search;
use App\Models\ImGroup as GroupModel;
use App\Models\User as UserModel;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class GroupDocument extends Component
class GroupDocument extends Injectable
{
/**

View File

@ -6,9 +6,9 @@ use App\Models\Question as QuestionModel;
use App\Repos\Answer as AnswerRepo;
use App\Repos\Category as CategoryRepo;
use App\Repos\User as UserRepo;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class QuestionDocument extends Component
class QuestionDocument extends Injectable
{
/**

View File

@ -2,9 +2,9 @@
namespace App\Services\Search;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
abstract class Searcher extends Component
abstract class Searcher extends Injectable
{
/**

View File

@ -3,9 +3,9 @@
namespace App\Services\Search;
use App\Models\User as UserModel;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class UserDocument extends Component
class UserDocument extends Injectable
{
/**

View File

@ -19,4 +19,35 @@ class ChapterVod extends Validator
return $value;
}
public function checkDuration($duration)
{
$value = $value = $this->filter->sanitize($duration, ['trim', 'int']);
if ($value < 10 || $value > 10 * 3600) {
throw new BadRequestException('chapter_vod.invalid_duration');
}
return $value;
}
public function checkFileUrl($url)
{
$value = $this->filter->sanitize($url, ['trim', 'string']);
if (!CommonValidator::url($value)) {
throw new BadRequestException('chapter_vod.invalid_file_url');
}
$ext = strtolower(pathinfo($url, PATHINFO_EXTENSION));
/**
* 点播只支持mp4,m3u8格式
*/
if (!in_array($ext, ['mp4', 'm3u8'])) {
throw new BadRequestException('chapter_vod.invalid_file_ext');
}
return $value;
}
}

View File

@ -4,9 +4,9 @@ namespace App\Validators;
use App\Exceptions\Forbidden as ForbiddenException;
use App\Exceptions\Unauthorized as UnauthorizedException;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class Validator extends Component
class Validator extends Injectable
{
public function checkAuthUser($userId)

View File

@ -4,10 +4,10 @@ namespace Bootstrap;
use App\Library\Logger as AppLogger;
use Phalcon\Config as PhConfig;
use Phalcon\Di\Injectable;
use Phalcon\Logger\Adapter\File as PhLogger;
use Phalcon\Mvc\User\Component;
class ConsoleErrorHandler extends Component
class ConsoleErrorHandler extends Injectable
{
public function __construct()

View File

@ -9,9 +9,9 @@ use App\Exceptions\ServiceUnavailable as ServiceUnavailableException;
use App\Exceptions\Unauthorized as UnauthorizedException;
use App\Library\Logger as AppLogger;
use Phalcon\Config;
use Phalcon\Mvc\User\Component;
use Phalcon\Di\Injectable;
class HttpErrorHandler extends Component
class HttpErrorHandler extends Injectable
{
public function __construct()

76
composer.lock generated
View File

@ -15,16 +15,16 @@
"reference": "3e9d791b67d0a2912922b7b7c7312f4b37af41e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/3e9d791b67d0a2912922b7b7c7312f4b37af41e4",
"reference": "3e9d791b67d0a2912922b7b7c7312f4b37af41e4",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
"type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/3e9d791b67d0a2912922b7b7c7312f4b37af41e4",
"reference": "3e9d791b67d0a2912922b7b7c7312f4b37af41e4",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"dasprid/enum": "^1.0.3",
@ -1199,16 +1199,16 @@
"name": "Ashot Khanamiryan",
"email": "a.khanamiryan@gmail.com",
"homepage": "https://github.com/khanamiryan",
"role": "Developer"
"role": "Developer"
}
],
"description": "QR code decoder / reader",
"homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder/",
"keywords": [
"barcode",
"qr",
"zxing"
],
"description": "QR code decoder / reader",
"homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder/",
"keywords": [
"barcode",
"qr",
"zxing"
],
"support": {
"issues": "https://github.com/khanamiryan/php-qrcode-detector-decoder/issues",
"source": "https://github.com/khanamiryan/php-qrcode-detector-decoder/tree/1.0.4"
@ -1333,16 +1333,16 @@
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1"
},
@ -5348,15 +5348,15 @@
"homepage": "http://riimu.net"
}
],
"description": "Highly customizable alternative to var_export for PHP code generation",
"homepage": "http://kit.riimu.net",
"keywords": [
"code",
"encoder",
"export",
"generator",
"variable"
],
"description": "Highly customizable alternative to var_export for PHP code generation",
"homepage": "http://kit.riimu.net",
"keywords": [
"code",
"encoder",
"export",
"generator",
"variable"
],
"support": {
"issues": "https://github.com/Riimu/Kit-PHPEncoder/issues",
"source": "https://github.com/Riimu/Kit-PHPEncoder/tree/v2.4.1"
@ -5377,5 +5377,5 @@
"ext-fileinfo": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.0.0"
}

View File

@ -110,7 +110,7 @@ $error['nav.has_child_node'] = '不允许相关操作(存在子节点)';
/**
* 文章相关
*/
$error['article.not_found'] = '课程不存在';
$error['article.not_found'] = '文章不存在';
$error['article.title_too_short'] = '标题太短少于5个字符';
$error['article.title_too_long'] = '标题太长多于50个字符';
$error['article.content_too_short'] = '内容太短少于10个字符';
@ -254,6 +254,8 @@ $error['chapter.child_existed'] = '不允许相关操作(存在子章节)';
*/
$error['chapter_vod.not_found'] = '点播资源不存在';
$error['chapter_vod.invalid_file_id'] = '无效的文件编号';
$error['chapter_vod.invalid_file_url'] = '无效的文件地址';
$error['chapter_vod.invalid_file_ext'] = '无效的文件格式目前只支持mp4m3u8';
/**
* 直播相关

View File

@ -0,0 +1,44 @@
<?php
use Phinx\Migration\AbstractMigration;
final class V20210610034658 extends AbstractMigration
{
public function up()
{
$this->modifyChapterVodTable();
$this->handleVodFileRemote();
}
public function down()
{
$this->table('kg_chapter_vod')
->removeColumn('file_remote')
->save();
}
protected function modifyChapterVodTable()
{
$this->table('kg_chapter_vod')
->addColumn('file_remote', 'string', [
'null' => false,
'default' => '',
'limit' => 1500,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '远程文件',
'after' => 'file_transcode',
])->save();
}
protected function handleVodFileRemote()
{
$this->getQueryBuilder()
->update('kg_chapter_vod')
->set('file_remote', '[]')
->where(['file_remote' => ''])
->execute();
}
}

View File

@ -1,10 +1,10 @@
layui.config({
base: '/static/lib/layui/extends/'
}).extend({
dropdown: 'dropdown'
kgDropdown: 'kg-dropdown'
});
layui.use(['jquery', 'form', 'element', 'layer', 'dropdown'], function () {
layui.use(['jquery', 'form', 'element', 'layer', 'kgDropdown'], function () {
var $ = layui.jquery;
var form = layui.form;

View File

@ -1,7 +1,9 @@
layui.config({
base: '/static/lib/layui/extends/'
base: '/static/lib/layui/extends/',
layimAssetsPath: '/static/lib/layui/extends/layim/assets/',
}).extend({
helper: 'helper'
layim: 'layim/layim',
helper: 'helper',
});
layui.use(['jquery', 'form', 'element', 'layer', 'helper'], function () {

BIN
public/static/lib/layui.zip Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1 @@
/** layui-v2.5.6 MIT License By https://www.layui.com */
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #eee;border-left-width:6px;background-color:#FAFAFA;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:40px;line-height:40px;border-bottom:1px solid #eee}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 10px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view .layui-code-ol li:first-child{padding-top:10px}.layui-code-view .layui-code-ol li:last-child{padding-bottom:10px}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}.layui-code-demo .layui-code{visibility:visible!important;margin:-15px;border-top:none;border-right:none;border-bottom:none}.layui-code-demo .layui-tab-content{padding:15px;border-top:none}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,11 +0,0 @@
/*dropdown*/
.layui-dropdown {display: inline-block;position: relative;font-size: 14px;}
.layui-dropdown>ul {position: absolute;display: none;top:100%;left:0px;padding: 5px 0;margin:5px 0;white-space: nowrap;line-height: 36px;box-shadow: 0 2px 4px rgba(0,0,0,.12);border: 1px solid #d2d2d2;background-color: #fff;border-radius: 2px;z-index: 900;}
.layui-dropdown>ul>h4 {color: #999;display: block;padding: 0 20px;font-size: 12px;}
.layui-dropdown>ul>li {position: relative;}
.layui-dropdown>ul>li>a {color: #333;display: block;padding: 0 20px;transition: all .3s;-webkit-transition: all .3s;}
.layui-dropdown>ul>li>a>i {font-size: 14px;margin-right:5px;}
.layui-dropdown>ul>li>a:hover {background-color: #f2f2f2;color: #000;}
.layui-dropdown.dropdown-right>ul {left:auto;right:0px;}
.layui-dropdown>ul>li.dropdown-item-disabled>a{color: #999;cursor:not-allowed;}
.layui-dropdown>ul>li.dropdown-item-disabled>a:hover{color: #999;background-color:transparent;}

View File

@ -0,0 +1,66 @@
/*dropdown*/
.kg-dropdown {
display: inline-block;
position: relative;
font-size: 14px;
}
.kg-dropdown > ul {
position: absolute;
display: none;
top: 100%;
left: 0px;
padding: 5px 0;
margin: 5px 0;
white-space: nowrap;
line-height: 36px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12);
border: 1px solid #d2d2d2;
background-color: #fff;
border-radius: 2px;
z-index: 900;
}
.kg-dropdown > ul > h4 {
color: #999;
display: block;
padding: 0 20px;
font-size: 12px;
}
.kg-dropdown > ul > li {
position: relative;
}
.kg-dropdown > ul > li > a {
color: #333;
display: block;
padding: 0 20px;
transition: all .3s;
-webkit-transition: all .3s;
}
.kg-dropdown > ul > li > a > i {
font-size: 14px;
margin-right: 5px;
}
.kg-dropdown > ul > li > a:hover {
background-color: #f2f2f2;
color: #000;
}
.kg-dropdown.dropdown-right > ul {
left: auto;
right: 0px;
}
.kg-dropdown > ul > li.dropdown-item-disabled > a {
color: #999;
cursor: not-allowed;
}
.kg-dropdown > ul > li.dropdown-item-disabled > a:hover {
color: #999;
background-color: transparent;
}

View File

@ -4,8 +4,8 @@
layui.define(['jquery'], function (exports) {
var MOD_NAME = 'dropdown',
CLASS_NAME = '.layui-dropdown',
var MOD_NAME = 'kgDropdown',
CLASS_NAME = '.kg-dropdown',
$ = layui.jquery;
var dropdown = {
@ -58,4 +58,4 @@ layui.define(['jquery'], function (exports) {
//输出接口
exports(MOD_NAME, dropdown);
});
});

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>聊天记录</title>
<link rel="stylesheet" href="http://local.res.layui.com/layui/src/css/layui.css">
<link rel="stylesheet" href="//unpkg.com/layui/dist/css/layui.css">
<style>
body .layim-chat-main {
height: auto;
@ -23,7 +23,7 @@
<textarea title="消息模版" id="LAY_tpl" style="display:none;">
{{# layui.each(d.data, function(index, item){
if(item.id == parent.layui.layim.cache().mine.id){ }}
if(item.id == layui.layim.cache().mine.id){ }}
<li class="layim-chat-mine"><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite><i>{{ layui.data.date(item.timestamp) }}</i>{{ item.username }}</cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
{{# } else { }}
<li><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite>{{ item.username }}<i>{{ layui.data.date(item.timestamp) }}</i></cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
@ -32,22 +32,24 @@
</textarea>
<!--
上述模版采用了 laytpl 语法不了解的同学可以去看下文档http://www.layui.com/doc/modules/laytpl.html
上述模版采用了 laytpl 语法
-->
<script src="http://local.res.layui.com/layui/src/layui.js"></script>
<script src="//unpkg.com/layui/dist/layui.js"></script>
<script>
layui.use(['layim', 'laypage'], function () {
var layim = layui.layim
, layer = layui.layer
, laytpl = layui.laytpl
layui.link('../layim.css', 'skinlayimcss') //加载 css
layui.config({
layimPath: '../../' //配置 layim.js 所在目录
, layimAssetsPath: '../../layim-assets/' //layim 资源文件所在目录
}).use(['jquery'], function () {
var layim = parent.layui.layim
, laytpl = parent.layui.laytpl
, $ = layui.jquery
, laypage = layui.laypage;
//聊天记录的分页此处不做演示你可以采用laypage不了解的同学见文档http://www.layui.com/doc/modules/laypage.html
, laypage = parent.layui.laypage;
//聊天记录的分页此处不做演示,你可以采用 laypage
//开始请求聊天记录
var param = location.search //获得URL参数。该窗口url会携带会话id和type他们是你请求聊天记录的重要凭据
@ -59,27 +61,27 @@
, data: [{
username: '纸飞机'
, id: 100000
, avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
, avatar: '' || layui.cache.layimAssetsPath + 'images/default.png'
, timestamp: 1480897882000
, content: 'face[抱抱] face[心] 你好啊小美女'
, content: '我方模拟记录111'
}, {
username: 'Z_子晴'
username: 'test123'
, id: 108101
, avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
, avatar: '' || layui.cache.layimAssetsPath + 'images/default.png'
, timestamp: 1480897892000
, content: '你没发错吧face[微笑]'
, content: '对方模拟记录111'
}, {
username: 'Z_子晴'
username: 'test123'
, id: 108101
, avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
, avatar: '' || layui.cache.layimAssetsPath + 'images/default.png'
, timestamp: 1480897898000
, content: '你是谁呀亲。。我爱的是贤心!我爱的是贤心!我爱的是贤心!重要的事情要说三遍~'
, content: '对方模拟记录222'
}, {
username: 'Z_子晴'
username: 'test123'
, id: 108101
, avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
, avatar: '' || layui.cache.layimAssetsPath + 'images/default.png'
, timestamp: 1480897908000
, content: '注意:这些都是模拟数据,实际使用时,需将其中的模拟接口改为你的项目真实接口。\n该模版文件所在目录相对于layui.js\n/css/modules/layim/html/chatlog.html'
, content: '注意:这些都是模拟数据,实际使用时,需将其中的模拟接口改为你的项目真实接口。\n该模版文件所在目录相对于layim.js\n/layim-assets/html/chatlog.html'
}]
}

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>发现</title>
<link rel="stylesheet" href="http://local.res.layui.com/layui/src/css/layui.css">
<link rel="stylesheet" href="//unpkg.com/layui/dist/css/layui.css">
<style>
</style>
@ -14,14 +14,19 @@
<div style="margin: 15px;">
<blockquote class="layui-elem-quote">此为自定义的【查找】页面,因需求不一,所以官方暂不提供该模版结构与样式,实际使用时,可移至该文件到你的项目中,对页面自行把控。
<br>文件所在目录相对于layui.js/css/modules/layim/html/find.html
<br>文件所在目录相对于layui.js/layim-assets/html/find.html
</blockquote>
</div>
<script src="http://local.res.layui.com/layui/src/layui.js"></script>
<script src="//unpkg.com/layui/dist/layui.js"></script>
<script>
layui.use(['layim', 'laypage'], function () {
layui.config({
layimPath: '../../' //配置 layim.js 所在目录
, layimAssetsPath: '../../layim-assets/' //layim 资源文件所在目录
}).extend({
layim: layui.cache.layimPath + 'layim' //配置 layim 组件所在的路径
}).use(['layim', 'laypage'], function () {
var layim = layui.layim
, layer = layui.layer
, laytpl = layui.laytpl

View File

@ -9,14 +9,14 @@
"from": 166488,
"from_group": 0,
"type": 1,
"remark": "有问题要问",
"remark": "test1",
"href": null,
"read": 1,
"time": "刚刚",
"user": {
"id": 166488,
"avatar": "http://q.qlogo.cn/qqapp/101235792/B704597964F9BD0DB648292D1B09F7E8/100",
"username": "李彦宏",
"username": "测试111",
"sign": null
}
},
@ -27,20 +27,20 @@
"from": 347592,
"from_group": 0,
"type": 1,
"remark": "你好啊!",
"remark": "test2",
"href": null,
"read": 1,
"time": "刚刚",
"user": {
"id": 347592,
"avatar": "http://q.qlogo.cn/qqapp/101235792/B78751375E0531675B1272AD994BA875/100",
"username": "麻花疼",
"username": "测试222",
"sign": null
}
},
{
"id": 62,
"content": "雷军 拒绝了你的好友申请",
"content": "测试333 拒绝了你的好友申请",
"uid": 168,
"from": null,
"from_group": null,
@ -55,22 +55,7 @@
},
{
"id": 60,
"content": "马小云 已经同意你的好友申请",
"uid": 168,
"from": null,
"from_group": null,
"type": 1,
"remark": null,
"href": null,
"read": 1,
"time": "10天前",
"user": {
"id": null
}
},
{
"id": 61,
"content": "贤心 已经同意你的好友申请",
"content": "测试666 已经同意你的好友申请",
"uid": 168,
"from": null,
"from_group": null,

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>消息盒子</title>
<link rel="stylesheet" href="../../../layui.css?v=1">
<link rel="stylesheet" href="//unpkg.com/layui/dist/css/layui.css">
<style>
.layim-msgbox {
margin: 15px;
@ -15,6 +15,7 @@
position: relative;
margin-bottom: 10px;
padding: 0 130px 10px 60px;
padding-bottom: 10px;
line-height: 22px;
border-bottom: 1px dotted #e2e2e2;
}
@ -76,7 +77,7 @@
<div style="margin: 0 15px;">
<blockquote class="layui-elem-quote">注意:这些都是模拟数据,实际使用时,需将其中的模拟接口改为你的项目真实接口。
<br>该模版文件所在目录相对于layui.js/css/modules/layim/html/msgbox.html
<br>该模版文件所在目录(相对于 layim.js/html/msgbox.html
</blockquote>
</div>
@ -109,13 +110,18 @@
</textarea>
<!--
上述模版采用了 laytpl 语法不了解的同学可以去看下文档http://www.layui.com/doc/modules/laytpl.html
上述模版采用了 laytpl 语法
-->
<script src="../../../../layui.js?v=1"></script>
<script src="//unpkg.com/layui/dist/layui.js"></script>
<script>
layui.use(['layim', 'flow'], function () {
layui.config({
layimPath: '../../' //配置 layim.js 所在目录
, layimAssetsPath: '../../layim-assets/' //layim 资源文件所在目录
}).extend({
layim: layui.cache.layimPath + 'layim' //配置 layim 组件所在的路径
}).use(['layim', 'flow'], function () {
var layim = layui.layim
, layer = layui.layer
, laytpl = layui.laytpl
@ -233,15 +239,20 @@
, uid = li.data('uid');
layer.confirm('确定拒绝吗?', function (index) {
layer.close(index);
othis.parent().html('<em>已拒绝</em>');
/*
$.post('/im/refuseFriend', {
uid: uid //对方用户ID
}, function (res) {
if (res.code != 0) {
return layer.msg(res.msg);
}
layer.close(index);
othis.parent().html('<em>已拒绝</em>');
uid: uid //对方用户ID
}, function(res){
if(res.code != 0){
return layer.msg(res.msg);
}
layer.close(index);
othis.parent().html('<em>已拒绝</em>');
});
*/
});
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

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