• #Nuxt
  • #Vue
  • #URL
  • #UX
開発tax-assistantメモ

Nuxt/Vueで選択行をURLクエリパラメータに同期する実装

結論

テーブルで選択した行のインデックスをURLクエリパラメータに含めることで、ブラウザバックで元の位置に戻れるようになる。

実装のポイント:

  1. buildQueryに選択インデックスを追加
  2. 行選択時にupdateQueryParams()を呼ぶ(内部でrouter.replaceを使用し履歴は増えない)
  3. onMountedでクエリパラメータから復元

注意: インデックスは0始まり。UI表示(例: 1/18)は+1して表示する。

問題

クレカ明細ビューで以下の問題があった。

修正前のクレカ明細

  • URLには年月(ccYear=2024&ccMonth=12)までしか含まれていない
  • 「仕訳ルール編集」リンクをクリックして別ページに遷移
  • Alt+←でブラウザバックすると、どの明細を見ていたか分からなくなる
  • ルールマッチ時のプレビューにナビゲーション(前へ/次へ、何件中何件目)が表示されない

解決策

1. buildQueryに選択インデックスを追加

const { currentTab, updateQueryParams, markInitialized } = useTabQuerySync({
  tabName: 'creditcard',
  buildQuery: () => ({
    ccYear: selectedYear.value || undefined,
    ccMonth: selectedMonth.value || undefined,
    ccIndex: selectedIndex.value >= 0 ? String(selectedIndex.value) : undefined,  // 追加
  }),
  // ...
})

2. 行選択時にURLを更新

updateQueryParams()は内部でrouter.replaceを使用するため、選択を変えるたびにブラウザ履歴が増えることはない。

function selectRow(index: number) {
  selectedIndex.value = index
  // ... 既存の処理 ...

  // URLを更新(replaceなので履歴は増えない)
  updateQueryParams()
}

3. onMountedでクエリパラメータから復元

onMounted(async () => {
  await loadData()

  if (import.meta.client) {
    const queryYear = route.query.ccYear as string | undefined
    const queryMonth = route.query.ccMonth as string | undefined
    const queryIndex = route.query.ccIndex as string | undefined  // 追加

    // ... 年月の復元処理 ...

    // 選択行の復元(年月の復元後に実行)
    await nextTick()
    if (queryIndex) {
      const idx = parseInt(queryIndex, 10)
      if (!isNaN(idx) && idx >= 0 && idx < filteredTransactions.value.length) {
        selectRow(idx)
      }
    }
  }
})

4. ルールマッチ時もナビゲーションを表示

ImageNavigationPanelコンポーネントで包むことで、ルール詳細表示でも「前へ/次へ」ボタンと「X/Y」表示を追加。

<template v-if="selectedTransaction?.matched_rules?.length > 0">
  <ImageNavigationPanel
    title="マッチしたルール"
    :current-index="selectedIndex"
    :total-count="filteredTransactions.length"
    @prev="goPrev"
    @next="goNext"
  >
    <!-- ルール詳細の表示 -->
  </ImageNavigationPanel>
</template>

結果

  • URLが?tab=creditcard&ccYear=2024&ccMonth=12&ccIndex=4のような形式に(0始まり、UI表示は5/18)
  • 仕訳ルール編集 → ブラウザバック で元の明細行が選択された状態で戻れる
  • ルールマッチの行でも「1/18」のような位置表示とナビゲーションボタンが表示される

設計判断

localStorageとURLクエリパラメータの使い分け

方式用途
localStorageセッションをまたいで保持したい設定プレビュー幅、最後に見ていた年月
URLクエリブラウザ履歴で復元したい状態選択中の行、フィルタ条件

今回の選択行はブラウザバックで復元したいため、URLクエリパラメータを採用した。

インデックス vs ID

選択行の同期方法には2つの選択肢がある。

方式メリットデメリット
インデックスURLが短い、実装がシンプルソート・フィルタ変更でずれる
ID(transactionId等)データ更新に強いURLが長くなる

今回はインデックスを採用した。理由:

  • 年月フィルタ後のデータは日付昇順で固定
  • フィルタ変更時は選択がリセットされる仕様

データの並び順が動的に変わる場合はIDでの同期を検討すべき。

まとめ

  • URLに状態を含めることで、ブラウザの「戻る」機能と連携できる
  • 他のビュー(読取一覧など)と同じパターンで実装し、コードベースの一貫性を保つ
  • useTabQuerySyncのような共通composableを使うと、各ビューでの実装が楽になる