在 Yii 2.0 中导出资源列表为 Excel 文件的实现流程
1、我的任务的资源列表,计划在资源列表头部添加一个导出按钮,如图1
2、在 GitHub 中搜索:yii2 export excel,其结果:moonlandsoft/yii2-phpexcel、kartik-v/yii2-export,如图2
3、经过对比分析,主要从支持的导出格式的丰富性、后续可能需要支持导入的扩展性上考虑,最终决定选择:moonlandsoft/yii2-phpexcel
moonlandsoft/yii2-phpexcel的导出格式:Xls、Xlsx、Xml、Ods、Slk、Gnumeric、Csv、Html
kartik-v/yii2-export的导出格式:Xls、Xlsx、Csv、Html、Txt、Pdf
4、基于 Composer 安装此扩展,执行如下命令:,注:由于网络问题,总计执行了 3 次,第 3 次为翻墙之后再执行,如图3
PS E:\wwwroot\pcs-api-feature-task-statistics-print> composer require --prefer-dist moonlandsoft/yii2-phpexcel "*" Content-Length mismatch, received 16128 bytes out of the expected 3759703 https://asset-packagist.org could not be fully loaded, package information was loaded from the local cache and may be ou t of date ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) https://asset-packagist.org could not be fully loaded, package information was loaded from the local cache and may be ou t of date Package operations: 5 installs, 0 updates, 0 removals - Installing markbaker/matrix (1.1.4): Downloading (100%) - Installing markbaker/complex (1.4.7): Downloading (100%) - Installing psr/simple-cache (1.0.1): Downloading (100%) - Installing phpoffice/phpspreadsheet (1.9.0): Downloading (100%) PS E:\wwwroot\pcs-api-feature-task-statistics-print> composer require --prefer-dist moonlandsoft/yii2-phpexcel "*" The "https://asset-packagist.org/p/provider-latest/49c795e8ff9b455adc45e73e45b8fa84fd39ce4761894526a7d05b455390a960.json " file could not be downloaded: failed to open stream: HTTP request failed! https://asset-packagist.org could not be fully loaded, package information was loaded from the local cache and may be ou t of date ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Content-Length mismatch, received 16128 bytes out of the expected 3759943 https://asset-packagist.org could not be fully loaded, package information was loaded from the local cache and may be ou t of date Package operations: 2 installs, 0 updates, 0 removals - Installing phpoffice/phpspreadsheet (1.9.0): PS E:\wwwroot\pcs-api-feature-task-statistics-print> composer require --prefer-dist moonlandsoft/yii2-phpexcel "*" Content-Length mismatch, received 16128 bytes out of the expected 3759943 https://asset-packagist.org could not be fully loaded, package information was loaded from the local cache and may be ou t of date ./composer.json has been updated Loading composer repositories with package information https://asset-packagist.org could not be fully loaded, package information was loaded from the local cache and may be ou t of date Updating dependencies (including require-dev) Package operations: 2 installs, 0 updates, 0 removals - Installing phpoffice/phpspreadsheet (1.9.0): Loading from cache - Installing moonlandsoft/yii2-phpexcel (2.0.0): Downloading (100%) phpoffice/phpspreadsheet suggests installing mpdf/mpdf (Option for rendering PDF with PDF Writer) phpoffice/phpspreadsheet suggests installing dompdf/dompdf (Option for rendering PDF with PDF Writer) phpoffice/phpspreadsheet suggests installing tecnickcom/tcpdf (Option for rendering PDF with PDF Writer) phpoffice/phpspreadsheet suggests installing jpgraph/jpgraph (Option for rendering charts, or including charts with PDF or HTML Writers) Writing lock file Generating autoload files
5、用于导出数据的新功能,如果您熟悉 yii gridview,则可以使用此功能。这与 gridview 数据列相同。数组模式中的列有效参数是 ‘attribute’,’header’,’format’,’value’和footer(TODO)。字符串模式有效布局中的列是’attribute:format:header:footer(TODO)’。如图4
6、复制 \api\rests\plan_task\IndexAction.php 至 \api\rests\plan_task\ExportAction.php,由于导出任务为全部记录,因此设置每页资源数量为资源总数
// 设置每页资源数量为资源总数 $count = $query->count(); $requestParams['per-page'] = $count;
7、编辑 \api\rests\plan_task\Serializer.php,判断操作ID,如果为 export,则导出,新增请求参数,file_type:文件类型
use moonland\phpexcel\Excel; use yii\helpers\ArrayHelper; /** * Serializes a data provider. * @param DataProviderInterface $dataProvider * @return array|null the array representation of the data provider. * @throws UnprocessableEntityHttpException */ protected function serializeDataProvider($dataProvider) { // 导出文件 $actionId = Yii::$app->controller->action->id; if ($actionId == 'export') { $fileType = isset($requestParams['file_type']) ? $requestParams['file_type'] : 'xlsx'; $allowedFileTypes = ['xlsx']; // 判断文件类型范围 if (!in_array($fileType, $allowedFileTypes)) { throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '226823'), ['file_types' => implode(",", $allowedFileTypes)])), 226823); } // 导出文件的名称 $fileName = Yii::t('application', '326001') . '-' . date("Y-m-d-H-i-s"); // 导出文件的格式,将字符串的首字母转换为大写 $format = ucfirst($fileType); Excel::export([ 'models' => $result['items'], 'asAttachment' => true, 'fileName' => $fileName, 'format' => $format, 'columns' => [ [ 'attribute' => 'id', 'header' => Yii::t('model/plan-task-export-param', 'ID'), ], [ 'attribute' => 'is_deferred', 'header' => Yii::t('model/plan-task-export-param', 'Is Deferred'), 'value' => function($model) { return $model['is_deferred'] == PlanTask::IS_DEFERRED_NO ? Yii::t('model/plan-task-export-param', 'No') : Yii::t('model/plan-task-export-param', 'Yes'); }, ], [ 'attribute' => 'title', 'header' => Yii::t('model/plan-task-export-param', 'Title'), ], [ 'attribute' => 'task_info', 'header' => Yii::t('model/plan-task-export-param', 'Task Info'), ], [ 'attribute' => 'config_task_name', 'header' => Yii::t('model/plan-task-export-param', 'Config Task Name'), ], [ 'attribute' => 'config_column_name', 'header' => Yii::t('model/plan-task-export-param', 'Config Column Name'), ], [ 'attribute' => 'create_name', 'header' => Yii::t('model/plan-task-export-param', 'Create Name'), ], [ 'attribute' => 'exec_name', 'header' => Yii::t('model/plan-task-export-param', 'Exec Name'), ], [ 'attribute' => 'plan_task_attended_users', 'header' => Yii::t('model/plan-task-export-param', 'Attended Names'), 'value' => function($model) { $planTaskAttendedUserNames = ArrayHelper::getColumn($model['plan_task_attended_users'], 'relation_user_name'); return implode(",", $planTaskAttendedUserNames); }, ], [ 'attribute' => 'sort_order', 'header' => Yii::t('model/plan-task-export-param', 'Sort Order'), ], [ 'attribute' => 'place', 'header' => Yii::t('model/plan-task-export-param', 'Place'), ], [ 'attribute' => 'occur_at', 'header' => Yii::t('model/plan-task-export-param', 'Occur At'), 'format' => ['date', 'php:Y-m-d H:i:s'], ], [ 'attribute' => 'ended_at', 'header' => Yii::t('model/plan-task-export-param', 'Ended At'), 'format' => ['date', 'php:Y-m-d H:i:s'], ], [ 'attribute' => 'task_location_status', 'header' => Yii::t('model/plan-task-export-param', 'Task Location Status'), 'value' => function($model) { return $model['task_location_status'] == PlanTask::PLAN_TASK_LOCATION_STATUS_OFF ? Yii::t('model/plan-task-export-param', 'Off') : Yii::t('model/plan-task-export-param', 'On'); }, ], [ 'attribute' => 'is_united', 'header' => Yii::t('model/plan-task-export-param', 'Is United'), 'value' => function($model) { return $model['task_location_status'] == PlanTask::IS_DELETED_NO ? Yii::t('model/plan-task-export-param', 'No') : Yii::t('model/plan-task-export-param', 'Yes'); }, ], [ 'attribute' => 'prev_status', 'header' => Yii::t('model/plan-task-export-param', 'Prev Status'), 'value' => function($model) { if ($model['prev_status'] == PlanTask::STATUS_NOT_STARTED) { $prevStatusValue = Yii::t('model/plan-task-export-param', 'Not Started'); } elseif ($model['prev_status'] == PlanTask::STATUS_STARTED) { $prevStatusValue = Yii::t('model/plan-task-export-param', 'Started'); } elseif ($model['prev_status'] == PlanTask::STATUS_COMPLETED) { $prevStatusValue = Yii::t('model/plan-task-export-param', 'Completed'); } else { $prevStatusValue = Yii::t('model/plan-task-export-param', 'Disabled'); } return $prevStatusValue; }, ], [ 'attribute' => 'status', 'header' => Yii::t('model/plan-task-export-param', 'Status'), 'value' => function($model) { if ($model['status'] == PlanTask::STATUS_NOT_STARTED) { $statusValue = Yii::t('model/plan-task-export-param', 'Not Started'); } elseif ($model['status'] == PlanTask::STATUS_STARTED) { $statusValue = Yii::t('model/plan-task-export-param', 'Started'); } elseif ($model['status'] == PlanTask::STATUS_COMPLETED) { $statusValue = Yii::t('model/plan-task-export-param', 'Completed'); } else { $statusValue = Yii::t('model/plan-task-export-param', 'Disabled'); } return $statusValue; }, ], [ 'attribute' => 'plan_task_steps', 'header' => Yii::t('model/plan-task-export-param', 'Task Steps'), 'value' => function($model) { $planTaskStepNames = ArrayHelper::getColumn($model['plan_task_steps'], 'step_name'); return implode(",", $planTaskStepNames); }, ], [ 'attribute' => 'is_not_isolated', 'header' => Yii::t('model/plan-task-export-param', 'Is Not Isolated'), 'value' => function($model) { return $model['is_not_isolated'] == PlanTask::IS_NOT_ISOLATED_NO ? Yii::t('model/plan-task-export-param', 'No') : Yii::t('model/plan-task-export-param', 'Yes'); }, ], [ 'attribute' => 'created_at', 'header' => Yii::t('model/plan-task-export-param', 'Created At'), 'format' => ['date', 'php:Y-m-d H:i:s'], ], [ 'attribute' => 'updated_at', 'header' => Yii::t('model/plan-task-export-param', 'Updated At'), 'format' => ['date', 'php:Y-m-d H:i:s'], ], [ 'attribute' => 'deleted_at', 'header' => Yii::t('model/plan-task-export-param', 'Deleted At'), 'format' => ['date', 'php:Y-m-d H:i:s'], ], ], ]); } if ($pagination !== false) { return ['code' => 10000, 'message' => Yii::t('success', '126007'), 'data' => array_merge($result, $this->serializePagination($pagination))]; } return ['code' => 10000, 'message' => Yii::t('success', '126007'), 'data' => $result]; }
8、新增模型的语言包文件
\common\messages\en-US\model\plan-task-export-param.php
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2019/09/20 * Time: 17:31 */ return [ 'ID' => 'ID', 'Is Deferred' => 'Is Deferred', 'Title' => 'Title', 'Task Info' => 'Task Info', 'Config Task Name' => 'Config Task Name', 'Config Column Name' => 'Config Column Name', 'Create Name' => 'Create Name', 'Exec Name' => 'Exec Name', 'Attended Names' => 'Attended Names', 'Sort Order' => 'Sort Order', 'Place' => 'Place', 'Occur At' => 'Occur At', 'Ended At' => 'Ended At', 'Task Location Status' => 'Task Location Status', 'Off' => 'Off', 'On' => 'On', 'Is United' => 'Is United', 'Prev Status' => 'Prev Status', 'Status' => 'Status', 'Disabled' => 'Disabled', 'Not Started' => 'Not Started', 'Started' => 'Started', 'Completed' => 'Completed', 'Task Steps' => 'Task Steps', 'Is Not Isolated' => 'Is Not Isolated', 'No' => 'No', 'Yes' => 'Yes', 'Created At' => 'Created At', 'Updated At' => 'Updated At', 'Deleted At' => 'Deleted At', ];
\common\messages\zh-CN\model\plan-task-export-param.php
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2019/09/20 * Time: 15:47 */ return [ 'ID' => '编号', 'Is Deferred' => '是否已延期', 'Title' => '任务名称', 'Task Info' => '任务内容', 'Config Task Name' => '任务类型', 'Config Column Name' => '任务栏目', 'Create Name' => '创建人', 'Exec Name' => '负责人', 'Attended Names' => '参与人', 'Sort Order' => '排序', 'Place' => '地点', 'Occur At' => '开始时间', 'Ended At' => '结束时间', 'Task Location Status' => '任务定位状态', 'Off' => '禁用', 'On' => '启用', 'Is United' => '是否联合', 'Prev Status' => '上一状态', 'Status' => '状态', 'Disabled' => '禁用', 'Not Started' => '未认领', 'Started' => '已认领', 'Completed' => '已完成', 'Task Steps' => '任务步骤', 'Is Not Isolated' => '是否跨租户', 'No' => '否', 'Yes' => '是', 'Created At' => '创建时间', 'Updated At' => '更新时间', 'Deleted At' => '删除时间', ];
9、在浏览器中打开网址:http://api.pcs-api.localhost/v1/plan-tasks/export ,成功下载文件:我的任务-2019-09-20-19-39-11.xlsx,如图5
10、打开文件:我的任务-2019-09-20-19-39-11.xlsx,符合预期,如图6
11、在 Postman 中打开网址:http://api.pcs-api.localhost/v1/plan-tasks/export ,点击 “Send” 按钮,响应乱码,如图7
12、在 Postman 中打开网址:http://api.pcs-api.localhost/v1/plan-tasks/export ,点击 “Send and download” 按钮,下载名为:response.txt 的文件,且文件中无内容,如图8
13、在 Postman 中设置 Headers , Accept:application/xml; version=0.0,打开网址:http://api.pcs-api.localhost/v1/plan-tasks/export ,点击 “Send and download” 按钮,下载名为:*-2019-09-26-10-44-40.xlsx 的文件,文件名中的中文乱码,但文件中的内容符合预期,如图9
14、在前端(基于 Ant Design)页面中,请求网址:http://api.pcs-api.localhost/v1/plan-tasks/export ,响应乱码,并未下载文件,如图10
15、初步分析原因,应该在于前端代码对于响应头并未实现完整地解析,因此,即使服务端响应要求前端下载文件,但是前端仍然基于 json 格式来解析数据,如图11
16、最终决定基于纯粹的链接来实现,使用 download 属性,此属性指示浏览器下载 URL 而不是导航到它,因此将提示用户将其保存为本地文件。,点击 “导出” 按钮,下载名为:我的任务-2019-09-26-10-44-40.xlsx 的文件,如图12
<a href='http://api.pcs-api.localhost/v1/plan-tasks/export' download>导出</a>
近期评论