vueからアップロードしてLaravelで保存したデータを論理削除できるようにしてみます
検索時に「論理削除したデータも含める」かも指定可能に
論理削除する対象データは関連記事の給与明細データ
ついでのついでに明細データの公開フラグも更新できるようにしときます
環境設定他関連記事はこちら
Laravel + Vue + Vuetify で業務サイト作ってみる
DBテーブル
論理削除するのはアップした給与明細のCSVファイルを管理するテーブル 「csv_payslips」
テーブルにはすでに論理削除用のカラムを設定済み
~~~
class CreateCsvPayslipsTable extends Migration
~~~
Schema::create('csv_payslips', function (Blueprint $table) {
~~~
$table -> softDeletes();
~~~
モデル
テーブル「csv_payslips」に対応するモデルにも論理削除に必要な項目を設定済み
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
★ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Crypt;
class CsvPayslip extends Model
{
// 論理削除有効化
★ use SoftDeletes;
★ protected $dates = ['deleted_at'];
~~~
削除ボタンを追加
アップした給与明細のCSVファイルの一覧画面に「削除」と「公開」のボタンを追加します
削除済みや公開済みの時に不活性化したりなどでちょっとめんどくさい記述になってます。。
もっときれいに書きたいな。。
~~~
<v-data-table
:headers="headers"
:items="tabledata"
:pagination.sync="pagination"
:rows-per-page-items='[5,10,20,{"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 :class="{'pink--text': (props.item.deleted_at != null)}">
<td class="text-xs-center" xs1>{{ (props.index + 1) + (pagination.page - 1) * pagination.rowsPerPage }}</td>
<template v-for="n in (headers.length - 1)">
★1 <td v-if="headers[n].text != 'アクション'"
:class="'text-xs-' + headers[n].align"
style="white-space: nowrap;"
v-text="props.item[headers[n].value]"
></td>
★1 <td v-else
:class="'text-xs-' + headers[n].align"
style="white-space: nowrap;"
>
★2 <v-tooltip v-if="props.item.published_at == null" right :color="(props.item.deleted_at == null) ? 'success' : 'gray'">
<v-btn fab small flat @click="dialog_open(props.item, 'pub')" slot="activator"
:disabled="props.item.deleted_at != null"
>
<v-icon color="success">lock</v-icon>
</v-btn>
<span>公開</span>
</v-tooltip>
<v-btn v-else fab small flat disabled>
<v-icon color="grey lighten-1">lock_open</v-icon>
</v-btn>
★3 <v-tooltip right :color="(props.item.deleted_at == null) ? 'error': 'gray'">
<v-btn fab small flat @click="dialog_open(props.item, 'del')" slot="activator"
:disabled="props.item.deleted_at != null">
<v-icon color="error">delete</v-icon>
</v-btn>
<span>{{(props.item.deleted_at == null ? '削除' : '削除済')}}</span>
</v-tooltip>
</td>
</template>
</tr>
</template>
</v-data-table>
~~~
★1 headers でアクションを指定したところにアクションボタンを配置
★2 「公開」ボタン 色は「success」で指定 ボタンを押したら dialog_open を呼び出し
★3 「削除」ボタン 色は「error」で指定 ボタンを押したら dialog_open を呼び出し
確認ダイアログの追加
間違ってボタン押したときに問答無用で削除されたりすると悲しすぎるので、確認用のダイアログを追加します
ダイアログは画面の外をクリックしても消えない モーダル で表示
(ダイアログに persistent を付けてモーダルタイプに)
削除と公開と同じダイアログを使いまわします
ダイアログレイアウト部分
~~~
<!-- 確認ダイアログ -->
<v-dialog v-model="dialog" width="500" persistent>
<v-card>
<v-toolbar :color="d.titlecolor" dark>
<v-toolbar-title>{{ d.title }}</v-toolbar-title>
</v-toolbar>
<v-card-text class="subheading">
<span v-html="d.body"></span>
<br>よろしいですか?
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn flat block @click="dialog_no()" > いいえ </v-btn>
<v-spacer></v-spacer>
<v-btn flat block @click="dialog_yes()" :color="d.titlecolor"> はい </v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-flex>
</template>
<script>
~~~
data: () => ({
~~~
// dialog
dialog: false,
d: {
title: '',
titlecolor: '',
icon: '',
type: '',
item: [],
},
}),
ダイアログ制御部分
削除の時と公開の時とで表示する文言を変えてます
ダイアログで「はい」を押したらLaravelへ更新要求を投げます( this.csvUpdate )
ダイアログで「いいえ」を押したら何もしないで閉じます(変数の初期化だけ)
~~~
dialog_open(item, type) {
if (process.env.MIX_DEBUG) console.log('Payslip Component Dialog Open')
this.d.type = type
this.d.item = item
this.d.body = 'CSV ID:' + item.id + '<br>' + '対象年月:' + item.ym + '<br>' + 'ファイル:' + item.filename + '<br><br>'
if (type == 'pub') {
this.d.title = '明細情報を公開します'
this.d.titlecolor = 'success'
this.d.body += '対象のデータを公開します。<br>公開後は非公開とすることはできません。'
}
else {
this.d.title = '明細情報を削除します'
this.d.titlecolor = 'error'
this.d.body += '対象のデータを削除します。'
}
this.dialog = true
},
dialog_yes() {
if (process.env.MIX_DEBUG) console.log('Payslip Component Dialog YES')
this.dialog = false
var url = '/api/admin/payslip/publish'
if (this.d.type == 'del') { url = '/api/admin/payslip/delete' }
this.csvUpdate(url, this.d.item.id)
this.d.type = ''
this.d.item = []
},
dialog_no() {
if (process.env.MIX_DEBUG) console.log('Payslip Component Dialog NO')
this.dialog = false
this.d.type = ''
this.d.item = []
},
~~~
削除時の処理 vue側
削除の時と公開の時とで処理は共用(呼び出しURLだけ変えてます)
削除時:/api/admin/payslip/delete
公開時:/api/admin/payslip/publish
~~~
// CSVの更新
csvUpdate(url, id) {
if (process.env.MIX_DEBUG) console.log('Payslip Component CSV Update')
// パラメータ設定
var params = new URLSearchParams()
params.append('id', id)
// 更新要求
axios.post(url, params)
// 検索結果[正常]
.then( function (response) {
if (process.env.MIX_DEBUG) console.log(response)
this.reload()
}.bind(this))
// 検索結果[異常]
.catch(function (error) {
console.log(error)
if (error.response) {
if ([401, 419].includes(error.response.status)) {
this.$emit('axios-logout')
}
else {
alert('ERROR ' + error.response.status + ' ' + error.response.statusText)
}
}
else {
alert('ERROR ' + error)
}
}.bind(this))
},
~~~
削除時の処理 Laravel側
更新要求を受け取るLaravel側
router側で Adminしか触れないURLに指定するのですごく単純に書いちゃってます
公開日時の設定は Carbon です
public function delete(Request $request)
{
Log::Debug(__CLASS__.':'.__FUNCTION__, $request->all());
// 対象データ削除
$csv_payslip = CsvPayslip::find($request -> id);
if ($csv_payslip) {
$csv_payslip -> delete();
}
return;
}
public function publish(Request $request)
{
Log::Debug(__CLASS__.':'.__FUNCTION__, $request->all());
// 更新データ生成
$data['published_at'] = Carbon::now();
$data['status'] = '1';
// 対象データ更新
$csv_payslip = CsvPayslip::find($request -> id);
if ($csv_payslip) {
$csv_payslip -> fill($data) -> save();
}
return;
}
これで対象データを論理削除可能になりました
ついでに「公開」も
論理削除済みデータをさらに論理削除処理しようとしたりとかのデータチェック、エラー処理等はもろもろ省いてます
何らかのエラーが発生したらたいていは Laravelがブラウザに通知してくれるので。。
あまり使わない?機能だし。。
Laravel ルートの追加
Admin 権限だけがアクセス可能なように設定
~~~
// Admin
Route::group( ['middleware' => ['auth', 'can:admin']], function() {
~~~
// Payslip
Route::post('/api/admin/payslip/csvlist', 'PayslipController@csvlist')->name('admin/payslip/csvlist');
Route::post('/api/admin/payslip/upload', 'PayslipController@upload')->name('admin/payslip/upload');
★ Route::post('/api/admin/payslip/publish', 'PayslipController@publish')->name('admin/payslip/publish');
★ Route::post('/api/admin/payslip/delete', 'PayslipController@delete')->name('admin/payslip/delete');
~~~
検索時に削除済みを含めるかを指定可能に
論理削除は可能になったけど、論理削除したデータをまた確認したい!?って要望に応えるために、削除済みデータも検索可能にしときます
~~~
<v-layout row wrap class="mx-3 my-2">
<!-- 共通: 対象年月(カレンダーで月を選択)-->
<v-flex xs2 md4 lg3>
<v-menu
ref="menu"
v-model="menu"
:return-value.sync="target.ym"
:close-on-content-click="false"
:nudge-right="20"
lazy transition="scale-transition" offset-y full-width max-width="290px" min-width="290px"
show-current="true"
>
<v-text-field
readonly
clearable
autofocus
slot="activator"
v-model="target.ym"
label="対象年月"
placeholder="明細の対象年月を選択"
:hint="'明細の対象年月を選択' + (searchTab?'(指定なしで全期間対象)':'')"
></v-text-field>
<v-date-picker v-model="target.ym" type="month" no-title scrollable locale="ja">
<v-spacer></v-spacer>
<v-btn flat color="primary" @click="menu = false">Cancel</v-btn>
<v-btn flat color="primary" @click="$refs.menu.save(target.ym)">OK</v-btn>
</v-date-picker>
</v-menu>
</v-flex>
★ <!-- 検索: 削除済みデータも検索に含める -->
<v-flex xs5 md4 lg3 v-if="searchTab">
<v-switch
:label="`削除済データ ${target.deleted ? '対象' : '対象外'}`"
v-model="target.deleted"
color="error"
class="px-2"
hint="削除済みデータも検索したい場合に指定"
></v-switch>
</v-flex>
</v-layout>
~~~
★部分を追加
年月指定の後ろにスイッチのイメージを追加して、「検索」処理時にのみ表示するようにしときます
検索時の処理 vue側
検索時に追加した「削除済含」フラグを渡すように改修
~~~
data: () => ({
~~~
target: {
ym: '',
★ deleted: false,
},
~~~
// 登録済みCSVのリストをサーバから取得する
getCsvPayslip() {
if (process.env.MIX_DEBUG) console.log('Payslip Component getCsvPayslip')
// 初期化
this.initList()
// 検索パラメータ設定
var params = new URLSearchParams()
params.append('ym', (this.target.ym ? this.target.ym : ''))
★ params.append('deleted', this.target.deleted)
~~~
★ 部分を追記
ついでにステータス表示も「削除」を追加しときます
setStatus() {
var wk = '不明'
for (var i=0; i<this.tabledata.length; i++) {
if (this.tabledata[i].status) {
wk = '不明'
if (this.tabledata[i].status == 0) { wk = '非公開' }
else if (this.tabledata[i].status == 1) { wk = '公開' }
★ if (this.tabledata[i].deleted_at != null ) { wk = '削除' }
this.tabledata[i].status = wk
}
}
},
検索時の処理 Laravel側
削除済みも含める場合は 「 withTrashed 」を指定するように改修
public function csvlist(Request $request)
{
Log::Debug(__CLASS__.':'.__FUNCTION__, $request->all());
// INIT
$where = array();
// 検索条件確認 - 対象年月(未指定の場合は全期間を対象とする)
$ym = $this -> getYM($request -> all(), false);
if (is_object($ym)) { return $ym; } // 期間エラー
if ($ym) {
$where['ym'] = $ym;
}
if ($where) {
★ if ($request -> deleted == 'true') {
$CsvPayslips = CsvPayslip::withTrashed() -> where($where) -> get();
}
else {
$CsvPayslips = CsvPayslip::where($where)->get();
}
}
else {
★ if ($request -> deleted == 'true') {
$CsvPayslips = CsvPayslip::withTrashed() -> get();
}
else {
$CsvPayslips = CsvPayslip::all();
}
}
return ['data' => $CsvPayslips];
}
★ 部分で判定しているけど、すごく。。美しくない。。。
もっとキレイなコードにならないかな。。
改修 (2019/1/14)
もっときれいに書き直してみた
public function csvlist(Request $request)
{
Log::Debug(__CLASS__.':'.__FUNCTION__, $request->all());
// 検索条件指定準備
$query = CsvPayslip::query();
// -- 削除済みを含む
if (array_key_exists('deleted', $request -> all())) {
if ($request -> deleted == 'true') {
$query -> withTrashed();
}
}
// -- 対象年月(未指定の場合は全期間を対象とする)
$ym = $this -> getYM($request -> all(), false);
if (is_object($ym)) { return $ym; } // 期間エラー
if ($ym) {
$query -> where('ym', '=', $ym);
}
// データ取得 orderby 年月 / id
$CsvPayslips = $query -> orderBy('ym','desc')
-> orderBy('id', 'desc')
-> get();
// 検索結果を戻す
return ['data' => $CsvPayslips];
}
調べてみたら 「 ::query() 」なるものが使えそうってことで書き直してみたら
すごくスッキリした
削除済みデータも含めた検索結果
削除済みデータは色を変えて表示するようにしときました
以上
論理削除の処理と論理削除したデータの検索ができるようになりました
記事ではソースが細切れなので github でソース全文を見てみると良いかもです
今回もソースはこちら
https://github.com/u9m31/u9m31/tree/step12