Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5909eabba8 | ||
|
|
2ee5f5760f |
@@ -0,0 +1,9 @@
|
|||||||
|
# v0.8.1 修复启动竞态崩溃
|
||||||
|
|
||||||
|
## 修复
|
||||||
|
- 修复「Cannot read properties of undefined (reading 'attendance')」启动崩溃:当本机存有登录会话但静默登录失败时(典型场景:服务器重启/重建容器的窗口期打开应用),旧版会按已登录渲染但本地缓存库未绑定,视图挂载即崩溃。
|
||||||
|
- 现在会话恢复会校验本地缓存库已绑定;未绑定则回到登录页,重新登录即可(服务器恢复后一次点击进入)。
|
||||||
|
- 退出登录改为先卸载视图再解绑缓存库,消除微任务间隙的同类崩溃窗口。
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
- 该问题为潜伏缺陷,服务器重建窗口期触发;v0.8.0 功能本身不受影响。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# v0.8.2 部署缓存确定性
|
||||||
|
|
||||||
|
## 优化
|
||||||
|
- 服务端静态托管补齐缓存策略:带内容哈希的 /assets/ 产物永久缓存(immutable),index.html 与 sw.js 改为 no-cache 每次校验——部署后任何浏览器/缓存层都会立刻拿到新包。
|
||||||
|
- PWA Service Worker 页面导航请求强制重新校验(cache: no-cache),缓存存储升级至 v2 自动清理旧条目。
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
- 配合 v0.8.1 的启动竞态修复使用;部署命令不变,部署后普通刷新即可拿到新包(无需强制刷新)。
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-oDK3lWem.js","./index-BI5aMIlx.js","./index-CwfY7_G6.css"])))=>i.map(i=>d[i]);
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-BQsFOvf-.js","./index-COxaR0MK.js","./index-BWain5t9.css"])))=>i.map(i=>d[i]);
|
||||||
import{r as s,_ as i}from"./index-BI5aMIlx.js";var t;(function(e){e.Documents="DOCUMENTS",e.Data="DATA",e.Library="LIBRARY",e.Cache="CACHE",e.External="EXTERNAL",e.ExternalStorage="EXTERNAL_STORAGE"})(t||(t={}));var a;(function(e){e.UTF8="utf8",e.ASCII="ascii",e.UTF16="utf16"})(a||(a={}));const E=s("Filesystem",{web:()=>i(()=>import("./web-oDK3lWem.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem};
|
import{r as s,_ as i}from"./index-COxaR0MK.js";var t;(function(e){e.Documents="DOCUMENTS",e.Data="DATA",e.Library="LIBRARY",e.Cache="CACHE",e.External="EXTERNAL",e.ExternalStorage="EXTERNAL_STORAGE"})(t||(t={}));var a;(function(e){e.UTF8="utf8",e.ASCII="ascii",e.UTF16="utf16"})(a||(a={}));const E=s("Filesystem",{web:()=>i(()=>import("./web-BQsFOvf-.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem};
|
||||||
+10
-10
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -8,8 +8,8 @@
|
|||||||
<link rel="icon" href="./icons/icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="./icons/icon.svg" type="image/svg+xml" />
|
||||||
<link rel="apple-touch-icon" href="./icons/icon-192.png" />
|
<link rel="apple-touch-icon" href="./icons/icon-192.png" />
|
||||||
<title>实习工作日志</title>
|
<title>实习工作日志</title>
|
||||||
<script type="module" crossorigin src="./assets/index-BI5aMIlx.js"></script>
|
<script type="module" crossorigin src="./assets/index-COxaR0MK.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-CwfY7_G6.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-BWain5t9.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'intern-log-v1'
|
const CACHE = 'intern-log-v2'
|
||||||
const CORE = ['./', './index.html']
|
const CORE = ['./', './index.html']
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener('install', event => {
|
||||||
@@ -22,7 +22,7 @@ self.addEventListener('fetch', event => {
|
|||||||
// 页面导航:网络优先,离线回退缓存的应用壳
|
// 页面导航:网络优先,离线回退缓存的应用壳
|
||||||
if (req.mode === 'navigate') {
|
if (req.mode === 'navigate') {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(req)
|
fetch(req, { cache: 'no-cache' })
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
const clone = resp.clone()
|
const clone = resp.clone()
|
||||||
caches.open(CACHE).then(c => c.put('./index.html', clone))
|
caches.open(CACHE).then(c => c.put('./index.html', clone))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "intern-log",
|
"name": "intern-log",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.8.0",
|
"version": "0.8.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'intern-log-v1'
|
const CACHE = 'intern-log-v2'
|
||||||
const CORE = ['./', './index.html']
|
const CORE = ['./', './index.html']
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener('install', event => {
|
||||||
@@ -22,7 +22,7 @@ self.addEventListener('fetch', event => {
|
|||||||
// 页面导航:网络优先,离线回退缓存的应用壳
|
// 页面导航:网络优先,离线回退缓存的应用壳
|
||||||
if (req.mode === 'navigate') {
|
if (req.mode === 'navigate') {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(req)
|
fetch(req, { cache: 'no-cache' })
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
const clone = resp.clone()
|
const clone = resp.clone()
|
||||||
caches.open(CACHE).then(c => c.put('./index.html', clone))
|
caches.open(CACHE).then(c => c.put('./index.html', clone))
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ import { getWorkRule, getWeeklyNote, searchLogItems, searchMemos } from './db/re
|
|||||||
import { todayStr, weekStart } from './lib/date'
|
import { todayStr, weekStart } from './lib/date'
|
||||||
import { showToast, useToasts } from './lib/toast'
|
import { showToast, useToasts } from './lib/toast'
|
||||||
import { focusDate, focusMemo } from './lib/bus'
|
import { focusDate, focusMemo } from './lib/bus'
|
||||||
import { categoryLabel, type LogItem, type Memo } from './db/schema'
|
import { categoryLabel, db, type LogItem, type Memo } from './db/schema'
|
||||||
|
|
||||||
const toasts = useToasts()
|
const toasts = useToasts()
|
||||||
|
|
||||||
// ---- 用户会话(持久化登录,退出走顶栏按钮)----
|
// ---- 用户会话(持久化登录,退出走顶栏按钮)----
|
||||||
const session = ref<Session | null>(getSession())
|
// 会话恢复必须确认本地缓存库已绑定:静默登录失败(如服务器重启窗口期)时
|
||||||
|
// db 仍为 undefined,此时退回登录页,避免视图挂载即读库崩溃
|
||||||
|
const session = ref<Session | null>(getSession() && db ? getSession() : null)
|
||||||
async function onLogin(s: Session) {
|
async function onLogin(s: Session) {
|
||||||
session.value = s
|
session.value = s
|
||||||
openUserDatabase(s.userId)
|
openUserDatabase(s.userId)
|
||||||
@@ -48,8 +50,8 @@ async function doLogout() {
|
|||||||
moreOpen.value = false
|
moreOpen.value = false
|
||||||
stopRealtime()
|
stopRealtime()
|
||||||
if (syncTimer !== null) { window.clearInterval(syncTimer); syncTimer = null }
|
if (syncTimer !== null) { window.clearInterval(syncTimer); syncTimer = null }
|
||||||
|
session.value = null // 先卸载视图再解绑缓存库,避免微任务间隙读到已关闭的库
|
||||||
await logoutUser()
|
await logoutUser()
|
||||||
session.value = null
|
|
||||||
route.value = 'calendar'
|
route.value = 'calendar'
|
||||||
location.hash = '#/calendar'
|
location.hash = '#/calendar'
|
||||||
showToast('已退出登录')
|
showToast('已退出登录')
|
||||||
|
|||||||
+4
-2
@@ -331,16 +331,18 @@ async function handle(req, res) {
|
|||||||
if (rel === '/' || rel === '') rel = '/index.html'
|
if (rel === '/' || rel === '') rel = '/index.html'
|
||||||
const file = path.normalize(path.join(DIST_DIR, rel))
|
const file = path.normalize(path.join(DIST_DIR, rel))
|
||||||
if (!file.startsWith(DIST_DIR)) { json(res, 403, { error: 'forbidden' }); return }
|
if (!file.startsWith(DIST_DIR)) { json(res, 403, { error: 'forbidden' }); return }
|
||||||
|
// 带内容哈希的构建产物可永久缓存;入口与 SW 每次校验,保证部署后立刻拿到新包
|
||||||
|
const cacheControl = rel.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'no-cache'
|
||||||
fs.readFile(file, (err, data) => {
|
fs.readFile(file, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
fs.readFile(path.join(DIST_DIR, 'index.html'), (e2, d2) => {
|
fs.readFile(path.join(DIST_DIR, 'index.html'), (e2, d2) => {
|
||||||
if (e2) { json(res, 404, { error: 'not found' }); return }
|
if (e2) { json(res, 404, { error: 'not found' }); return }
|
||||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' })
|
||||||
res.end(d2)
|
res.end(d2)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.writeHead(200, { 'Content-Type': MIME[path.extname(file).toLowerCase()] || 'application/octet-stream' })
|
res.writeHead(200, { 'Content-Type': MIME[path.extname(file).toLowerCase()] || 'application/octet-stream', 'Cache-Control': cacheControl })
|
||||||
res.end(data)
|
res.end(data)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user