fe17886ac4
- 车辆 / 洗车 / 加油 / 充电 / 保养 / 保险 完整 CRUD + 软删 - AI 截图识别(5 类型 OCR schema):OpenAI 兼容 + MiniMax M3 - 化学品 / Grocy 实例对接 + 库存镜像同步 - 仪表盘:30 天频次 + 健康度 + 同比环比 + 油价趋势 + 年均养护 - 月度报表:Excel 6 sheet + PDF - PWA:manifest / SW / 离线缓存 / iOS 引导 - 安全:bcrypt + CSRF + 登录锁定(IP/用户/全局三级)+ 401 自动跳登录 + 表单草稿 - 高 ROI 8 功能:里程/提醒/成本/搜索/标签/通知/同比/成就 - 3 个新 migration(0016/0017/0018)+ 18 个迁移全幂等 - 101/101 测试通过(含 ipRateLimit / CSRF / retry / stats / tags / notifications) - 部署:宝塔面板文档 + PM2 + Nginx
90 lines
2.8 KiB
JavaScript
90 lines
2.8 KiB
JavaScript
// client/src/stores/pwa.js — PWA 状态:更新提示 / 安装提示 / 离线
|
||
import { defineStore } from 'pinia';
|
||
import { ref, computed } from 'vue';
|
||
|
||
export const usePwaStore = defineStore('pwa', () => {
|
||
/** true = 已有新版本 SW 等激活,需要刷新 */
|
||
const needRefresh = ref(false);
|
||
/** true = 资源已缓存完,可离线用 */
|
||
const offlineReady = ref(false);
|
||
/** 浏览器/桌面触发安装的事件(Android/Desktop Chrome) */
|
||
const installPromptEvent = ref(null);
|
||
/** 已安装(standalone 模式运行) */
|
||
const isInstalled = computed(() => {
|
||
if (typeof window === 'undefined') return false;
|
||
return (
|
||
window.matchMedia('(display-mode: standalone)').matches ||
|
||
window.navigator.standalone === true // iOS Safari
|
||
);
|
||
});
|
||
/** iOS 设备 + Safari + 未安装 → 引导走"分享 → 添加到主屏幕" */
|
||
const isIosSafari = computed(() => {
|
||
if (typeof window === 'undefined') return false;
|
||
const ua = window.navigator.userAgent;
|
||
return /iPad|iPhone|iPod/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS|EdgiOS/.test(ua);
|
||
});
|
||
|
||
let updateFn = null;
|
||
|
||
function bindRegisterSw(registerFn) {
|
||
updateFn = registerFn;
|
||
}
|
||
|
||
function triggerNeedRefresh() {
|
||
needRefresh.value = true;
|
||
}
|
||
function triggerOfflineReady() {
|
||
offlineReady.value = true;
|
||
// 5s 后自动收起
|
||
setTimeout(() => (offlineReady.value = false), 5000);
|
||
}
|
||
async function applyUpdate() {
|
||
if (updateFn) {
|
||
await updateFn(true);
|
||
needRefresh.value = false;
|
||
} else {
|
||
window.location.reload();
|
||
}
|
||
}
|
||
function dismissNeedRefresh() {
|
||
needRefresh.value = false;
|
||
}
|
||
function dismissOfflineReady() {
|
||
offlineReady.value = false;
|
||
}
|
||
async function promptInstall() {
|
||
const e = installPromptEvent.value;
|
||
if (!e) return false;
|
||
e.prompt();
|
||
const choice = await e.userChoice;
|
||
installPromptEvent.value = null;
|
||
return choice.outcome === 'accepted';
|
||
}
|
||
function captureInstallPrompt(e) {
|
||
e.preventDefault();
|
||
installPromptEvent.value = e;
|
||
}
|
||
// 全局监听 beforeinstallprompt
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener('beforeinstallprompt', captureInstallPrompt);
|
||
window.addEventListener('appinstalled', () => {
|
||
installPromptEvent.value = null;
|
||
});
|
||
}
|
||
|
||
return {
|
||
needRefresh,
|
||
offlineReady,
|
||
installPromptEvent,
|
||
isInstalled,
|
||
isIosSafari,
|
||
bindRegisterSw,
|
||
triggerNeedRefresh,
|
||
triggerOfflineReady,
|
||
applyUpdate,
|
||
dismissNeedRefresh,
|
||
dismissOfflineReady,
|
||
promptInstall,
|
||
};
|
||
});
|