2025.08.11(更新日: 2025.08.11)
現状のオフライン対応の仕組み
はじめに
以下のプロジェクトを「docker compose up –build」で起動した後、

同一IPアドレス:ポート番号でスマホでアクセスできる。

機内モードに変更
機内モードにすると、以下のようになる。

オフラインモード(変更は後で同期)が表示されている。
機内モードを外す
機内モードを外すと、オフラインモードの文字が消える。
現状の問題点
5Gのモバイル通信で、オフライン操作が行えない。
具体的には、商品の追加ができない。
同一IPセグメントにいないためと思われる。
機内モードにしたときに、オフラインモードの表示が行われる理由
それは、frontend/src/components/ShoppingList.vueで以下の定義をしているから。
<template>
<div>
<div v-if="offline" style="margin:8px 0; font-size:12px; opacity:.7;">オフラインモード(変更は後で同期)</div>
</div>
</template>
v-if
v-if=”offline”で、offlineの時に、この要素が表示されるという指定をしている。
<div v-if="offline" style="margin:8px 0; font-size:12px; opacity:.7;">オフラインモード(変更は後で同期)</div>
リアクティブ変数 : offline
このofflineは以下のように定義されていた。
<script setup>
const offline = ref(!navigator.onLine)
window.addEventListener('online', () => { offline.value = false; flushQueue() })
window.addEventListener('offline', () => { offline.value = true })
</script>
offlineという変数は、TrueかFalseになる。
インターネット接続されている時に、navigator.onLineがTrueになる。
!navigator.onLineは、インターネット接続されていないときにTrueになる。
それをrefで囲むことで、Vueのリアクティブ変数というものになる。
つまり、インターネット接続がされていない時、offlineは、Trueになる。
const offline = ref(!navigator.onLine)
オンラインイベント検知
インターネット接続のイベントを検知すると、offlineがfalseになる。
そして、flushQueue関数というものが発火する。
window.addEventListener('online', () => { offline.value = false; flushQueue() })
flushQueue関数
ShoppingList.vueに定義されている。
<script setup>
// オンライン復帰時:キューを順に実行
async function flushQueue() {
let q = loadQueue()
if (!q.length) return
for (const job of q) {
try {
if (job.type === 'createSection') {
await axios.post('/api/sections/', { list: listId, title: job.payload.title })
} else if (job.type === 'updateSection') {
// tmp-id の可能性:最新状態取得後に反映されるのでスキップしてもOK
const { id, title } = job.payload
if (!(''+id).startsWith('tmp-sec-')) await axios.patch('/api/sections/'+id+'/', { title })
} else if (job.type === 'removeSection') {
const { id } = job.payload
if (!(''+id).startsWith('tmp-sec-')) await axios.delete('/api/sections/'+id+'/')
} else if (job.type === 'createItem') {
const { sectionId, name, qty } = job.payload
// tmp section の時は最新pull後に作成し直されるので一旦後回しにできる
if (!(''+sectionId).startsWith('tmp-sec-')) {
await axios.post('/api/items/', { section: sectionId, name, qty, position: 9999 })
}
} else if (job.type === 'toggleItem') {
const { id } = job.payload
if (!(''+id).startsWith('tmp-item-')) await axios.post('/api/items/'+id+'/toggle/', { hard_delete: true })
} else if (job.type === 'reorder') {
const { sectionId, ids } = job.payload
if (!(''+sectionId).startsWith('tmp-sec-') && !ids.some(i => (''+i).startsWith('tmp-item-'))) {
await axios.post(`/api/sections/${sectionId}/reorder/`, { item_ids: ids })
}
}
// 成功したjobは削除
q = loadQueue().filter(j => j.id !== job.id); setQueue(q)
} catch (e) {
// 失敗したら残して終了(次回オンラインで再試行)
break
}
}
await fetchSectionsOnline()
}
onMounted(async () => {
try {
if (!offline.value) {
await ensureListOnline()
await fetchSectionsOnline()
await flushQueue()
} else {
loadSectionsOffline()
}
} catch (e) {
// APIに繋がらない場合はオフライン扱いでローカル読込
offline.value = true
loadSectionsOffline()
}
})
</script>
オフライン中に溜め込んだ操作をオンライン復帰後に行っている。
オフラインイベント検知
インターネット未接続のイベントを検知すると、offlineがtrueになる。
window.addEventListener('offline', () => { offline.value = true })
コメントを残す