Vuetify3のv-data-tableは、まだ開発中でリリースされていない。
Vuetify3でリリースされたv-data-tableにアップデートする

仕様が変わる可能性はありますが、Vuetify Labsで使用できるので試してみました。
今回はVuetify2からの移行で、表示・変更・削除する基本的な表示や挙動が対象です。

先に結論、item-class(trのclass指定)、headersのclass(thのclass指定)・cellClass(tdのclass指定)、@dblclick:rowやmobile-breakpoint(モバイルデザイン)は、現段階では動きませんでした。
mobile-breakpoint以外は、項目毎でなく全体をSlots(headers/item)するように修正すれば対応可能ですが、v-data-tableに任せている部分も追加する事になるので、果たして良いのか?
急ぎでなければ、待つ方が得策かもしれません。

vue-infinite-loadingはv3-infinite-loadingとほぼ同じ挙動なので、slotでカスタマイズしていなければ、そのまま使えます。sortとdescが纏められてsort-byで取得でき、個別に設定されなくなったので、別々に設定される事を考慮しなくて良くなったのがいい感じ。

Upgrade guide — Vuetify

v3-infinite-loading導入と移行

GitHub – oumoussa98/vue3-infinite-loading: Infinite scroll component compatible with vuejs-3 and vitejs

% yarn add -D v3-infinite-loading

(Options API)pages/spaces/index.vue

        <InfiniteLoading
          v-if="!reloading && space != null && space.current_page < space.total_pages"
          :identifier="page"
          @infinite="getNextSpacesList"
        >
-          <div slot="no-more" />
-          <div slot="no-results" />
-          <div slot="error" slot-scope="{ trigger }">
-            取得できませんでした。
-            <v-btn @click="error = false; trigger()">再取得</v-btn>
-          </div>
+          <template #spinner>
+            <AppLoading height="10vh" class="mt-4" />
+          </template>
+          <template #complete />
+          <template #error="{ retry }">
+            <AppErrorRetry class="mt-4" @retry="error = false; retry()" />
+          </template>
        </InfiniteLoading>

<script>
- import InfiniteLoading from 'vue-infinite-loading'
+ import InfiniteLoading from 'v3-infinite-loading'
+ import AppErrorRetry from '~/components/app/ErrorRetry.vue'
import AppLoading from '~/components/app/Loading.vue'

export default defineNuxtComponent({
  components: {
    InfiniteLoading,
+    AppErrorRetry,
    AppLoading,

  methods: {
    // 次頁のスペース一覧取得
    async getNextSpacesList ($state) {
-      if (this.processing || this.error) { return }
+      if (this.error) { return $state.error() } // NOTE: errorになってもloaded(spinnerが表示される)に戻る為
+      if (this.processing) { return }

      this.page = this.space.current_page + 1
      if (!await this.getSpacesList()) {
        if ($state == null) { this.testState = 'error'; return }

        $state.error()

style.cssは、slotでカスタマイズする場合は不要です。
API取得に失敗して、$state.error()を呼んでも、直後に@infiniteが呼び出されちゃうので、method側で対応しています。

components/app/Loading.vue

LoadingはAPI取得完了までの初期表示でも使っているので、共通化して見た目を揃えています。

(Options API)

<template>
  <div class="text-center">
    <v-progress-circular indeterminate color="primary" :size="50" :style="`height: ${height}`" />
  </div>
</template>

<script>
export default defineNuxtComponent({
  props: {
    height: {
      type: String,
      default: '80vh'
    }
  }
})
</script>

<style scoped>
.v-progress-circular >>> svg {
  height: 50%; /* NOTE: モバイルでヘッダアイコンが左右に動く為 */
}
</style>

モバイルでヘッダアイコンが左右に動く件は、Vuetify2の時から。

components/app/ErrorRetry.vue

複数箇所で使うので、共通化しました。

(Composition API)

<template>
  <div class="d-flex align-center justify-center" style="height: 10vh">
    <div class="text-grey">
      続きを取得できませんでした。
      <v-btn id="error_retry_btn" color="secondary" @click="emit('retry')">再取得</v-btn>
    </div>
  </div>
</template>

<script setup lang="ts">
const emit = defineEmits(['retry'])
</script>

Vuetify Labs導入

Introduction to Labs — Vuetify

最新を使いたいので、@nextでインストールします。

% yarn add -D vuetify@next

package.json に下記が入りました。

-    "vuetify": "^3.3.13"
+    "vuetify": "^3.4.0-alpha.1"

plugins/vuetify.ts

import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
+ import * as labsComponents from 'vuetify/labs/components'
import * as directives from 'vuetify/directives'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(createVuetify({
-    components,
+    components: {
+      ...components,
+      ...labsComponents
+    },

v-data-table暫定対応

v-data-table-serverとv-data-table-virtualが増えています。
-serverだとソートがサーバーサイドになる。
-virtualは書き換えるので早そうですが、表示がチカチカするので見送り。

VDataTableServer API

(Options API)components/members/Lists.vue

<template>
-  <v-data-table
+  <v-data-table-server
    v-if="members != null && members.length > 0"
    v-model="syncSelectedMembers"
-    :sort-by.sync="syncSort"
-    :sort-desc.sync="syncDesc"
+    v-model:sort-by="syncSortBy"
    :headers="headers"
    :items="members"
+    :items-length="members.length"
-    item-key="user.code"
-    :item-class="itemClass"
    :items-per-page="-1"
-   hide-default-footer
-    mobile-breakpoint="600"
    fixed-header
    :height="appTableHeight"
-    must-sort
-    :custom-sort="disableSortItem"
    :show-select="admin"
+    :item-value="item => item"
-    @dblclick:row="showUpdate"
  >
+    <!--
+    :item-class="itemClass"  TODO: 背景色が変わらない
+    mobile-breakpoint="600"  TODO: モバイルデザインにならない
+    @dblclick:row="showUpdate"  TODO: 動かない
+    -->

-  </v-data-table>
+  </v-data-table-server>

<script>

export default defineNuxtComponent({

  computed: {
    syncSortBy: {
      get () {
        return [{ key: this.sort, order: this.desc ? 'desc' : 'asc' }]
      },
      set (value) {
        if (value.length === 0) {
          this.$emit('reload', { sort: this.sort, desc: !this.desc }) // NOTE: 同じ項目で並び順を2回変えると空になる為
        } else {
          this.$emit('reload', { sort: value[0].key, desc: value[0].order === 'desc' })
        }
      }
    },

  methods: {
-    disableSortItem (items) {
-      return items
-    },

text/valueがtitle/keyに変わった

  computed: {
    headers () {
      const result = []
      if (this.admin) {
-        result.push({ value: 'data-table-select', class: 'pl-3 pr-0', cellClass: 'pl-3 pr-0 py-2' })
+        result.push({ key: 'data-table-select', class: 'pl-3 pr-0', cellClass: 'pl-3 pr-0 py-2' }) // TODO: class/cellClassが効かない
      }
      for (const item of this.$tm('items.member')) {
-        if ((item.required || !this.hiddenItems.includes(item.value)) && (!item.adminOnly || this.admin)) {
+        if ((item.required || !this.hiddenItems.includes(item.key)) && (!item.adminOnly || this.admin)) {
-          result.push({ text: item.text, value: item.value, class: 'text-no-wrap', cellClass: 'px-1 py-2' })
+          result.push({ title: item.title, key: item.key, class: 'text-no-wrap', cellClass: 'px-1 py-2' })
        }
      }
      if (result.length > 0) { result[result.length - 1].cellClass = 'pl-1 pr-4 py-2' } // NOTE: スクロールバーに被らないようにする為
      return result
    },

itemがitem.rawに変わった

    <template #[`item.user.name`]="{ item }">
      <div class="ml-1">
-        <UsersAvatar :user="item.user" />
+        <UsersAvatar :user="item.raw.user" />

  methods: {
    showUpdate (event, { item }) {
-      if (!this.admin || item.user.code === this.$auth.user.code) { return }
+      if (!this.admin || item.raw.user.code === this.$auth.user.code) { return }

-      this.$emit('showUpdate', item)
+      this.$emit('showUpdate', item.raw)

headerがcolumnに変わった。ソートアイコンが出なくなった

-    <template #[`header.user.email`]="{ header }">
+    <template #[`column.user.email`]="{ column, getSortIcon }">
-      {{ header.text }}
+      {{ column.title }}
      <OnlyIcon power="admin" />
+      <v-icon class="v-data-table-header__sort-icon">{{ getSortIcon(column) }}</v-icon>
    </template>

slot内のaリンクに色や下線が付かなくなった

通常のaリンクに付く、これを追加しました。

a:-webkit-any-link {
    color: -webkit-link;
    cursor: pointer;
    text-decoration: underline;
}
    <template #[`item.power`]="{ item }">
      <a
        class="text-no-wrap"
+        style="color: -webkit-link; cursor: pointer; text-decoration: underline"

hide-default-footerが効かない

footerのSlotsがなさそうなので、CSSで対応しました。

<style scoped>
+ .v-data-table >>> .v-data-table-footer {
+   display: none; /* NOTE: hide-default-footerが効かない為 */
+ }

hide-default-headerが効かない

こちらはheadersのSlotsに空を渡せばOK。

components/spaces/Lists.vue

  <v-data-table-server
  >
+    <template #headers /><!-- NOTE: hide-default-headerが効かない為 -->

disable-sortが効かない

すべて使わないは指定できなそうでしたが、
headersに「sortable: false」を追加すれば個別指定が可能。

components/invitations/Lists.vue

- result.push({ title: item.title, key: item.key })
+ result.push({ title: item.title, key: item.key, sortable: false })

今回のコミット内容

origin#507 Vuetify Labs・v3-infinite-loading導入、v-data-table暫定対応、必須・任意ラベル変更、リファクタ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です