chrome PWA 支持.

This commit is contained in:
lijiahang
2024-05-22 12:50:30 +08:00
parent 35ee4faffc
commit ca8e629e4c
17 changed files with 1982 additions and 30 deletions

View File

@@ -16,6 +16,9 @@
### v2.0.2
* ⭐ 添加预览模式
* ⭐ 支持 Chrome PWA
`2024-05-2` `release`
### v2.0.1

View File

@@ -1,5 +1,6 @@
node_modules
.DS_Store
dist
dev-dist
dist-ssr
*.local

View File

@@ -0,0 +1,131 @@
import type { VitePWAOptions } from 'vite-plugin-pwa';
import { VitePWA } from 'vite-plugin-pwa';
import { isProductionMode } from '../utils';
/**
* 配置 pwa
*/
export default function configPwaPlugin() {
if (isProductionMode()) {
// 生产启用
return VitePWA(enabled());
} else {
// 本地禁用
return VitePWA(disabled());
}
}
// 禁用
const disabled = (): Partial<VitePWAOptions> => {
return {
disable: true,
manifest: false,
selfDestroying: true,
devOptions: {
enabled: false,
disableRuntimeConfig: true,
},
};
};
// 启用
const enabled = (): Partial<VitePWAOptions> => {
return {
manifest: {
name: 'Orion Visor Community',
short_name: 'Orion Visor',
description: '一款高颜值、现代化的智能运维&轻量堡垒机平台。',
theme_color: '#212529',
icons: [{
src: 'logo_150.png',
sizes: '150x150',
type: 'image/png',
}],
},
registerType: 'autoUpdate',
workbox: {
// 缓存相关静态资源
globPatterns: ['**/*.{js,css,html,ico,png,jpg,svg}'],
// 配置自定义运行时缓存
runtimeCaching: [
isProductionMode()
? {
urlPattern: ({ url }) => url.origin === 'https://app-api.id',
handler: 'NetworkFirst',
options: {
cacheName: 'wisbayar-api',
cacheableResponse: {
statuses: [200]
}
}
} : {
urlPattern: ({ url }) => url.origin === 'https://app-api-0.com',
handler: 'NetworkFirst',
options: {
cacheName: 'wisbayar-api',
cacheableResponse: {
statuses: [200]
}
}
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
handler: 'CacheFirst',
options: {
cacheName: 'wisbayar-images',
expiration: {
// 最多30个图
maxEntries: 30
}
}
},
{
urlPattern: /.*\.js.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'wisbayar-js',
// 最多缓存30个, 超过的按照LRU原则删除
expiration: {
maxEntries: 30,
maxAgeSeconds: 7 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [200]
}
}
},
{
urlPattern: /.*\.css.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'wisbayar-css',
expiration: {
maxEntries: 20,
maxAgeSeconds: 7 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [200]
}
}
},
{
urlPattern: /.*\.html.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'wisbayar-html',
expiration: {
maxEntries: 20,
maxAgeSeconds: 7 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [200]
}
}
}
]
},
devOptions: {
enabled: true,
},
};
};

View File

@@ -1,8 +1,15 @@
export default {};
/**
* 是否生成打包报告
*/
export default {};
export function isReportMode(): boolean {
return process.env.REPORT === 'true';
}
/**
* 是否为生产模式
*/
export function isProductionMode(): boolean {
return process.env.NODE_ENV === 'production';
}

View File

@@ -4,6 +4,7 @@ import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import svgLoader from 'vite-svg-loader';
import configArcoStyleImportPlugin from './plugin/arcoStyleImport';
import configPwaPlugin from './plugin/pwa';
export default defineConfig({
plugins: [
@@ -11,6 +12,7 @@ export default defineConfig({
vueJsx(),
svgLoader({ svgoConfig: {} }),
configArcoStyleImportPlugin(),
configPwaPlugin(),
],
resolve: {
alias: [

View File

@@ -19,5 +19,5 @@ export default mergeConfig(
}),
],
},
baseConfig
baseConfig,
);

View File

@@ -27,5 +27,5 @@ export default mergeConfig(
chunkSizeWarningLimit: 2000,
},
},
baseConfig
baseConfig,
);

View File

@@ -1,5 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local

View File

@@ -103,6 +103,7 @@
"vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-imagemin": "^0.6.1",
"vite-plugin-pwa": "^0.20.0",
"vite-svg-loader": "^3.6.0",
"vue-tsc": "^1.0.14"
},

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -8,7 +8,7 @@
<a-link target="_blank" href="https://gitee.com/lijiahangmax/orion-visor">gitee</a-link>
<a-link target="_blank" href="https://lijiahangmax.github.io/orion-visor">文档</a-link>
<a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor/blob/main/LICENSE">License</a-link>
<a-link target="_blank" :href="`https://github.com/lijiahangmax/orion-visor/releases/tag/v${version}`">V{{ version }} 社区版</a-link>
<a-link target="_blank" :href="`https://github.com/lijiahangmax/orion-visor/releases/tag/v${version}`">v{{ version }} Community</a-link>
</a-space>
<span class="copyright">
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} Li Jiahang, All rights reserved.

View File

@@ -21,6 +21,14 @@
<block :options="dataOpts" title="数据设置" />
<!-- 页面视图 -->
<block :options="viewsOpts" title="页面视图" />
<!-- 保存为桌面程序 -->
<a-button v-if="visibleCreatePwaApp()"
class="mb16"
type="primary"
@click="createPwaApp"
long>
保存为桌面程序
</a-button>
</div>
</a-drawer>
</template>
@@ -30,6 +38,8 @@
import { useAppStore } from '@/store';
import useVisible from '@/hooks/visible';
import { CardPageSizeOptions, TablePageSizeOptions } from '@/types/const';
import { Message } from '@arco-design/web-vue';
import { isStandaloneMode } from '@/utils/env';
import Block from './block.vue';
const appStore = useAppStore();
@@ -39,6 +49,7 @@
const open = () => {
setVisible(true);
};
defineExpose({ open });
// 布局设置
@@ -110,7 +121,6 @@
},
]);
// 页面视图配置
const viewsOpts = computed(() => [
{
@@ -142,6 +152,26 @@
},
]);
// 是否展示创建 PWA 应用
const visibleCreatePwaApp = () => {
return !isStandaloneMode && !!(window as CustomWindow).deferredPrompt;
};
// 创建 PWA 应用
const createPwaApp = () => {
const win = window as CustomWindow;
try {
win.deferredPrompt.prompt();
win.deferredPrompt.userChoice.then((choiceResult: any) => {
if (choiceResult.outcome === 'accepted') {
win.deferredPrompt = null;
}
});
} catch (e) {
Message.error('无法安装 PWA 应用');
}
};
</script>
<style lang="less" scoped>

View File

@@ -7,6 +7,14 @@ declare module '*.vue' {
export default component;
}
// window
interface CustomWindow extends Window {
deferredPrompt?: any;
}
declare const window: CustomWindow;
// .env
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
readonly VITE_WS_BASE_URL: string;

View File

@@ -7,12 +7,12 @@ import store from './store';
import i18n from './locale';
import directive from './directive';
import './mock';
import App from './App.vue';
// 样式通过 arco-plugin 插件导入 详见目录文件 config/plugin/arcoStyleImport.ts
import '@/assets/style/global.less';
import '@/assets/style/layout.less';
import '@/assets/style/arco-extends.less';
import '@/api/interceptor';
import App from './App.vue';
const app = createApp(App);
@@ -26,3 +26,9 @@ app.use(globalComponents);
app.use(directive);
app.mount('#app');
// 监听 PWA 注册事件
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
(window as CustomWindow).deferredPrompt = e;
});

View File

@@ -5,6 +5,7 @@ import { appRoutes } from './routes';
import BASE_ROUTERS from './routes/base';
import createRouteGuard from './guard';
import { openWindow } from '@/utils';
import { isStandaloneMode } from '@/utils/env';
import 'nprogress/nprogress.css';
NProgress.configure({ showSpinner: false });
@@ -27,7 +28,13 @@ createRouteGuard(router);
// 新页面打开路由
export const openNewRoute = (route: RouteLocationRaw) => {
const { href } = router.resolve(route);
openWindow(href);
if (isStandaloneMode) {
// 单应用 PWA 则跳转
window.location.href = href;
} else {
// 浏览器 则直接打开
openWindow(href);
}
};
export default router;

View File

@@ -5,6 +5,13 @@ export const isSecureEnvironment = (() => {
return window.location.protocol === 'https:' || window.location.hostname === 'localhost';
})();
// 当前是否为单应用模式 PWA
export const isStandaloneMode = (() => (
(window.matchMedia('(display-mode: standalone)').matches)
|| ((window.navigator as any).standalone)
|| document.referrer.includes('android-app://')
) === true)();
// http base url
export const httpBaseUrl = (() => {
const configBase = import.meta.env.VITE_API_BASE_URL;