Laravelのユーザテーブル情報をVueの画面からダウンロードします
Vue側のダウンロードの処理一切はダウンロードボタンのコンポーネントに閉じ込めてみます
CSVはWindowsで開けるように BOM つけときます(macは動作確認してません..)
環境設定他関連記事はこちら
Laravel + Vue + Vuetify で業務サイト作ってみる
作業はこちらを参考に
LaravelでCSVダウンロード。 - Qiita
[Laravel5.4] 大容量CSVのダウンロード - Qiita
Laravel 5.3でcsvのダウンロード機能を実装
【Laravel 5.4】ファサード の作り方 - Qiita
#1.LaravelでCSVダウンロードするサービスを作成
<?php
namespace App\Services;
use Illuminate\Support\Facades\Log;
use Response;
class Csv
{
/**
* CSVダウンロード
* @param array $csv_data
* @param array $csv_header
* @param string $csv_filename
* @return \Illuminate\Http\Response
*/
public function download($csv_data, $csv_header, $csv_filename)
{
Log::Debug(__CLASS__.':'.__FUNCTION__);
// ヘッダー指定あれば1行目にヘッダーをセット
if (count($csv_header) > 0) {
array_unshift($csv_data, $csv_header);
}
// ストリームでレスポンス ::
// vendor/laravel/framework/src/Illuminate/Routing/ResponseFactory.php
// streamDownload($callback, $name = null, array $headers = [], $disposition = 'attachment')
return response() -> streamDownload(
function () use($csv_data) {
$file = new \SplFileObject('php://output', 'w');
foreach ($csv_data as $row) {
$file->fputcsv($row);
}
},
$csv_filename,
array('Content-Type' => 'application/octet-stream')
);
}
}
今回、BOMは Vue側で付与するので、Laravelでは素直に出力するだけにしてます
また、ファイル名指定をつけてますが、実際のファイル名はVue側で設定するので、ここのファイル名指定は利用してないです(手抜き。。)
本来は Vue 側で attachment の ファイル名を取得して利用するべきですが。。。手抜きです。。
あまりネット上で記述がなかった streamDownload (https://readouble.com/laravel/5.7/ja/responses.html#file-downloads) を利用した手順で書いてみました。
#2.作成したサービスをファサード化
参考記事のままです。
【Laravel 5.4】ファサード の作り方 - Qiita
助かりました。
2-1.サービスプロバイダ作成
artisnコマンドでひな形作って
$ php artisan make:provider CsvServiceProvider
レジスタ部分にバインドを記述
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class CsvServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->bind(
'Csv', 'App\Services\Csv'
);
}
}
2-2.ファサードクラスを作成
ディレクトリを作成して
$ mkdir app/Facades
クラスを作成
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Csv extends Facade {
protected static function getFacadeAccessor() {
return 'Csv';
}
}
2-3.サービスプロバイダと別名の設定を追加
~~~
'providers' => [
~~~
App\Providers\CsvServiceProvider::class,
],
~~~
'aliases' => [
~~~
'Csv' => App\Facades\Csv::class,
],
~~~
#3.Laravel のUserコントローラに download を追加
ユーザテーブルから指定カラムのみを取り出してCSVダウンロードのファサードに渡してます
お手軽ですね
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Hash;
use App\User;
use Validator;
use App\Facades\Csv; //★これを追加
class UserController extends Controller
{
public function index()
{
Log::Debug(__CLASS__.':'.__FUNCTION__);
$users = User::all();
return ['users' => $users];
}
public function download(Request $request) //★このfunctionも追加
{
Log::Debug(__CLASS__.':'.__FUNCTION__);
$csv_data = User::get(['loginid', 'name', 'role'])->toArray();
$csv_header = ['loginid', 'name', 'role'];
return Csv::download($csv_data, $csv_header, 'test.csv'); // ここでファサード呼んでる
}
~~~
#4.Laravelのルーティング設定
CSVダウンロードは管理者以上の権限でないと利用できないように制限かけときます
<?php
Route::get('/', function () {
return view('home');
})->middleware('auth');
// Authentication Routes...
Route::get('/login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('/login', 'Auth\LoginController@login');
Route::post('/logout', 'Auth\LoginController@logout')->name('logout');
// Admin
Route::group( ['middleware' => ['auth', 'can:admin']], function() {
// USER
Route::post('/api/admin/user', 'UserController@index')->name('admin/user');
Route::post('/api/admin/user/store', 'UserController@store')->name('admin/user/store');
Route::post('/api/admin/user/destroy', 'UserController@destroy')->name('admin/user/destroy');
Route::post('/api/admin/user/download', 'UserController@download')->name('admin/user/download');
});
// Other
Route::get('/{any}', function () {
return view('home');
})->middleware('auth')->where('any', '.*');
★ /api/admin/user/download で Vueから UserコントローラのCSVダウンロードを呼び出します
設定確認
php artisan route:list
+--------+----------+-------------------------+---------------------+---------------------------------------------------------+--------------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+----------+-------------------------+---------------------+---------------------------------------------------------+--------------------+
| | GET|HEAD | / | | Closure | web,auth |
| | POST | api/admin/user | admin/user | App\Http\Controllers\UserController@index | web,auth,can:admin |
| | POST | api/admin/user/destroy | admin/user/destroy | App\Http\Controllers\UserController@destroy | web,auth,can:admin |
| | POST | api/admin/user/download | admin/user/download | App\Http\Controllers\UserController@download | web,auth,can:admin |
| | POST | api/admin/user/store | admin/user/store | App\Http\Controllers\UserController@store | web,auth,can:admin |
| | GET|HEAD | api/user | | Closure | api,auth:api |
| | GET|HEAD | login | login | App\Http\Controllers\Auth\LoginController@showLoginForm | web,guest |
| | POST | login | | App\Http\Controllers\Auth\LoginController@login | web,guest |
| | POST | logout | logout | App\Http\Controllers\Auth\LoginController@logout | web |
| | GET|HEAD | {any} | | Closure | web,auth |
+--------+----------+-------------------------+---------------------+---------------------------------------------------------+--------------------+
#5.VueでCSVダウンロード専用のコンポーネントを作成
このボタンの中でサーバ通信からファイル保存までCSVダウンロード処理のすべてをまかないます
ダウンロード処理中はボタンを非活性にして処理中を表す「ぐるぐる」を表示
※ ぐるぐるを見るには Chromeで通信帯域を絞ったりすると出ると思います
ダウンロードボタンの色やアイコンや文言などはコンポーネント呼び出し時に指定可能にしときます(props)
サーバのURLやCSVファイル名も指定可能にしておきます(props)
<template>
<v-btn block flat
:color="color ? color : 'primary'"
:loading="csvdownloading"
:disabled="csvdownloading"
@click="csvdownload(filename, url)"
>
<v-icon dark class="mr-1">{{icon ? icon : 'cloud_download' }}</v-icon> {{title ? title : 'CSV ダウンロード'}}
<v-progress-circular slot="csvdownload" indeterminate color="primary" dark></v-progress-circular>
</v-btn>
</template>
<script>
export default {
name: 'CSVDownload',
props: {
color: String,
icon: String,
title: String,
url: String,
filename: String,
},
data: () => ({
csvdownloading: false,
}),
created() {
if (process.env.MIX_DEBUG) console.log('CSV Download Btn created.')
},
methods: {
csvdownload(filename, url) {
if (process.env.MIX_DEBUG) console.log("CSV Download func csvdownload")
var config = {
responseType: 'blob',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
}
this.csvdownloading = true
axios.post(this.url, config)
.then( function (response) {
this.csvdownloading = false
this.saveCsvFile(response)
}.bind(this))
.catch(function (error) {
if (process.env.MIX_DEBUG) console.log("CSV Download csvdownload error")
this.csvdownloading = false
console.log(error)
if (error.response && [401, 419].includes(error.response.status)) {
this.$emit('axios-logout')
}
}.bind(this))
},
saveCsvFile(res) {
// CSVデータ取得 - BOM 付与
var blob = new Blob(['\ufeff' + res.data], { type: 'text/csv' })
// ファイル名設定 - ファイル名には日時をつけて拡張子 csv を設定
// - ボタンの引数で指定された名前があれば尊重
// - 指定なしならルーティングのページ名をつけておく
// - (サーバから指定されたファイル名は無視してます)
var filename = this.filename
if (! filename) {
filename = this.$route.meta.name
}
filename += '_' + moment(Date.now()).format("YYYYMMDD_HHmmss") + '.csv'
// IE11 ( msSaveBlog が有効なら)
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, filename)
window.navigator.msSaveOrOpenBlob(blob, filename)
}
// IE11 以外なら( Chrome, Firefox, Android, etc...)
else {
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', filename)
link.click()
}
},
},
}
</script>
#6.Vueのユーザ一覧画面にダウンロードボタンを追加
CSVダウンロードボタンのコンポーネントを組み込みます
<template>
<v-flex>
<v-card xs12 class="m-3 px-3">
<v-card-title class="title">
<v-icon class="pr-2">{{ $route.meta.icon }}</v-icon> {{ $route.meta.name }} {{ /* 社員管理 */ }}
<user-dialog ref="userDialog" @reload="reload" @setsearch="setsearch"></user-dialog>
<v-spacer></v-spacer>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
prepend-icon="search"
label="Search"
single-line
hide-details
clearable
></v-text-field>
</v-card-title>
<v-data-table
:headers="headers"
:items="tabledata"
:pagination.sync="pagination"
:rows-per-page-items='[10,25,50,{"text":"All","value":-1}]'
:loading="loading"
:search="search"
class="elevation-0 p-1"
>
<v-progress-linear slot="progress" color="blue" indeterminate></v-progress-linear>
<template slot="items" slot-scope="props">
<tr>
<td class="text-xs-center" xs1>{{ (props.index + 1) + (pagination.page - 1) * pagination.rowsPerPage }}</td>
<template v-for="n in (headers.length - 2)">
<td :class="'text-xs-' + headers[n].align" style="white-space: nowrap;" v-text="props.item[headers[n].value]"></td>
</template>
<td class="text-xs-center" xs1>
<v-btn flat small fab @click="dialogOpen(props.item)"><v-icon color="success">edit</v-icon></v-btn>
<v-btn flat small fab @click="dialogOpen(props.item,true)"><v-icon color="error">delete</v-icon></v-btn>
</td>
</tr>
</template>
</v-data-table>
<v-spacer></v-spacer>
<v-card-actions>
<v-btn flat block color="primary" @click="dialogOpen(null)"><v-icon>person_add</v-icon>新規追加</v-btn>
<v-spacer></v-spacer>
★1 <csv-download url="/api/admin/user/download" color="primary"></csv-download>
</v-card-actions>
</v-card>
</v-flex>
</template>
<script>
import user_dialog from './UserDialog.vue'
★2 import csv_download from './CsvDownload.vue'
export default {
name: 'UserComponent',
components: {
'user-dialog': user_dialog,
★2 'csv-download': csv_download,
},
props: {
},
~~~
★2 でコンポーネント登録
★1 でCSVダウンロードボタンコンポーネントを利用
ユーザ一覧側の変更はこれだけ
ダウンロード処理系はボタンのコンポーネントに閉じ込めたからすっきりです
#7.日付処理系の moment 導入
ダウンロードしたCSVファイル名に日時をつけるために momentを導入です
$ npm install moment
読み込み設定
require('./bootstrap');
// Vue
import Vue from 'vue'
// Vuetify
import Vuetify from 'vuetify'
import colors from 'vuetify/es5/util/colors'
Vue.use(Vuetify)
import 'vuetify/dist/vuetify.min.css'
import 'material-design-icons-iconfont/dist/material-design-icons.css'
// Vue-Router
import router from './router'
// moment
window.moment = require('moment')
// Main app
const app = new Vue({
el: '#app',
router,
});
#8.動作確認
npm コンパイルして実行
管理者でログインして
npm run dev
php artisan serve --host=172.16.0.100 --port=8000
8-1 ユーザ一覧ページを開いて
ダウンロードボタンができていること
8-2 CSVダウンロードボタンを押すと
CSVファイルがダウンロードされること
Excel でダウンロードファイルを開いたら、文字化けしていないことも確認!
(Windows10、 Excel2016 で動作確認済み)
以上
なんとか文字化けせずにCSVダウンロードできるようになりました。。。
Vue でいったん受け取ってからファイルにしているからか Laravel側で BOM つけてもうまくいかなかったんですよね。。
めんどくさくなって Vue 側でつけたらすんなりいきました
今回もソースはこちら
https://github.com/u9m31/u9m31/tree/step07