Commit 98b9beca authored by xiabin's avatar xiabin

Merge remote-tracking branch 'origin/dev' into dev

parents 846ee92f 74189245
......@@ -13,6 +13,7 @@ use common\libs\Utils;
use common\models\Classes;
use common\models\Course;
use common\models\Courseware;
use common\models\TranscodeQueue;
use common\models\User;
use Yii;
use yii\base\ExitException;
......@@ -83,18 +84,21 @@ class CourseController extends Controller
case Course::STATUS_TRANSCODING:
if ($check == Course::STATUS_TRANSCODING) {
//如果该内容课件已经拥有转码后的标准内容,则不再进行转码,直接进入已审核状态
if (!$courseware->src_stand) {
if ($courseware->src_stand) {
$check = Course::STATUS_OK;
} else {
//发起转码流程
if (!Courseware::sendTranscodeSignal($courseware)) {
Utils::ajaxResponse(false, '审核失败,发送转码请求失败!');
$result = TranscodeQueue::sendTranscodeSignal($course, $courseware);
if ($result === false) {
Utils::ajaxResponse(false, TranscodeQueue::$lastError);
} elseif ($result === null) {
$check = Course::STATUS_OK;
}
}
}
$course->status = $check;
$courseware->status = Courseware::STATUS_OK;
$courseware->status = $check;
if ($course->save() && $courseware->save() && $msg) {
$template = Yii::$app->params['course.check.msg.ok'];
......@@ -147,18 +151,20 @@ class CourseController extends Controller
public function actionSig()
{
$courseware = Courseware::findOne(['course_id'=>7]);
$courseware = Courseware::findOne(['course_id'=>6]);
if (!$courseware) {
throw new ExitException('Courseware not found!');
}
$course = Course::findOne(['id'=>$courseware->course_id]);
print_r($courseware->toArray());
$res = Courseware::sendTranscodeSignal($courseware);
$res = TranscodeQueue::sendTranscodeSignal($course, $courseware);
if (!$res) {
echo Courseware::$lastError;
echo TranscodeQueue::$lastError;
}
}
......
<?php
/**
* Created by PhpStorm.
* User: pader
* Date: 2016/10/26
* Time: 11:15
*/
namespace common\libs;
use Qiniu\Processing\PersistentFop;
use Qiniu\Storage\BucketManager;
use yii\helpers\Url;
/**
* 七牛转码类
*
* @package common\libs
*/
class QiniuTranscode
{
public static $lastError;
const TARGET_FORMAT = 'mp4';
/**
* 发送指定课件的转码信号
*
* @param string $sourceKey
* @param string $targetKey
* @return string|boolean 成功返回处理的 presistentId 或者返回 false
*/
public static function startTranscode($sourceKey, $targetKey)
{
/* @var $qiniu \common\components\Qiniu */
$qiniu = \Yii::$app->qiniu;
$auth = $qiniu->getAuthInstance();
//获取视频元信息,对比与目标格式的区别
$baseUrl = $qiniu->getStorageConfig('video', 'url').$sourceKey;
$avUrl = $auth->privateDownloadUrl($baseUrl.'?avinfo');
$response = \Qiniu\Http\Client::get($avUrl);
$avInfo = $response->json();
if (!$avInfo) {
self::$lastError = '获取视频文件信息失败';
}
if (isset($avInfo['error'])) {
self::$lastError = $avInfo['error'];
return false;
}
$fops = self::transcodeFops($avInfo);
$bucket = $qiniu->getStorageConfig('video', 'bucket');
//存储位置
$target = $bucket.':'.$targetKey;
$notifyUrl = YII_ENV == 'dev'
? 'http://t.pp.cc/tmp/qiniu_callback.php'
: Url::to(['qiniu-callback/transcode'], true);;
/* @var $err null|\Qiniu\Http\Error */
if ($fops) {
$fops .= '|saveas/'.\Qiniu\base64_urlSafeEncode($target);
//发送处理请求
$persistent = new PersistentFop($auth, $bucket, null, $notifyUrl);
list($presistendId, $err) = $persistent->execute($sourceKey, $fops);
if ($err) {
self::$lastError = $err;
return false;
} else {
return $presistendId;
}
} else {
//不需要转换时则直接拷贝资源
$bucketMgr = new BucketManager($auth);
$err = $bucketMgr->copy($bucket, $sourceKey, $bucket, $targetKey);
if ($err) {
self::$lastError = $err->message();
return false;
}
return true;
}
}
/**
* 生成免费视频
*
* @param string $standKey
* @param string $targetKey
* @param int $freeTime 免费分钟数
* @return string|bool
*/
public static function transFreeVideo($standKey, $targetKey, $freeTime)
{
/* @var $qiniu \common\components\Qiniu */
$qiniu = \Yii::$app->qiniu;
$auth = $qiniu->getAuthInstance();
$bucket = $qiniu->getStorageConfig('video', 'bucket');
//获取视频元信息,对比与目标格式的区别
$baseUrl = $qiniu->getStorageConfig('video', 'url').$standKey;
$avUrl = $auth->privateDownloadUrl($baseUrl.'?avinfo');
$response = \Qiniu\Http\Client::get($avUrl);
$avInfo = $response->json();
if (!$avInfo) {
self::$lastError = '获取视频文件信息失败';
}
if (isset($avInfo['error'])) {
self::$lastError = $avInfo['error'];
return false;
}
$fops = self::transcodeFops($avInfo, true);
//存储位置
$target = $bucket.':'.$targetKey;
$fops = $fops.'/ss/0/t/'.($freeTime*60).'|saveas/'.\Qiniu\base64_urlSafeEncode($target);
$notifyUrl = YII_ENV == 'dev'
? 'http://t.pp.cc/tmp/qiniu_callback.php'
: Url::to(['qiniu-callback/transcode'], true);;
//发送处理请求
/* @var $err null|\Qiniu\Http\Error */
$persistent = new PersistentFop($auth, $bucket, null, $notifyUrl);
list($presistendId, $err) = $persistent->execute($standKey, $fops);
if ($err) {
self::$lastError = $err->message();
return false;
}
return $presistendId;
}
/**
* 对比并返回
*
* @param array $avInfo
* @param bool $force
* @return string
*/
public static function transcodeFops($avInfo, $force=false)
{
//转换目录最大参数
$target = [
'width' => 1280, //分辨率
'height' => 720,
'ab' => '64k', //音频码率
'ar' => 44100, //音频采样率
'vb' => 1950*1000, //视频码率1950k
'vcodec' => 'libx264', //视频编码方案
'acodec' => 'aac', //音频编码方案
'fps' => 30 //FPS
];
list($video, $audio) = $avInfo['streams'];
$fopsMain = ''; //必要参数,当必要参数具备时一定转换编码
$fopsAddon = '/ab/'.$target['ab']; //非必要参数,只有必要参数具备,非必要参数才会作为附属添加进去
//音频采样率对比
if ($audio['sample_rate'] > $target['ar']) {
$fopsAddon .= '/ar/'.$target['ar'];
}
//视频采样率对比
//if ($video['bit_rate'] > $target['vb']) {
$fopsAddon .= '/vb/'.$target['vb'];
//}
//编码对比
if ($video['codec_name'] != 'h264') {
$fopsMain .= '/vcodec/' . $target['vcodec'];
}
if ($audio['codec_name'] != 'aac') {
$fopsMain .= '/acodec/'.$target['acodec'];
}
//FPS 对比
$fpsRate = explode('/', $video['r_frame_rate']);
if ($fpsRate['0'] > $target['fps']) {
$fopsMain .= '/r/'.$target['fps'];
}
//分辨率对比,以原比例限定在指定分辨率以内
if ($force) {
$fopsMain .= '/s/' . ($video['width'] > $target['width'] ? $target['width'] : $video['width'])
. 'x' . ($video['height'] > $target['height'] ? $target['height'] : $video['height']) . '/autoscale/1';
} elseif ($video['width'] > $target['width'] || $video['height'] > $target['height']) {
$fopsMain .= '/s/'.$target['width'].'x'.$target['height'].'/autoscale/1';
}
$fops = '';
//如果视频不是目标格式,或有必要参数,则进行转码
if ($force || strpos($avInfo['format']['format_name'], self::TARGET_FORMAT) === false) {
$fops = 'avthumb/'.self::TARGET_FORMAT.$fopsMain.$fopsAddon;
}
return $fops;
}
}
\ No newline at end of file
......@@ -90,7 +90,7 @@ class Course extends \yii\db\ActiveRecord
['free_time', 'integer', 'min' => 0, 'max' => 15],
['recommend', 'boolean'],
[['recommend', 'comment_count', 'favorite_count', 'support_count', 'play_count'], 'default', 'value' => 0],
['status', 'in', 'range' => [0, 1, 2, 3]]
['status', 'in', 'range' => [0, 1, 2, 3, 4]]
];
}
......
......@@ -30,8 +30,6 @@ class Courseware extends ActiveRecord
const STATUS_FAILED = 2;
const STATUS_LOCKED = 3;
public static $lastError = '';
/**
* @inheritdoc
*/
......@@ -109,60 +107,5 @@ class Courseware extends ActiveRecord
$result = self::find()->select('id')->where('id=:id', [':id' => $id])->one();
return $result === NULL ? FALSE : TRUE;
}
/**
* 发送指定课件的转码信号
*
* @param Courseware $courseware
* @return bool
*/
public static function sendTranscodeSignal($courseware)
{
//非视频不可发送转码请求
if ($courseware->type != 0) {
self::$lastError = '视频不可发送转码请求';
return false;
}
//已有转码后的视频不可再发送转码请求
if ($courseware->src_stand) {
self::$lastError = '已有转码后的视频不可再发送转码请求';
return false;
}
/**
* @var $qiniu \common\components\Qiniu
*/
$qiniu = \Yii::$app->qiniu;
$auth = $qiniu->getAuthInstance();
//获取视频元信息,对比与目标格式的区别
$baseUrl = $qiniu->getStorageConfig('video', 'url').$courseware->name;
$avUrl = $auth->privateDownloadUrl($baseUrl.'?avinfo');
$response = \Qiniu\Http\Client::get($avUrl);
$avInfo = $response->json();
if (!$avInfo) {
self::$lastError = '获取视频文件信息失败';
}
if (isset($avInfo['error'])) {
self::$lastError = $avInfo['error'];
return false;
}
list($video, $audio) = $avInfo['streams'];
exit;
$bucket = $qiniu->getStorageConfig('video', 'bucket');
$fops = '';
$notifyUrl = 'http://t.pp.cc/tmp/qiniu_callback.php';
$persistent = new \Qiniu\Processing\PersistentFop($auth, $bucket, null, $notifyUrl);
$$persistent->execute($courseware->name, $fops);
}
}
<?php
namespace common\models;
use common\libs\QiniuTranscode;
use common\libs\Utils;
use Yii;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
/**
* This is the model class for table "transcode_queue".
*
* @property string $id
* @property integer $course_id
* @property integer $courseware_id
* @property string $type
* @property string $src
* @property string $target
* @property int $status
* @property integer $created_at
* @property integer $updated_at
*/
class TranscodeQueue extends ActiveRecord
{
const STATUS_WAIT = 0; //进行等待中
const STATUS_DONE = 1; //已完成
//const STATUS_DISCARD = 2;
public static $lastError = '';
/**
* @inheritdoc
*/
public static function tableName()
{
return 'transcode_queue';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['id', 'course_id', 'courseware_id', 'type', 'src', 'target'], 'required'],
[['course_id', 'courseware_id', 'status', 'created_at', 'updated_at'], 'integer'],
[['id', 'type'], 'string']
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'course_id' => 'Course ID',
'courseware_id' => 'Courseware ID',
'type' => 'Type',
'src' => 'Src',
'target' => 'Target',
'status' => 'Status',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
];
}
/**
* @inheritdoc
*/
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
]
]
];
}
/**
* 发送指定课件的转码信号
*
* 进入队列后,一旦转码完成,通知会自动理发课程与课件的状态为已审核
*
* @param Course $course
* @param Courseware $courseware
* @return bool|null 返回null代表转码完成,true代表进入队列,false代表失败
*/
public static function sendTranscodeSignal($course, $courseware)
{
//非视频不可发送转码请求
if ($courseware->type != 0) {
self::$lastError = '视频不可发送转码请求';
return false;
}
//已有转码后的视频不可再发送转码请求
if ($courseware->src_stand) {
self::$lastError = '已有转码后的视频不可再发送转码请求';
return false;
}
/* @var $qiniu \common\components\Qiniu */
$qiniu = \Yii::$app->qiniu;
$srcStand = 'stand/'.$qiniu->generateRandomFilePath('.'.QiniuTranscode::TARGET_FORMAT);;
$result = QiniuTranscode::startTranscode($courseware->name, $srcStand);
if ($result === false) {
self::$lastError = QiniuTranscode::$lastError;
return false;
}
//返回 true 为直接拷贝完成的情况,否则为进入了转码队列中
if ($result === true) {
if ($courseware->src_stand && $courseware->src_stand != $srcStand) {
UploadPools::deleteFile('video:'.$courseware->src_stand);
}
$courseware->src_stand = $srcStand;
$courseware->save();
//如果有免费时间,发起截取转码免费部分的请求
if ($course->free_time > 0) {
return (bool)self::sendFreeTranscodeSignal($courseware->course_id, $courseware->id, $srcStand, $course->free_time);
}
return null;
} else {
//进入队列
$queue = new self();
$queue->id = $result;
$queue->course_id = $courseware->course_id;
$queue->courseware_id = $courseware->id;
$queue->type = 'stand';
$queue->src = $courseware->name;
$queue->target = $srcStand;
if (!$queue->save()) {
self::$lastError = Utils::fetchErrorFirst($queue->errors);
return false;
}
return true;
}
}
public static function sendFreeTranscodeSignal($courseId, $coursewareId, $srcStand, $freeTime)
{
/* @var $qiniu \common\components\Qiniu */
$qiniu = \Yii::$app->qiniu;
$srcFree = 'free/'.$qiniu->generateRandomFilePath('.'.QiniuTranscode::TARGET_FORMAT);
$presistendId = QiniuTranscode::transFreeVideo($srcStand, $srcFree, $freeTime);
if ($presistendId == false) {
self::$lastError = '免费视频转码请求发送失败:'.QiniuTranscode::$lastError;
return false;
}
$newQueue = new TranscodeQueue();
$newQueue->id = $presistendId;
$newQueue->course_id = $courseId;
$newQueue->courseware_id = $coursewareId;
$newQueue->type = 'free';
$newQueue->src = $srcStand;
$newQueue->target = $srcFree;
if (!$newQueue->save()) {
self::$lastError = Utils::fetchErrorFirst($newQueue->errors);
return false;
}
return $newQueue;
}
}
<?php
use yii\db\Migration;
class m161026_085127_transcode_queue extends Migration
{
public $table = '{{%ranscode_queue}}';
public function up()
{
$this->createTable($this->table, [
'`id` varchar(50) NOT NULL',
'course_id' => $this->integer(10)->unsigned()->notNull(),
'courseware_id' => $this->integer(10)->unsigned()->notNull(),
'`type` enum(\'stand\',\'free\') NOT NULL',
'src' => $this->string(200)->notNull()->comment('源视频'),
'target' => $this->string(200)->notNull()->comment('视频'),
'`status` tinyint(1) NOT NULL DEFAULT \'0\' COMMENT \'状态:0转换中,1已完成\'',
'created_at' => $this->integer(10)->unsigned()->notNull(),
'updated_at' => $this->integer(10)->unsigned()->notNull(),
'PRIMARY KEY (`id`)'
]);
$this->addCommentOnTable($this->table, '转码队列表');
}
public function down()
{
$this->dropTable($this->table);
}
/*
// Use safeUp/safeDown to run migration code within a transaction
public function safeUp()
{
}
public function safeDown()
{
}
*/
}
......@@ -7,6 +7,11 @@
*/
namespace frontend\controllers;
use common\libs\QiniuTranscode;
use common\libs\Utils;
use common\models\Course;
use common\models\Courseware;
use common\models\TranscodeQueue;
use common\models\UploadPools;
use Yii;
use yii\web\Response;
......@@ -23,20 +28,7 @@ class QiniuCallbackController extends \yii\web\Controller
*/
public function actionRemUpload()
{
/* @var \Qiniu\Auth $auth */
$auth = Yii::$app->qiniu->getAuthInstance();
$response = Yii::$app->response;
$response->format = Response::FORMAT_JSON;
//验证回调请求
$requestBody = file_get_contents('php://input');
$authorization = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : '';
$currentUrl = YII_ENV == 'dev'
? 'http://t.pp.cc/tmp/qiniu_callback.php'
: Yii::$app->request->hostInfo.Yii::$app->request->getUrl();
if (!$auth->verifyCallback($_SERVER['CONTENT_TYPE'], $authorization, $currentUrl, $requestBody)) {
if (!$this->verifyCallback()) {
return ['error'=>'回调验证失败!'];
}
......@@ -67,6 +59,71 @@ class QiniuCallbackController extends \yii\web\Controller
}
}
/**
* 接收转码回调
*
* @return array
*/
public function actionTranscode()
{
$post = Yii::$app->request->getRawBody();
$data = json_decode($post, true);
if (!is_array($data)) {
return ['error'=>'回调数据格式错误!'];
}
if (!empty($data['error'])) {
Yii::error($data['error']);
return ['error'=>true];
}
if (!isset($data['id']) || !isset($data['items'])) {
return ['error'=>'数据格式错误'];
}
$queue = TranscodeQueue::findOne(['id'=>$data['id']]);
if (!$queue) {
return ['error'=>'该本地队列不存在!'];
}
$course = Course::findOne(['id'=>$queue->course_id]);
$courseware = Courseware::findOne(['id'=>$queue->courseware_id]);
$srcField = $queue->type == 'stand' ? 'src_stand' : 'src_free';
//$comeKey = $data['items'][0]['key'];
if ($courseware->$srcField && $courseware->$srcField != $queue['target']) {
UploadPools::deleteFile('video:'.$courseware->$srcField);
}
$courseware->$srcField = $queue['target'];
//确认完成
$queue->status = TranscodeQueue::STATUS_DONE;
$queue->save();
//如果还有免费视频则处理免费视频
if ($queue->type == 'stand' && $course->free_time > 0) {
$freeResult = TranscodeQueue::sendFreeTranscodeSignal($courseware->course_id, $courseware->id, $queue['target'], $course->free_time);
if (!$freeResult) {
return ['error'=>TranscodeQueue::$lastError];
}
} else {
$courseware->status = Courseware::STATUS_OK;
$course->status = Course::STATUS_OK;
}
if ($queue->status == TranscodeQueue::STATUS_DONE || $course->status == Course::STATUS_OK) {
$courseware->save();
$course->save();
}
return ['error'=>false];
}
/**
* 响应回调成功的信息
*
......@@ -93,4 +150,31 @@ class QiniuCallbackController extends \yii\web\Controller
return $return;
}
/**
* 检查七牛回调是否通过
*
* @return bool
*/
private function verifyCallback()
{
/* @var \Qiniu\Auth $auth */
$auth = Yii::$app->qiniu->getAuthInstance();
$response = Yii::$app->response;
$response->format = Response::FORMAT_JSON;
//验证回调请求
$requestBody = file_get_contents('php://input');
$authorization = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : '';
$currentUrl = YII_ENV == 'dev'
? 'http://t.pp.cc/tmp/qiniu_callback.php'
: Yii::$app->request->hostInfo.Yii::$app->request->getUrl();
if (!$auth->verifyCallback($_SERVER['CONTENT_TYPE'], $authorization, $currentUrl, $requestBody)) {
return false;
}
return true;
}
}
\ No newline at end of file
......@@ -424,8 +424,8 @@ a,a:hover,a:link {
width: 100%;
left: 0;
top: 0;
height: 180px;
line-height: 180px;
height: 150px;
line-height: 150px;
text-align: center;
font-size: 64px;
color: #fff;
......@@ -507,6 +507,7 @@ a,a:hover,a:link {
background-color: #fff;
border: 1px solid #f3f3f3;
border-top: none;
padding-left: 10px;
}
.video-teacher {
padding: 10px;
......@@ -538,7 +539,7 @@ a,a:hover,a:link {
.video-sm-info {
height: 80px;
line-height: 80px;
padding-right: 30px;
padding-right: 20px;
}
.video-sm-info a {
display: inline-block;
......@@ -559,26 +560,22 @@ a,a:hover,a:link {
line-height: 30px;
top: 0;
left: 0;
font-size: 30px;
font-size: 24px;
}
/*video detail*/
.warp-box {
background-color: #fff;
margin-top: 40px;
margin-top: 20px;
padding: 20px 0;
border-top: 1px solid #f3f3f3;
}
.video-description .title {
font-size: 16px;
color: #333;
padding-bottom: 10px;
}
.video-description .text {
color: #999;
}
.video-description .text img {
max-width: 100%;
height: auto;
border: none;
vertical-align: top;
}
......@@ -586,10 +583,14 @@ a,a:hover,a:link {
.video-comment {
margin-top: 30px;
}
.video-description .title,
.video-comment .title {
font-size: 16px;
color: #333;
padding-bottom: 10px;
padding-left: 10px;
border-left: 3px solid #3f7de4;
margin-bottom: 20px;
margin-top: 20px;
}
.video-comment .title small {
font-size: 12px;
......@@ -608,30 +609,49 @@ a,a:hover,a:link {
}
.comment-box {
position: relative;
padding-left: 60px;
margin-top: 20px;
padding-bottom: 40px;
margin-bottom: 40px;
}
.comment-head img {
width: 50px;
height: 50px;
width: 34px;
height: 34px;
border-radius: 100%;
}
.comment-head {
position: absolute;
left: 0;
top: 0;
padding-right: 10px;
padding: 10px;
}
.comment-head span {
display: inline-block;
height: 34px;
line-height: 34px;
}
.comment-input {
display: block;
border: 1px solid #e7e7e7;
}
.comment-input textarea {
height: 50px;
height: 80px;
border: none;
box-shadow: none;
border-radius: inherit;
width: 100%;
vertical-align: top;
outline: none;
}
.textarea-box {
padding: 10px;
}
.btn {
outline: none;
}
.comment-foot {
border-top:1px solid #f3f3f3;
}
.m-t-10 {
margin-top: 10px;
}
.m-r-10 {
margin-right: 10px;
}
.m-l-20 {
margin-left: 20px;
}
......@@ -643,16 +663,20 @@ a,a:hover,a:link {
width: 50px;
border-radius: 100%;
}
.user-i span,
.user-i span small {
.user-i span {
font-size: 14px;
color: #999;
}
.user-i span small {
font-size:12px;
color: #999;
}
.user-i span small {
padding-left: 5px;
}
.user-i p {
padding-top: 5px;
font-size: 14px;
}
.comment-main-list {
margin-bottom: 40px;
......
......@@ -13,21 +13,32 @@
<small>{{coursewareInfo.comment_count}}</small>
</div>
</div>
<div v-if="coursewareInfo.isGuest" class="comment-login">
<!--<div v-if="coursewareInfo.isGuest" class="comment-login">
需要 <a @click="login" href="javascript:;">登录</a> 才能评论
</div>
</div>-->
<div v-else class="comment-box">
<div class="comment-input">
<textarea v-model="content" cols="30" rows="10" class="form-control"
placeholder="说几句吧,亲..."></textarea>
<div class="textarea-box">
<textarea v-model="content" cols="30" rows="10" placeholder="说几句吧,亲..."></textarea>
</div>
<div v-if="coursewareInfo.isGuest" class="comment-foot clearfix">
<div class="comment-head pull-left" >
<span>需要 <a @click="login" href="javascript:;">登录</a> 才能评论</span>
</div>
<button @click="comment" class="btn btn-info pull-right m-t-10 m-r-10" disabled="disabled">发表评论</button>
</div>
<div v-else class="comment-foot clearfix">
<div class="comment-foot clearfix">
<div class="comment-head pull-left">
<div class="comment-head pull-left" >
<img :src="coursewareInfo.userAvatar">
</div>
<button @click="comment" class="btn btn-default pull-right m-t-10">发表评论</button>
<button @click="comment" class="btn btn-primary pull-right m-t-10 m-r-10">发表评论</button>
</div>
</div>
......
......@@ -30,7 +30,7 @@
</button>
<template v-else>
<button v-if="coursewareInfo.isShop" class="btn btn-danger m-l-20 hidden-xs mb-left"
data-toggle="modal">已购买
data-toggle="modal" disabled="disabled">已购买
</button>
<template v-else>
<button v-if="isMobile === false && isWeixin === false" class="btn btn-danger m-l-20 mb-left"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment