|
|
<?php
|
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
|
|
|
|
use EasyWeChat\Factory;
|
|
|
use EasyWeChat\Kernel\Http\StreamResponse;
|
|
|
use Illuminate\Filesystem\Filesystem;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
|
class Course extends SoftDeletesModel
|
|
|
{
|
|
|
protected $appends = [
|
|
|
'date_status',
|
|
|
'publicize',
|
|
|
'sign_date_status',
|
|
|
// qrcode 不自动 append:列表/关联序列化时会调微信,易导致接口 500
|
|
|
'teacher_detail',
|
|
|
'status_text',
|
|
|
'is_fee_text',
|
|
|
'is_arrange_text',
|
|
|
'show_txl_text',
|
|
|
'show_mobile_text',
|
|
|
'auto_schoolmate_text',
|
|
|
'free_enter_schoolmate_text',
|
|
|
'is_virtual_text'
|
|
|
];
|
|
|
|
|
|
protected $casts = ['publicize_ids' => 'json'];
|
|
|
|
|
|
public function getStatusTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '待发布', 1 => '已发布'];
|
|
|
return $array[$this->attributes['status'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
public function getIsFeeTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '免费', 1 => '收费'];
|
|
|
return $array[$this->attributes['is_fee'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
public function getIsArrangeTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '否', 1 => '是'];
|
|
|
return $array[$this->attributes['is_arrange'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
public function getShowTxlTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '否', 1 => '是'];
|
|
|
return $array[$this->attributes['show_txl'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
public function getShowMobileTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '否', 1 => '是'];
|
|
|
return $array[$this->attributes['show_mobile'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
public function getAutoSchoolmateTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '否', 1 => '是'];
|
|
|
return $array[$this->attributes['auto_schoolmate'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
public function getFreeEnterSchoolmateTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '否', 1 => '是'];
|
|
|
return $array[$this->attributes['free_enter_schoolmate'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
public function getIsVirtualTextAttribute()
|
|
|
{
|
|
|
$array = [0 => '否', 1 => '是'];
|
|
|
return $array[$this->attributes['is_virtual'] ?? 0] ?? '';
|
|
|
}
|
|
|
|
|
|
|
|
|
public function getQrcodeAttribute($value)
|
|
|
{
|
|
|
try {
|
|
|
return $this->getCourseQrcode($this->attributes['id']);
|
|
|
} catch (\Throwable $e) {
|
|
|
Log::warning('course qrcode append failed', [
|
|
|
'course_id' => $this->attributes['id'] ?? null,
|
|
|
'message' => $e->getMessage(),
|
|
|
]);
|
|
|
return '';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public function getTeacherDetailAttribute($value)
|
|
|
{
|
|
|
if (empty($this->teacher_id)) {
|
|
|
return collect();
|
|
|
}
|
|
|
$teacherIds = explode(',', $this->teacher_id);
|
|
|
return Teacher::whereIn('id', $teacherIds)->get();
|
|
|
}
|
|
|
|
|
|
public function getPublicizeAttribute($value)
|
|
|
{
|
|
|
if (empty($this->publicize_ids))
|
|
|
return [];
|
|
|
return Upload::whereIn('id', $this->publicize_ids)->get();
|
|
|
}
|
|
|
|
|
|
public function getDateStatusAttribute()
|
|
|
{
|
|
|
$text = '课程未开始';
|
|
|
if ($this->course_status == 10) {
|
|
|
$text = '课程进行中';
|
|
|
}
|
|
|
if ($this->course_status == 40) {
|
|
|
$text = '课程已结束';
|
|
|
}
|
|
|
return $text;
|
|
|
}
|
|
|
|
|
|
public function getSignDateStatusAttribute()
|
|
|
{
|
|
|
$text = '未开始';
|
|
|
if ($this->sign_status == 10) {
|
|
|
$text = '进行中';
|
|
|
}
|
|
|
if ($this->sign_status == 40) {
|
|
|
$text = '已结束';
|
|
|
}
|
|
|
return $text;
|
|
|
}
|
|
|
|
|
|
public function typeDetail()
|
|
|
{
|
|
|
return $this->hasOne(CourseType::class, 'id', 'type');
|
|
|
}
|
|
|
|
|
|
public function courseForms()
|
|
|
{
|
|
|
return $this->hasMany(CourseForm::class, 'course_id', 'id');
|
|
|
}
|
|
|
|
|
|
public function teacher()
|
|
|
{
|
|
|
return $this->hasOne(Teacher::class, 'id', 'teacher_id');
|
|
|
}
|
|
|
|
|
|
public function courseSettings()
|
|
|
{
|
|
|
return $this->hasMany(CourseSetting::class, 'course_id', 'id');
|
|
|
}
|
|
|
|
|
|
public function coursePeriods()
|
|
|
{
|
|
|
return $this->hasMany(CoursePeriod::class, 'course_id', 'id');
|
|
|
}
|
|
|
|
|
|
public function courseSigns()
|
|
|
{
|
|
|
return $this->hasMany(CourseSign::class, 'course_id', 'id');
|
|
|
}
|
|
|
|
|
|
public function image()
|
|
|
{
|
|
|
return $this->hasOne(Upload::class, 'id', 'image_id');
|
|
|
}
|
|
|
|
|
|
public function qunImage()
|
|
|
{
|
|
|
return $this->hasOne(Upload::class, 'id', 'qun_image_id');
|
|
|
}
|
|
|
|
|
|
public function courseContents()
|
|
|
{
|
|
|
return $this->hasMany(CourseContent::class, 'course_id', 'id');
|
|
|
}
|
|
|
|
|
|
public function courseContentEvaluation()
|
|
|
{
|
|
|
return $this->hasMany(CourseContentEvaluation::class, 'course_id', 'id');
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 更新课程报名状态
|
|
|
*/
|
|
|
public static function updateSignStatus($courseId)
|
|
|
{
|
|
|
// 进行中-未开始-已结束
|
|
|
$now = date('Y-m-d');
|
|
|
$course = Course::find($courseId);
|
|
|
// 默认未开始
|
|
|
$sign_status = 30;
|
|
|
if ($course->sign_start_date && $now < $course->sign_start_date) {
|
|
|
// 未开始
|
|
|
$sign_status = 20;
|
|
|
}
|
|
|
if ($course->sign_start_date && $now >= $course->sign_start_date) {
|
|
|
// 进行中
|
|
|
$sign_status = 10;
|
|
|
}
|
|
|
if ($course->sign_end_date && $now > $course->sign_end_date) {
|
|
|
// 已结束
|
|
|
$sign_status = 40;
|
|
|
}
|
|
|
$course->sign_status = $sign_status;
|
|
|
$course->save();
|
|
|
return $course;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 更新课程状态
|
|
|
*/
|
|
|
public static function updateStatus($courseId)
|
|
|
{
|
|
|
// 进行中-未开始-已结束
|
|
|
$now = date('Y-m-d');
|
|
|
$course = Course::find($courseId);
|
|
|
// 默认待定
|
|
|
$course_status = 30;
|
|
|
if ($course->start_date && $now < $course->start_date) {
|
|
|
// 未开始
|
|
|
$course_status = 20;
|
|
|
}
|
|
|
if ($course->start_date && $now >= $course->start_date) {
|
|
|
// 进行中
|
|
|
$course_status = 10;
|
|
|
}
|
|
|
if ($course->end_date && $now > $course->end_date) {
|
|
|
// 已结束
|
|
|
$course_status = 40;
|
|
|
}
|
|
|
$course->course_status = $course_status;
|
|
|
$course->save();
|
|
|
return $course;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取课程详情小程序码
|
|
|
* @param mixed $courseId
|
|
|
* @param bool $generate 是否在文件不存在时尝试生成
|
|
|
*/
|
|
|
public function getCourseQrcode($courseId, $generate = true)
|
|
|
{
|
|
|
$course = Course::find($courseId);
|
|
|
if (empty($course)) {
|
|
|
return '';
|
|
|
}
|
|
|
return $this->resolveMiniProgramQrcode(
|
|
|
'course_qrcode/' . $course->id . '.png',
|
|
|
'packages/course/detail?id=' . $courseId,
|
|
|
['course_id' => $courseId],
|
|
|
$generate
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取课程报名小程序码
|
|
|
*/
|
|
|
public function getCourseCheckQrcode($courseId, $generate = true)
|
|
|
{
|
|
|
$course = Course::find($courseId);
|
|
|
if (empty($course)) {
|
|
|
return '';
|
|
|
}
|
|
|
return $this->resolveMiniProgramQrcode(
|
|
|
'course_check_qrcode/' . $course->id . '.png',
|
|
|
'packages/sign/course?course_id=' . $courseId,
|
|
|
['course_id' => $courseId],
|
|
|
$generate
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取问卷小程序码
|
|
|
*/
|
|
|
public function getEvaluationQrcode($id, $generate = true)
|
|
|
{
|
|
|
$courseContentEvaluation = CourseContentEvaluation::find($id);
|
|
|
if (empty($courseContentEvaluation)) {
|
|
|
return '';
|
|
|
}
|
|
|
return $this->resolveMiniProgramQrcode(
|
|
|
'course_evaluation_qrcode/' . $courseContentEvaluation->id . '.png',
|
|
|
'packages/surveyFill/index?id=' . $id,
|
|
|
['evaluation_id' => $id],
|
|
|
$generate
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 读取或生成小程序码;微信/缓存失败时返回空字符串,避免列表接口 500
|
|
|
*/
|
|
|
protected function resolveMiniProgramQrcode(string $relativePath, string $page, array $context = [], bool $generate = true): string
|
|
|
{
|
|
|
$path = config('filesystems.disks.public.root') . '/' . $relativePath;
|
|
|
$url = config('filesystems.disks.public.url') . '/' . $relativePath;
|
|
|
$fileSys = new Filesystem();
|
|
|
if ($fileSys->exists($path)) {
|
|
|
return $url;
|
|
|
}
|
|
|
|
|
|
if (!$generate) {
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
$appId = (string) config('app.applet_appid');
|
|
|
$secret = (string) config('app.applet_secret');
|
|
|
if ($appId === '' || $secret === '') {
|
|
|
Log::warning('mini program qrcode skipped: missing appid/secret', $context);
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
$app = Factory::miniProgram(miniProgramConfig([
|
|
|
'app_id' => $appId,
|
|
|
'secret' => $secret,
|
|
|
]));
|
|
|
$tmp = $app->app_code->get($page, [
|
|
|
'env_version' => 'release',
|
|
|
]);
|
|
|
$content = $tmp instanceof StreamResponse ? $tmp->getBodyContents() : (string) $tmp;
|
|
|
if ($content === '' || str_starts_with(ltrim($content), '{')) {
|
|
|
Log::warning('mini program qrcode invalid response', $context + [
|
|
|
'body' => substr($content, 0, 300),
|
|
|
]);
|
|
|
return '';
|
|
|
}
|
|
|
$fileSys->ensureDirectoryExists(dirname($path), 0755, true);
|
|
|
$fileSys->put($path, $content);
|
|
|
return $url;
|
|
|
} catch (\Throwable $e) {
|
|
|
Log::warning('mini program qrcode failed', $context + [
|
|
|
'message' => $e->getMessage(),
|
|
|
]);
|
|
|
return '';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取课程统计项元数据
|
|
|
* 返回每个统计项的计算逻辑和验证方法说明
|
|
|
* 从 statistics_metadata 表中读取
|
|
|
* @return array
|
|
|
*/
|
|
|
public static function getStatisticsMetadata()
|
|
|
{
|
|
|
return StatisticsMetadata::getAllAsArray();
|
|
|
}
|
|
|
|
|
|
}
|