トップイメージ

vue.jsとvuetifyでユーザー管理画面作った

こんにちは。休日もプログラムばっかりやってる筆者です。 筆者は業務でよく Rails + jqueryを利用しています。

jqueryで動く画面を作成するのに結構な手間がかかるのですが、その度に、「これってvue.jsならどんな風に書けるんだろう?」と思うことがあります。

今回は経験が浅いプログラマーが最初に作成を命じられるであろう、管理画面のうち、ユーザー管理画面を超簡単に作成してみました。

仕上がりイメージ

画面はこんな感じで適当に グリッドクリック前イメージ

グリッドをクリックするとこんな感じ グリッドクリック後

実装のコンセプトは?

jqueryは素晴らしいライブラリだと思うのですが、やはりコンポーネント化したものを呼び出すということがしにくいので、同じようなものを色んなページで使うには、また同じようなコードを書き直さなければなりません。😢

やはり、手でコードを書けば書くほどエラーというものに繋がりますので、同じような処理を繰り返すのは避けたいところです⭐️

今回はユーザー管理画面としていますが、他の管理画面でもこのようなデータテーブル(グリッド)を使用する場面は多々ありますので、今回は左のデータテーブル部分をコンポーネント(部品化)し、親画面で必要なデータを用意してあげて、呼び出すという点を意識しています。

コード

↓子コンポーネント(grid.vue)

<!-- grid.vue -->

<template>
<v-app>
    <v-flex class="text-xs-center" mb-5>
        <!-- tableを動的に表示 -->
        <v-data-table
            :headers="headers"
            :items="contents"
            class="elevation-1 w-100"
            load = true
            :search="search"
            light
            :pagination.sync="pagination"
        >
            <template slot="items" slot-scope="myprops">
                <tr v-on:click="trClick">
                <!-- 親コンポーネントで指定したヘッダーの値だけ表示 -->
                    <td v-for="header in headers" v-show="header.class">
                    {{ myprops.item[header.value] }}
                    </td>
                </tr>
            </template>
            <template v-slot:footer >
                <td :colspan="headers.length">
                    <v-card>
                        <v-card-title>
                        検索
                        <v-spacer></v-spacer>
                        <v-text-field
                            v-model="search"
                            append-icon="search"
                            label="検索"
                            single-line
                            hide-details
                        ></v-text-field>
                        </v-card-title>
                    </v-card>
                </td>
            </template>
            <!-- 検索が見つからない場合 -->
            <template v-slot:no-results>
                <v-alert :value="true" type="error" icon="warning">
                    "{{ search }}" が見つかりませんでした
                </v-alert>
            </template>
            
        </v-data-table>
    </v-flex>
</v-app>
</template>


<style>
.un-show:{
    display: block !important;
}
</style>

<script>
import Axios from 'axios'

export default{
  props:{
      headers:Array,
      contents:Array
  },
  data () {
    return {
      no_data_flg: false,
      search: '',
      pagination: {
        sortBy: 'content',
        descending: true,
        rowsPerPage: 10,
      },
    }
  },
  methods:{
      trClick:function(e){
          //親画面にイベントとデータを引き渡す
          this.$emit('grid_tr_click',e)
      }
     
  }
}
</script>



↓親コンポーネント

<template>
<v-app>
    <v-container>
    <!-- Userのデータテーブル -->
    <v-layout class="table" row wrap>
        <v-flex xs6>
            <grid
            :headers="headers"
            :contents="users"
            @grid_tr_click="InputForm"
            >
            </grid>
        </v-flex>   
        <v-flex xs5 class="ml-5">
          <template>
              <v-text-field
                label="氏名"
                value=""
                hint="ユーザー氏名"
                id="user_simei"
                v-model="simei"
              >
              </v-text-field>
              <v-text-field
                label="氏名かな"
                value=""
                hint="ユーザー氏名かな"
                id="user_simei_kana"
                v-model="simei_kana"
              >
              </v-text-field>
              <v-text-field
                label="所属"
                value=""
                hint="所属情報"
                id="user_syozoku"
                v-model="syozoku"
              >
              </v-text-field>
              <v-text-field
                label="生年月日"
                value=""
                id="user_seinen_ymd"
                v-model="seinen_ymd"
              >
              </v-text-field>
              <v-layout justify-end>
                <v-btn color="success">submit</v-btn>
                <v-btn color="error">clear</v-btn>
              </v-layout>
          </template>
        </v-flex>
    </v-layout>
  </v-container>
</v-app>
</template>


<style scoped>

</style>

<script>
import Axios from 'axios'
import Grid from '../base_conponent/grid.vue'


export default{
  components: {
    "grid":Grid
  },
  data () {
    return {
      //グリッドに詰めるデータ用の箱
      users:[],
      //ヘッダーをここで指定
      headers: [
          {
            text: 'ID',
            align: 'center',
            sortable: true,
            value: 'id',
            class:"false",
          },
          {
            text: '氏名',
            align: 'center',
            sortable: true,
            value: 'name',
            class:"true",
          },
          { text: 'ふりがな', 
            align:"center",
            value: 'name_kana',
            sortable: true,
            class:"true" },
            { text: '所属', 
            align:"center",
            value: 'syozoku',
            sortable: true,
            class:"false" },
            { text: '生年月日', 
            align:"center",
            value: 'seinen_ymd',
            sortable: true,
            class:"false" },
        ],
        //data-table検索用(無くても良い)
        search: '',
        //グリッド右のテキストボックスにデータを詰めるため
        simei:"",
        simei_kana:"",
        syozoku:"",
        seinen_ymd:"",
        //グリッド不要な列削除用
        id:false
    }
  },
  mounted(){
      let self = this;
      //data-table用のデータをrailsサーバーから取得
      Axios.get('/users/search')
        .then((res) => {
            self.users = res.data.data;
        })
        .catch(error => {
          alert("データ取得失敗");
        })
  },
  methods:{
    InputForm:function(data){
      this.simei = data.currentTarget.cells[1].innerHTML;
      this.simei_kana = data.currentTarget.cells[2].innerHTML;
      this.syozoku = data.currentTarget.cells[3].innerHTML;
      this.seinen_ymd = data.currentTarget.cells[4].innerHTML;
    }
  }
}
</script>

Rails側のAPI

def search
    users = User.all
    render json:{total:users.size,data:users.to_a}
  end

整理

見た目の部分に関しては、結構簡単に実装できてしまいました。

やはりvue.jsのコンポーネント化は素晴らしいですね🌟 これで何度も同じようなコードを書かずに済みますし、間違いも減ります。 どこかでバグが出ても、元のコンポーネントを修正すれば、あちこち変更をせずに済みます。

次回は非同期通信を利用して、ページ遷移をせずに、Userテーブルのレコードの新規作成・更新をできるようにしたいと思います。⭐️