laravel admin可以使用laravel exec进行数据导出,但往往对于复杂的导入支持并不好,所以需要我们自己自定义导出,现在针对我遇到的一种场景给自己做一下记录也给大家提供一种思路,导入的数据是某个学生看完老师直播后,进行课堂答题,导出学生的答题记录,这里我分成了两张表,一个表存本次总答题的记录,一个表存详细的答题记录,分成两个表的目的是学生每次想看自己的答题记录时,我就不需要从详细log表里再汇总他答对多少道,错多少道,得分情况等。
总表:answer,详细表answer_log
方式1:这个方法相对比较简单,就是以详细log表为主表进行导出即可,这里我们不再默认继承 ExcelExporter,而是直接继承ExcelExporter的父类AbstractExporter ,并实现FromCollection接口
//laravel admin默认继承ExcelExporter
class AnswerLogExporter extends ExcelExporter
//这里我们改为实现FromCollection接口
class AnswerLogExporter extends AbstractExporter implements FromCollection, WithMapping, WithHeadings
导出的数据形式为:每个详细的答题记录都跟着本次答题的总结果,红框为一个总答题,绿框为一次总答题
具体实现详见以下代码:
user_id = request()->get('user_id');
$this->live_id = request()->get('live_id');
$this->start = data_get(request()->get('created_at'), 'start') ?? Carbon::create(2000);
$this->end = data_get(request()->get('created_at'), 'end') ?? Carbon::create(2100);
}
protected $fileName = '答题记录.xlsx';
public function headings(): array
{
return [
'答题id', '正确数', '错误数', '本次答题得分', '用户id', '用户姓名', '直播id', '直播标题',
'详细答题id', '问题id', '问题标题', '用户答案', '正确答案', '是否正确', '答题时间',];
}
//map()里没什么特殊的,跟简单格式化数据一样
public function map($row): array
{
return [
$row->answer_id,
$row->correct_num,
$row->incorrect_num,
$row->score,
$row->user_id,
$row->name,
$row->live_id,
$row->live_title,
$row->id,
$row->question_id,
$row->title,
$row->answer,
$row->correct_option,
$row->is_correct == 1 ? '是' : '否',
$row->created_at,
];
}
//主要在collection()方法,我们通过实现FromCollection接口,进行自定义需要导出的数据,
//不然会以当前调用该导出方法的modal为主表,这里如果不重写collection()方法,就会以answers表为主表
public function collection()
{
//自己写sql
$query = AnswerLog::query()->from('answer_log as al')
->select('al.id', 'al.answer_id', 'a.score', 'a.correct_num', 'a.incorrect_num',
'al.created_at', 'al.user_id', 'u.name', 'al.question_id', 'q.title',
'q.correct_option', 'al.live_id', 'll.live_title',
'al.subject_id', 'al.answer', 'al.is_correct')
->leftJoin('answer as a', 'a.id', 'al.answer_id')
->leftJoin('users as u', 'u.id', 'al.user_id')
->leftJoin('live_list as ll', 'll.live_id', 'al.live_id')
->leftJoin('questions as q', 'q.id', 'al.question_id')
->whereBetween('al.created_at', [$this->start, $this->end]);
//如果有筛选条件则添加where
if ($this->user_id) {
$query = $query->whereIn('al.user_id', $this->user_id);
}
if ($this->live_id) {
$query = $query->whereIn('al.live_id', $this->live_id);
}
return $query->get();
}
public function export()
{
$this->download($this->fileName)->prepare(request())->send();
exit;
}
}
方式2:相对复杂一些,这次我以总答题表answer表为主表进行导出,但这里我们依然不再继承 ExcelExporter,也不实现FromCollection接口,改为实现FromView接口
导出的数据形式为:依然是红框为一个总答题,绿框为一次总答题,但跟上次不一样的是,这次将前面相同的部分,A到I列的字段进行的单元框合并
具体实现详见代码:分两部分,1:导出类,2:数据渲染的页面(主要是使用table来做表格的格式化)
user_id = request()->get('user_id');
$this->live_id = request()->get('live_id');
$this->start = data_get(request()->get('created_at'), 'start') ?? Carbon::create(2000);
$this->end = data_get(request()->get('created_at'), 'end') ?? Carbon::create(2100);
}
/**
* @var string
*/
protected $fileName = '答题记录.xlsx';
//这次在query中写SQL,自定义需要导出的数据
public function query()
{
$this->query = DB::table('answer as a')
->select('a.id as a_id', 'a.user_id', 'u.name',
'a.live_id', 'll.live_title', 'a.score', 'a.correct_num', 'a.incorrect_num',
'al.id as al_id', 'al.question_id', 'q.title', 'q.correct_option',
'al.answer', 'al.is_correct', 'a.created_at'
)
->leftJoin('answer_log as al', 'a.id', 'al.answer_id')
->leftJoin('users as u', 'u.id', 'a.user_id')
->leftJoin('live_list as ll', 'll.live_id', 'a.live_id')
->leftJoin('questions as q', 'q.id', 'al.question_id')
->whereBetween('a.created_at', [$this->start, $this->end]);
//有查询条件时,加where
if ($this->user_id) {
$this->query->whereIn('a.user_id', $this->user_id);
}
if ($this->live_id) {
$this->query->whereIn('a.live_id', $this->live_id);
}
return $this->query->get();
}
/**
* {@inheritdoc}
*/
public function export()
{
$this->download($this->fileName)->prepare(request())->send();
exit;
}
//这里是将数据渲染到页面上
public function view(): View
{
//获取查询的数据
$data = $this->query();
//将数据进行分组,方便之后的数据渲染
$oAnswers = $data->groupBy('a_id');
//为了防止数据过多,无法导出,这里加了一个判断,可以根据服务器性能来决定是否加限制或者提高单次可以导出的数量
//这里虽然是限制了5000条,但这里的count的时总答题表的数,比如一个总答题有10个详细答题log,
//那么其实这里导出的数据量,在Excel中看到的其实就是50000行记录
if ($oAnswers->count() > 5000) {
return view('export.export_answer', ['over' => 1]);
}
//将数据传递到视图中
return view('export.export_answer', ['oAnswers' => $oAnswers, 'over' => 0]);
}
}
答题记录
{{--头部设置导出的字段名--}}
总答题id
答题人id
答题人
直播id
直播标题
正确数
错误数
得分
答题时间
详细答题id
问题id
问题标题
用户答案
正确答案
是否正确
{{--为了防止数据过多,无法导出,这里加了一个判断,可以根据服务器性能来决定是否加限制或者提高单次可以导出的数量--}}
@if($over)
单次最多只能导出5000条数据,请筛选后再导出
@else
{{--第一次循环,先将公共部分展示出来,这里使用rowspan属性进行单元格合并,--}}
{{--具体合并多少行,看有多少详细答题数来决定,公共部分就取每个集合的第一条即可--}}
@foreach($oAnswers as $oAnswer)
{{$oAnswer->first()->a_id}}
{{$oAnswer->first()->user_id}}
{{$oAnswer->first()->name}}
{{$oAnswer->first()->live_id}}
{{$oAnswer->first()->live_title}}
{{$oAnswer->first()->correct_num}}
{{$oAnswer->first()->incorrect_num}}
{{$oAnswer->first()->score}}
{{$oAnswer->first()->created_at}}
{{--第二次循环,将详细答题记录进行展示,这里就直接将每条数据遍历出来即可--}}
@foreach($oAnswer as $k=>$v)
{{$v->al_id}}
{{$v->question_id}}
{{$v->title}}
{{$v->answer}}
{{$v->correct_option}}
@if($v->is_correct) 是 @else 否 @endif
@endforeach
@endforeach
@endif
细心的朋友可能会发现这种方式导出的Excel中,每两个总答题之间会多一行空白行(如下面蓝框部分),这里可能是我页面html布局的问题,但目前还没有找到解决的办法,也欢迎大家给予指教!