初始化项目:添加后端代码、ThinkPHP框架、前端资源
This commit is contained in:
@@ -0,0 +1,713 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\data;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use think\Db;
|
||||
use PHPExcel_IOFactory;
|
||||
/**
|
||||
* 专栏数据
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Column extends Backend
|
||||
{
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\data\Study();
|
||||
$this->courseModel = new \app\admin\model\course\Course();
|
||||
$this->userModel = new \app\admin\model\User();
|
||||
$this->timeModel = new \app\admin\model\data\Time();
|
||||
$this->columnBindModel = new \app\admin\model\course\ColumnBind;
|
||||
$this->subscriptionModel = new \app\common\model\user\Subscription();
|
||||
}
|
||||
|
||||
/**
|
||||
* 课程概况
|
||||
* @return void
|
||||
*/
|
||||
public function total(){
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
$columnId = input('column_id', 0);
|
||||
|
||||
|
||||
// 1. 获取专栏下的所有课程ID
|
||||
$courseIds = $this->columnBindModel
|
||||
->with(['course'])
|
||||
->where(['course.status' => 1, 'column_id' => $columnId,'course_id'=>['<>',0]])
|
||||
->column('course.id');
|
||||
|
||||
// 2. 学员总数 (student_total)
|
||||
// 这里我们假设 $this->subscriptionModel 是用来获取订阅/报名该专栏的用户
|
||||
$studentTotal = $this->subscriptionModel->where(['course_id' => $columnId])->count();
|
||||
|
||||
// 3. 小节总数 (section_total)
|
||||
// 如果你的“小节总数”实际上是“课程数”,请使用下面这行:
|
||||
$totalCourseCount = count($courseIds);
|
||||
|
||||
if (empty($courseIds)) {
|
||||
return $this->success("获取成功", [
|
||||
'student_total' => $studentTotal,
|
||||
'section_total' => $totalCourseCount,
|
||||
'participate_count' => 0,
|
||||
'participate_rate' => 0.00,
|
||||
'finish_count' => 0,
|
||||
'finish_rate' => 0.00,
|
||||
]);
|
||||
}
|
||||
|
||||
// 构造基础查询条件
|
||||
$baseWhere = [
|
||||
's.uniacid' => UNIACID,
|
||||
's.course_id' => ['in', $courseIds],
|
||||
's.column_id'=>$columnId
|
||||
];
|
||||
|
||||
// 4. 参与人数 (participate_count)
|
||||
// 定义:至少学习了一个小节的去重用户数
|
||||
$participateCount = $this->model
|
||||
->setUniacid(false)
|
||||
->alias('s')
|
||||
->where($baseWhere)
|
||||
->count('DISTINCT s.user_id');
|
||||
|
||||
// 5. 完成人数 (finish_count)
|
||||
// 子查询:计算每个用户完成了多少门课
|
||||
// 这里的逻辑是:一个用户在一门课中只要有一个小节标记为 finish=1,就认为他完成了这门课
|
||||
$finishedCoursesSubquery = $this->model
|
||||
->setUniacid(false)
|
||||
->alias('s')
|
||||
->field('s.user_id, COUNT(DISTINCT s.course_id) as finished_course_count')
|
||||
->where($baseWhere)
|
||||
->where('s.finish', 1)
|
||||
->group('s.user_id')
|
||||
->buildSql();
|
||||
|
||||
// 主查询:筛选出完成课程数等于总课程数的用户
|
||||
$finishCount = $this->model
|
||||
->setUniacid(false)
|
||||
->table("($finishedCoursesSubquery) AS user_finish_stats")
|
||||
->where('user_finish_stats.finished_course_count', $totalCourseCount)
|
||||
->count();
|
||||
|
||||
// 6. 参与率 (participate_rate)
|
||||
$participateRate = $studentTotal > 0 && $participateCount
|
||||
? floatval(($participateCount / $studentTotal))
|
||||
: 0.00;
|
||||
|
||||
// 7. 完成率 (finish_rate)
|
||||
$finishRate = $studentTotal > 0 && $finishCount
|
||||
? floatval(($finishCount / $studentTotal))
|
||||
: 0.00;
|
||||
|
||||
// 组织最终结果
|
||||
$result = [
|
||||
'student_total' => $studentTotal,
|
||||
'section_total' => $totalCourseCount,
|
||||
'participate_count' => $participateCount,
|
||||
'participate_rate' => round($participateRate, 2),
|
||||
'finish_count' => $finishCount,
|
||||
'finish_rate' => round($finishRate, 2),
|
||||
];
|
||||
|
||||
return $this->success("获取成功", $result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 章节概括
|
||||
* @return void
|
||||
*/
|
||||
public function userdetail(){
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
|
||||
$columnId = input('column_id/d', 0);
|
||||
$userId = input('user_id/d', 0);
|
||||
|
||||
if (!$columnId || !$userId) {
|
||||
return $this->error("缺少必要参数");
|
||||
}
|
||||
|
||||
// 1. 获取专栏所有课程
|
||||
$courseIds = $this->columnBindModel
|
||||
->with(['course'])
|
||||
->where(['course.status' => 1, 'column_id' => $columnId,'course_id'=>['<>',0]])
|
||||
->column('course.id');
|
||||
|
||||
if(!$courseIds){
|
||||
return $this->success("获取成功", []);
|
||||
}
|
||||
|
||||
|
||||
$studyList = $this->courseModel
|
||||
->alias('course')
|
||||
->join($this->model->getTable()." study", "study.course_id = course.id AND study.user_id = {$userId} AND study.column_id = {$columnId} AND study.uniacid = " . UNIACID, 'LEFT')
|
||||
->where([
|
||||
'course.id' => ['in', $courseIds]
|
||||
])
|
||||
->field([
|
||||
'course.id as course_id',
|
||||
'course.name as course_name',
|
||||
'course.cover as course_cover',
|
||||
'course.type as course_type',
|
||||
'COUNT(study.id) AS record_count',
|
||||
'SUM(study.total_time) AS total_time_sec',
|
||||
'MAX(study.media_progress) AS media_progress_sec',
|
||||
'MIN(study.start_time) AS first_study_time',
|
||||
'MAX(study.end_time) AS last_study_time',
|
||||
'SUM(CASE WHEN study.finish = 1 THEN 1 ELSE 0 END) AS finish_count'
|
||||
])
|
||||
->group('course.id')
|
||||
->order('total_time_sec','desc')
|
||||
->select();
|
||||
|
||||
foreach ($studyList as $cid => $row) {
|
||||
|
||||
|
||||
$row['course'] = [
|
||||
'id' => $row['course_id'],
|
||||
'name' => $row['course_name'],
|
||||
'cover' => $row['course_cover'],
|
||||
'type' => $row['course_type'],
|
||||
];
|
||||
|
||||
// 参与状态
|
||||
if ($row['finish_count'] > 0) {
|
||||
$row['status'] = '已完成';
|
||||
} elseif ($row['record_count'] > 0) {
|
||||
$row['status'] = '进行中';
|
||||
}
|
||||
// 时长(秒→分钟)
|
||||
$row['total_minutes'] = intval($row['total_time_sec']);
|
||||
// 观看进度
|
||||
$row['progress_minutes'] = intval($row['media_progress_sec']);
|
||||
// 时间格式
|
||||
$row['first_study_time'] = $row['first_study_time'] ? date('Y-m-d H:i:s', $row['first_study_time']) : '-';
|
||||
$row['last_study_time'] = $row['last_study_time'] ? date('Y-m-d H:i:s', $row['last_study_time']) : '-';
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $this->success("获取成功", $result);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 章节概括
|
||||
* @return void
|
||||
*/
|
||||
public function course(){
|
||||
$this->request->filter(['strip_tags','trim']);
|
||||
|
||||
$columnId = input('column_id/d', 0);
|
||||
if (!$columnId) {
|
||||
return $this->error("缺少必要参数");
|
||||
}
|
||||
|
||||
// 1. 获取专栏内所有课程
|
||||
$courses = $this->courseModel
|
||||
->alias('c')
|
||||
->join($this->columnBindModel->getTable()." cb", "cb.course_id = c.id AND cb.column_id = {$columnId}", 'LEFT')
|
||||
->where(['c.status' => 1,'cb.course_id'=>['<>',0]])
|
||||
->field('c.id,c.name,c.cover,c.type')
|
||||
->group('cb.course_id')
|
||||
->select();
|
||||
|
||||
if (!$courses) {
|
||||
return $this->success("获取成功", ['total' => 0, 'rows' => []]);
|
||||
}
|
||||
|
||||
$courseIds = array_column($courses, 'id');
|
||||
|
||||
// 2. 统计每门课程参与人数和完成人数
|
||||
$studyStats = $this->model
|
||||
->alias('s')
|
||||
->where(['s.column_id' => $columnId, 's.uniacid' => UNIACID])
|
||||
->whereIn('s.course_id', $courseIds)
|
||||
->field([
|
||||
's.course_id',
|
||||
'COUNT(DISTINCT s.user_id) AS participate_user_count',
|
||||
'SUM(CASE WHEN s.finish = 1 THEN 1 ELSE 0 END) AS finish_user_count'
|
||||
])
|
||||
->group('s.course_id')
|
||||
->select();
|
||||
|
||||
|
||||
// 3. 建立课程ID映射
|
||||
$statsMap = [];
|
||||
foreach ($studyStats as $stat) {
|
||||
$statsMap[$stat['course_id']] = $stat;
|
||||
}
|
||||
|
||||
// 4. 计算专栏总用户数(用于参与率)
|
||||
$totalUserCount = $this->subscriptionModel
|
||||
->where(['course_id' => $columnId])
|
||||
->count();
|
||||
|
||||
// 5. 构造结果
|
||||
$result = [];
|
||||
foreach ($courses as $course) {
|
||||
$cid = $course['id'];
|
||||
$participate = isset($statsMap[$cid]) ? (int)$statsMap[$cid]['participate_user_count'] : 0;
|
||||
$finish = isset($statsMap[$cid]) ? (int)$statsMap[$cid]['finish_user_count'] : 0;
|
||||
|
||||
$result[] = [
|
||||
'course' => [
|
||||
'id' => $course['id'],
|
||||
'name' => $course['name'],
|
||||
'cover' => $course['cover'],
|
||||
'type' => $course['type'],
|
||||
],
|
||||
'participate_user_count' => $participate,
|
||||
'participate_rate' => $totalUserCount > 0 && $participate ? number_format($participate / $totalUserCount * 100, 2) . '%' : '0%',
|
||||
'finish_user_count' => $finish,
|
||||
'finish_rate' => $participate > 0 && $totalUserCount ? number_format($finish / $totalUserCount * 100, 2) . '%' : '0%',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->success("获取成功", ['total' => count($result), 'rows' => $result]);
|
||||
}
|
||||
|
||||
|
||||
protected $exportCourseIndex = 0;
|
||||
|
||||
public function courseDownload(){
|
||||
$this->request->filter(['strip_tags','trim']);
|
||||
|
||||
$columnId = input('column_id/d', 0);
|
||||
if (!$columnId) {
|
||||
return $this->error("缺少必要参数");
|
||||
}
|
||||
|
||||
// 1. 获取专栏内所有课程
|
||||
$courses = $this->courseModel
|
||||
->alias('c')
|
||||
->join($this->columnBindModel->getTable()." cb", "cb.course_id = c.id AND cb.column_id = {$columnId}", 'LEFT')
|
||||
->where(['c.status' => 1,'cb.course_id'=>['<>',0]])
|
||||
->field('c.id,c.name')
|
||||
->group('cb.course_id')
|
||||
->select();
|
||||
|
||||
if (!$courses) {
|
||||
return $this->error("未获取到相关课程");
|
||||
}
|
||||
|
||||
$courseIds = array_column($courses, 'id');
|
||||
|
||||
// 2. 统计每门课程参与人数和完成人数
|
||||
$studyStats = $this->model
|
||||
->alias('s')
|
||||
->where(['s.column_id' => $columnId, 's.uniacid' => UNIACID])
|
||||
->whereIn('s.course_id', $courseIds)
|
||||
->field([
|
||||
's.course_id',
|
||||
'COUNT(DISTINCT s.user_id) AS participate_user_count',
|
||||
'SUM(CASE WHEN s.finish = 1 THEN 1 ELSE 0 END) AS finish_user_count'
|
||||
])
|
||||
->group('s.course_id')
|
||||
->select();
|
||||
|
||||
|
||||
// 3. 建立课程ID映射
|
||||
$statsMap = [];
|
||||
foreach ($studyStats as $stat) {
|
||||
$statsMap[$stat['course_id']] = $stat;
|
||||
}
|
||||
|
||||
// 4. 计算专栏总用户数(用于参与率)
|
||||
$totalUserCount = $this->subscriptionModel
|
||||
->where(['course_id' => $columnId])
|
||||
->count();
|
||||
|
||||
$excelTitle = '小节概况【'.date('Y-m-d h:i',time()).'】';
|
||||
|
||||
$objPHPExcel = new \PHPExcel();
|
||||
//设置Excel属性
|
||||
$objPHPExcel->getProperties()
|
||||
->setTitle($excelTitle); //设置标题
|
||||
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('A1', '课程');//可以指定位置
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('B1', '参与人数');//可以指定位置
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('C1', '参与率');
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('D1', '完成人数');
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('E1', '完成率');
|
||||
|
||||
|
||||
foreach ($courses as $course) {
|
||||
$cid = $course['id'];
|
||||
$participate = isset($statsMap[$cid]) ? (int)$statsMap[$cid]['participate_user_count'] : 0;
|
||||
$finish = isset($statsMap[$cid]) ? (int)$statsMap[$cid]['finish_user_count'] : 0;
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('A'.(2 + $this->exportCourseIndex), $course['name']);
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('B'.(2 + $this->exportCourseIndex), $participate);
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('C'.(2 + $this->exportCourseIndex), ($totalUserCount > 0 && $participate? number_format($participate / $totalUserCount * 100, 2) . '%' : '0%'));
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('D'.(2 + $this->exportCourseIndex), $finish);
|
||||
$objPHPExcel->getActiveSheet()->setCellValue('E'.(2 + $this->exportCourseIndex), ($participate > 0 && $finish ? number_format($finish / $totalUserCount * 100, 2) . '%' : '0%'));
|
||||
$this->exportCourseIndex++;
|
||||
}
|
||||
|
||||
|
||||
//设置字体
|
||||
$objPHPExcel->getActiveSheet()->getStyle('A1:E1')->getFont()->setBold(true)->getColor()->setARGB(\PHPExcel_Style_Color::COLOR_BLACK);
|
||||
//设置水平居中
|
||||
$objPHPExcel->getActiveSheet()->getStyle('A1:E1')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
|
||||
//设置背景颜色
|
||||
$objPHPExcel->getActiveSheet()->getStyle('A1:E1')->getFill()->setFillType(\PHPExcel_Style_Fill::FILL_SOLID)->getStartColor()->setARGB('D9FFC125');
|
||||
$objPHPExcel->getActiveSheet()->getColumnDimension('A')->setWidth(20);
|
||||
$objPHPExcel->getActiveSheet()->getColumnDimension('B')->setWidth(15);
|
||||
$objPHPExcel->getActiveSheet()->getColumnDimension('C')->setWidth(15);
|
||||
$objPHPExcel->getActiveSheet()->getColumnDimension('D')->setWidth(18);
|
||||
$objPHPExcel->getActiveSheet()->getColumnDimension('E')->setWidth(18);
|
||||
//激活当前表
|
||||
$objPHPExcel->setActiveSheetIndex(0);
|
||||
ob_end_clean();//清除缓冲区,避免乱码
|
||||
|
||||
//弹出提示下载文件
|
||||
header('pragma:public');
|
||||
header("Content-Disposition:attachment;filename={$excelTitle}.xlsx");
|
||||
header('Cache-Control: max-age=0');
|
||||
$objWriter = PHPExcel_IOFactory:: createWriter($objPHPExcel, 'Excel2007');
|
||||
|
||||
$objWriter->save( 'php://output');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 学员详情(包含未参与学习的用户)
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
$columnId = input('column_id/d', 0);
|
||||
|
||||
if (!$columnId) {
|
||||
return $this->error("缺少必要参数");
|
||||
}
|
||||
|
||||
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
||||
|
||||
// 1. 复用工具方法:获取订阅学员列表(带分页)
|
||||
$subscriptionResult = $this->getSubscriptionUsers($columnId, $limit);
|
||||
$userList = $subscriptionResult['userList'];
|
||||
$pagination = $subscriptionResult['pagination'];
|
||||
|
||||
if (empty($userList)) {
|
||||
return $this->success("获取成功", [
|
||||
"total" => 0,
|
||||
"rows" => [],
|
||||
"total_section_count" => 0
|
||||
]);
|
||||
}
|
||||
|
||||
// 2. 复用工具方法:获取学员学习统计数据
|
||||
$statResult = $this->getUserStudyStats($columnId, $userList);
|
||||
$map = $statResult['map'];
|
||||
$hasStudyUserIds = $statResult['hasStudyUserIds'];
|
||||
$totalSectionCount = $statResult['totalSectionCount'];
|
||||
|
||||
// 3. 复用工具方法:组装学员详情数据
|
||||
$rows = $this->assembleUserDetailData($userList, $map, $hasStudyUserIds, $totalSectionCount);
|
||||
|
||||
// 4. 自定义排序(当前方法特有逻辑)
|
||||
usort($rows, function($a, $b) use ($sort, $order) {
|
||||
if ($a['sort_flag'] != $b['sort_flag']) {
|
||||
return $b['sort_flag'] - $a['sort_flag'];
|
||||
}
|
||||
if (empty($sort)) {
|
||||
return 0;
|
||||
}
|
||||
$fieldA = $a[$sort] ?? 0;
|
||||
$fieldB = $b[$sort] ?? 0;
|
||||
if (in_array($sort, ['first_study_time', 'last_study_time'])) {
|
||||
$fieldA = $fieldA != '-' ? strtotime($fieldA) : 0;
|
||||
$fieldB = $fieldB != '-' ? strtotime($fieldB) : 0;
|
||||
}
|
||||
return $order == 'desc' ? $fieldB - $fieldA : $fieldA - $fieldB;
|
||||
});
|
||||
|
||||
return $this->success("获取成功", [
|
||||
"total" => $pagination->total(),
|
||||
"rows" => $rows,
|
||||
"total_section_count" => $totalSectionCount
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 学员详情导出
|
||||
*/
|
||||
public function userDownload()
|
||||
{
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
$columnId = input('column_id/d', 0);
|
||||
|
||||
if (!$columnId) {
|
||||
return $this->error("缺少必要参数:column_id");
|
||||
}
|
||||
|
||||
// 1. 复用工具方法:获取订阅学员列表(无分页)
|
||||
$subscriptionResult = $this->getSubscriptionUsers($columnId, 0);
|
||||
$userList = $subscriptionResult['userList'];
|
||||
|
||||
if (empty($userList)) {
|
||||
return $this->error("无学员数据可导出");
|
||||
}
|
||||
|
||||
// 2. 复用工具方法:获取学员学习统计数据
|
||||
$statResult = $this->getUserStudyStats($columnId, $userList);
|
||||
$map = $statResult['map'];
|
||||
$totalSectionCount = $statResult['totalSectionCount'];
|
||||
|
||||
// 3. 组装导出数据(当前方法特有逻辑)
|
||||
$exportData = $this->assembleExportData($userList, $map, $totalSectionCount);
|
||||
|
||||
// 4. 排序(导出特有排序:participated_count降序)
|
||||
usort($exportData, function($a, $b) {
|
||||
return $b['participated_count'] - $a['participated_count'];
|
||||
});
|
||||
|
||||
// 5. 执行导出(Excel逻辑不变)
|
||||
$this->exportExcel($exportData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:获取专栏订阅学员列表
|
||||
* @param int $columnId 专栏ID
|
||||
* @param int $limit 分页条数(0表示无分页)
|
||||
* @return array ['userList' => 学员列表, 'pagination' => 分页对象(无分页时为null)]
|
||||
*/
|
||||
private function getSubscriptionUsers(int $columnId, int $limit = 0)
|
||||
{
|
||||
$result = [
|
||||
'userList' => [],
|
||||
'pagination' => null
|
||||
];
|
||||
|
||||
// 构建查询
|
||||
$query = $this->subscriptionModel
|
||||
->with(['user', 'mobileuser'])
|
||||
->distinct(true)
|
||||
->where(['course_id' => $columnId])
|
||||
->group('subscription.id');
|
||||
|
||||
// 分页/无分页处理
|
||||
if ($limit > 0) {
|
||||
$pagination = $query->paginate($limit);
|
||||
$result['pagination'] = $pagination;
|
||||
$list = $pagination->items();
|
||||
} else {
|
||||
$list = $query->select();
|
||||
}
|
||||
|
||||
// 整理学员数据(user/mobileuser统一格式)
|
||||
if ($list) {
|
||||
foreach ($list as &$row) {
|
||||
if ($row->user->id) {
|
||||
$row->getRelation('user')->visible(\app\admin\model\User::$listShowField);
|
||||
$row->user->user_id = $row->user->id;
|
||||
$result['userList'][] = $row->user;
|
||||
continue;
|
||||
}
|
||||
if ($row->mobileuser->id) {
|
||||
$row->getRelation('mobileuser')->visible(\app\admin\model\User::$listShowField);
|
||||
$row->mobileuser->user_id = $row->mobileuser->id;
|
||||
$result['userList'][] = $row->mobileuser;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:获取学员学习统计数据
|
||||
* @param int $columnId 专栏ID
|
||||
* @param array $userList 学员列表(含user_id)
|
||||
* @return array ['map' => 学习数据映射, 'hasStudyUserIds' => 有学习记录的用户ID, 'totalSectionCount' => 专栏总课程数]
|
||||
*/
|
||||
private function getUserStudyStats(int $columnId, array $userList)
|
||||
{
|
||||
$userIds = array_column($userList, "user_id");
|
||||
|
||||
// 统计学习数据
|
||||
$studyStats = $this->model
|
||||
->alias("s")
|
||||
->where([
|
||||
"s.column_id" => $columnId,
|
||||
"s.uniacid" => UNIACID
|
||||
])
|
||||
->whereIn("s.user_id", $userIds)
|
||||
->field([
|
||||
's.user_id',
|
||||
'COUNT(s.id) AS participated_count',
|
||||
'SUM(CASE WHEN s.finish = 1 THEN 1 ELSE 0 END) AS finished_count',
|
||||
'SUM(s.total_time) AS total_study_time_minutes',
|
||||
'MIN(s.start_time) AS first_study_time',
|
||||
'MAX(s.end_time) AS last_study_time',
|
||||
])
|
||||
->group("s.user_id")
|
||||
->select();
|
||||
|
||||
// 构建映射和有学习记录的用户ID列表
|
||||
$map = [];
|
||||
$hasStudyUserIds = [];
|
||||
foreach ($studyStats as $row) {
|
||||
$map[$row['user_id']] = $row;
|
||||
$hasStudyUserIds[] = $row['user_id'];
|
||||
}
|
||||
|
||||
// 获取专栏总课程数
|
||||
$courseIds = $this->columnBindModel
|
||||
->where(['status' => 1, 'column_id' => $columnId])
|
||||
->column('course_id');
|
||||
$totalSectionCount = count($courseIds);
|
||||
|
||||
return [
|
||||
'map' => $map,
|
||||
'hasStudyUserIds' => $hasStudyUserIds,
|
||||
'totalSectionCount' => $totalSectionCount
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:组装学员详情页数据(带排序标识)
|
||||
* @param array $userList 学员列表
|
||||
* @param array $map 学习数据映射
|
||||
* @param array $hasStudyUserIds 有学习记录的用户ID
|
||||
* @param int $totalSectionCount 专栏总课程数
|
||||
* @return array 组装后的详情数据
|
||||
*/
|
||||
private function assembleUserDetailData(array $userList, array $map, array $hasStudyUserIds, int $totalSectionCount)
|
||||
{
|
||||
$rows = [];
|
||||
foreach ($userList as $user) {
|
||||
$uid = $user["user_id"];
|
||||
$stat = $map[$uid] ?? [
|
||||
"participated_count" => 0,
|
||||
"finished_count" => 0,
|
||||
"first_study_time" => null,
|
||||
"last_study_time" => null,
|
||||
"total_study_time_minutes" => 0,
|
||||
];
|
||||
|
||||
$participated = intval($stat["participated_count"]);
|
||||
$finished = intval($stat["finished_count"]);
|
||||
|
||||
$rows[] = [
|
||||
"sort_flag" => in_array($uid, $hasStudyUserIds) ? 1 : 0,
|
||||
"user" => [
|
||||
"id" => $uid,
|
||||
"username" => $user["username"],
|
||||
"nickname" => $user["nickname"],
|
||||
"avatar" => $user["avatar"],
|
||||
],
|
||||
"participated_count" => $participated,
|
||||
"finished_count" => $finished,
|
||||
"participated_rate" => $totalSectionCount && $participated ? number_format($participated / $totalSectionCount * 100, 2) . "%" : "0%",
|
||||
"completion_rate" => $totalSectionCount && $finished ? number_format($finished / $totalSectionCount * 100, 2) . "%" : "0%",
|
||||
"study_status" => $participated == 0 ? "未参与" : ($finished >= $totalSectionCount ? "已完成" : "进行中"),
|
||||
"first_study_time" => $stat["first_study_time"] ? date("Y-m-d H:i:s", $stat["first_study_time"]) : "-",
|
||||
"last_study_time" => $stat["last_study_time"] ? date("Y-m-d H:i:s", $stat["last_study_time"]) : "-",
|
||||
"total_study_time_minutes" => intval($stat["total_study_time_minutes"]),
|
||||
];
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:组装导出数据
|
||||
* @param array $userList 学员列表
|
||||
* @param array $map 学习数据映射
|
||||
* @param int $totalSectionCount 专栏总课程数
|
||||
* @return array 导出格式数据
|
||||
*/
|
||||
private function assembleExportData(array $userList, array $map, int $totalSectionCount)
|
||||
{
|
||||
$exportData = [];
|
||||
foreach ($userList as $user) {
|
||||
$uid = $user["user_id"];
|
||||
$stat = $map[$uid] ?? [
|
||||
"participated_count" => 0,
|
||||
"finished_count" => 0,
|
||||
"first_study_time" => null,
|
||||
"last_study_time" => null,
|
||||
"total_study_time_minutes" => 0,
|
||||
];
|
||||
|
||||
$participated = intval($stat["participated_count"]);
|
||||
$finished = intval($stat["finished_count"]);
|
||||
|
||||
$exportData[] = [
|
||||
'username' => $user["username"] ?? "未知用户",
|
||||
'study_status' => $participated == 0 ? "未参与" : ($finished >= $totalSectionCount ? "已完成" : "进行中"),
|
||||
'participated_count' => $participated,
|
||||
'participated_rate' => $totalSectionCount && $participated ? number_format($participated / $totalSectionCount * 100, 2) . "%" : "0%",
|
||||
'finished_count' => $finished,
|
||||
'completion_rate' => $totalSectionCount && $finished ? number_format($finished / $totalSectionCount * 100, 2) . "%" : "0%",
|
||||
'first_study_time' => $stat["first_study_time"] ? date("Y-m-d H:i:s", $stat["first_study_time"]) : "-",
|
||||
'last_study_time' => $stat["last_study_time"] ? date("Y-m-d H:i:s", $stat["last_study_time"]) : "-",
|
||||
'total_study_time' => number_format((float)$stat["total_study_time_minutes"] / 60, 1) . " 分钟",
|
||||
];
|
||||
}
|
||||
return $exportData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:Excel导出执行
|
||||
* @param array $exportData 导出数据
|
||||
*/
|
||||
private function exportExcel(array $exportData)
|
||||
{
|
||||
$excelTitle = '学员详情【' . date('Y-m-d H:i', time()) . '】';
|
||||
$objPHPExcel = new \PHPExcel();
|
||||
|
||||
// 设置Excel属性和表头
|
||||
$objPHPExcel->getProperties()->setTitle($excelTitle);
|
||||
$headers = [
|
||||
'A1' => '学员用户名', 'B1' => '课程参与状态', 'C1' => '小节参与数',
|
||||
'D1' => '小节参与率', 'E1' => '小节完成数', 'F1' => '小节完成率',
|
||||
'G1' => '首次学习时间', 'H1' => '最后学习时间', 'I1' => '学习时长(分钟)'
|
||||
];
|
||||
foreach ($headers as $cell => $title) {
|
||||
$objPHPExcel->getActiveSheet()->setCellValue($cell, $title);
|
||||
}
|
||||
|
||||
// 填充数据
|
||||
$startRow = 2;
|
||||
foreach ($exportData as $data) {
|
||||
$objPHPExcel->getActiveSheet()
|
||||
->setCellValue('A' . $startRow, $data['username'])
|
||||
->setCellValue('B' . $startRow, $data['study_status'])
|
||||
->setCellValue('C' . $startRow, $data['participated_count'])
|
||||
->setCellValue('D' . $startRow, $data['participated_rate'])
|
||||
->setCellValue('E' . $startRow, $data['finished_count'])
|
||||
->setCellValue('F' . $startRow, $data['completion_rate'])
|
||||
->setCellValue('G' . $startRow, $data['first_study_time'])
|
||||
->setCellValue('H' . $startRow, $data['last_study_time'])
|
||||
->setCellValue('I' . $startRow, $data['total_study_time']);
|
||||
$startRow++;
|
||||
}
|
||||
|
||||
// 设置样式
|
||||
$objPHPExcel->getActiveSheet()->getStyle('A1:I1')->getFont()->setBold(true)->getColor()->setARGB(\PHPExcel_Style_Color::COLOR_BLACK);
|
||||
$objPHPExcel->getActiveSheet()->getStyle('A1:I1')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
|
||||
$objPHPExcel->getActiveSheet()->getStyle('A1:I1')->getFill()->setFillType(\PHPExcel_Style_Fill::FILL_SOLID)->getStartColor()->setARGB('D9FFC125');
|
||||
$columnWidths = ['A' => 20, 'B' => 15, 'C' => 15, 'D' => 18, 'E' => 18, 'F' => 18, 'G' => 20, 'H' => 20, 'I' => 16];
|
||||
foreach ($columnWidths as $column => $width) {
|
||||
$objPHPExcel->getActiveSheet()->getColumnDimension($column)->setWidth($width);
|
||||
}
|
||||
|
||||
// 导出响应
|
||||
$objPHPExcel->setActiveSheetIndex(0);
|
||||
ob_end_clean();
|
||||
header('pragma:public');
|
||||
header("Content-Disposition:attachment;filename={$excelTitle}.xlsx");
|
||||
header('Cache-Control: max-age=0');
|
||||
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
|
||||
$objWriter->save('php://output');
|
||||
}
|
||||
|
||||
// 原有的导出索引属性(当前已无需使用,可删除)
|
||||
// protected $exportUserIndex = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user