開発tax-assistantメモ
Nuxt/Vueで選択行をURLクエリパラメータに同期する実装
結論
テーブルで選択した行のインデックスをURLクエリパラメータに含めることで、ブラウザバックで元の位置に戻れるようになる。
実装のポイント:
buildQueryに選択インデックスを追加- 行選択時に
updateQueryParams()を呼ぶ(内部でrouter.replaceを使用し履歴は増えない) 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を使うと、各ビューでの実装が楽になる