✨ chrome PWA 支持.
This commit is contained in:
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
### v2.0.2
|
### v2.0.2
|
||||||
|
|
||||||
|
* ⭐ 添加预览模式
|
||||||
|
* ⭐ 支持 Chrome PWA
|
||||||
|
|
||||||
`2024-05-2` `release`
|
`2024-05-2` `release`
|
||||||
|
|
||||||
### v2.0.1
|
### v2.0.1
|
||||||
|
|||||||
1
orion-visor-ui/.gitignore
vendored
1
orion-visor-ui/.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
dist
|
||||||
|
dev-dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|||||||
131
orion-visor-ui/config/plugin/pwa.ts
Normal file
131
orion-visor-ui/config/plugin/pwa.ts
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
|
export default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否生成打包报告
|
* 是否生成打包报告
|
||||||
*/
|
*/
|
||||||
export default {};
|
|
||||||
|
|
||||||
export function isReportMode(): boolean {
|
export function isReportMode(): boolean {
|
||||||
return process.env.REPORT === 'true';
|
return process.env.REPORT === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为生产模式
|
||||||
|
*/
|
||||||
|
export function isProductionMode(): boolean {
|
||||||
|
return process.env.NODE_ENV === 'production';
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import vue from '@vitejs/plugin-vue';
|
|||||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
import svgLoader from 'vite-svg-loader';
|
import svgLoader from 'vite-svg-loader';
|
||||||
import configArcoStyleImportPlugin from './plugin/arcoStyleImport';
|
import configArcoStyleImportPlugin from './plugin/arcoStyleImport';
|
||||||
|
import configPwaPlugin from './plugin/pwa';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -11,6 +12,7 @@ export default defineConfig({
|
|||||||
vueJsx(),
|
vueJsx(),
|
||||||
svgLoader({ svgoConfig: {} }),
|
svgLoader({ svgoConfig: {} }),
|
||||||
configArcoStyleImportPlugin(),
|
configArcoStyleImportPlugin(),
|
||||||
|
configPwaPlugin(),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: [
|
alias: [
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ export default mergeConfig(
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
baseConfig
|
baseConfig,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,5 +27,5 @@ export default mergeConfig(
|
|||||||
chunkSizeWarningLimit: 2000,
|
chunkSizeWarningLimit: 2000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConfig
|
baseConfig,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
node_modules
|
|
||||||
.DS_Store
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
@@ -103,6 +103,7 @@
|
|||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-imagemin": "^0.6.1",
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
"vite-svg-loader": "^3.6.0",
|
"vite-svg-loader": "^3.6.0",
|
||||||
"vue-tsc": "^1.0.14"
|
"vue-tsc": "^1.0.14"
|
||||||
},
|
},
|
||||||
|
|||||||
1788
orion-visor-ui/pnpm-lock.yaml
generated
1788
orion-visor-ui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -8,7 +8,7 @@
|
|||||||
<a-link target="_blank" href="https://gitee.com/lijiahangmax/orion-visor">gitee</a-link>
|
<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://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/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>
|
</a-space>
|
||||||
<span class="copyright">
|
<span class="copyright">
|
||||||
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} Li Jiahang, All rights reserved.
|
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} Li Jiahang, All rights reserved.
|
||||||
|
|||||||
@@ -21,6 +21,14 @@
|
|||||||
<block :options="dataOpts" title="数据设置" />
|
<block :options="dataOpts" title="数据设置" />
|
||||||
<!-- 页面视图 -->
|
<!-- 页面视图 -->
|
||||||
<block :options="viewsOpts" title="页面视图" />
|
<block :options="viewsOpts" title="页面视图" />
|
||||||
|
<!-- 保存为桌面程序 -->
|
||||||
|
<a-button v-if="visibleCreatePwaApp()"
|
||||||
|
class="mb16"
|
||||||
|
type="primary"
|
||||||
|
@click="createPwaApp"
|
||||||
|
long>
|
||||||
|
保存为桌面程序
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
</template>
|
</template>
|
||||||
@@ -30,6 +38,8 @@
|
|||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
import useVisible from '@/hooks/visible';
|
import useVisible from '@/hooks/visible';
|
||||||
import { CardPageSizeOptions, TablePageSizeOptions } from '@/types/const';
|
import { CardPageSizeOptions, TablePageSizeOptions } from '@/types/const';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { isStandaloneMode } from '@/utils/env';
|
||||||
import Block from './block.vue';
|
import Block from './block.vue';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
@@ -39,6 +49,7 @@
|
|||||||
const open = () => {
|
const open = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
|
|
||||||
// 布局设置
|
// 布局设置
|
||||||
@@ -110,7 +121,6 @@
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
// 页面视图配置
|
// 页面视图配置
|
||||||
const viewsOpts = computed(() => [
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
8
orion-visor-ui/src/env.d.ts
vendored
8
orion-visor-ui/src/env.d.ts
vendored
@@ -7,6 +7,14 @@ declare module '*.vue' {
|
|||||||
export default component;
|
export default component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// window
|
||||||
|
interface CustomWindow extends Window {
|
||||||
|
deferredPrompt?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const window: CustomWindow;
|
||||||
|
|
||||||
|
// .env
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_BASE_URL: string;
|
readonly VITE_API_BASE_URL: string;
|
||||||
readonly VITE_WS_BASE_URL: string;
|
readonly VITE_WS_BASE_URL: string;
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import store from './store';
|
|||||||
import i18n from './locale';
|
import i18n from './locale';
|
||||||
import directive from './directive';
|
import directive from './directive';
|
||||||
import './mock';
|
import './mock';
|
||||||
import App from './App.vue';
|
|
||||||
// 样式通过 arco-plugin 插件导入 详见目录文件 config/plugin/arcoStyleImport.ts
|
// 样式通过 arco-plugin 插件导入 详见目录文件 config/plugin/arcoStyleImport.ts
|
||||||
import '@/assets/style/global.less';
|
import '@/assets/style/global.less';
|
||||||
import '@/assets/style/layout.less';
|
import '@/assets/style/layout.less';
|
||||||
import '@/assets/style/arco-extends.less';
|
import '@/assets/style/arco-extends.less';
|
||||||
import '@/api/interceptor';
|
import '@/api/interceptor';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
@@ -26,3 +26,9 @@ app.use(globalComponents);
|
|||||||
app.use(directive);
|
app.use(directive);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
||||||
|
// 监听 PWA 注册事件
|
||||||
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
(window as CustomWindow).deferredPrompt = e;
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { appRoutes } from './routes';
|
|||||||
import BASE_ROUTERS from './routes/base';
|
import BASE_ROUTERS from './routes/base';
|
||||||
import createRouteGuard from './guard';
|
import createRouteGuard from './guard';
|
||||||
import { openWindow } from '@/utils';
|
import { openWindow } from '@/utils';
|
||||||
|
import { isStandaloneMode } from '@/utils/env';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
NProgress.configure({ showSpinner: false });
|
NProgress.configure({ showSpinner: false });
|
||||||
@@ -27,7 +28,13 @@ createRouteGuard(router);
|
|||||||
// 新页面打开路由
|
// 新页面打开路由
|
||||||
export const openNewRoute = (route: RouteLocationRaw) => {
|
export const openNewRoute = (route: RouteLocationRaw) => {
|
||||||
const { href } = router.resolve(route);
|
const { href } = router.resolve(route);
|
||||||
openWindow(href);
|
if (isStandaloneMode) {
|
||||||
|
// 单应用 PWA 则跳转
|
||||||
|
window.location.href = href;
|
||||||
|
} else {
|
||||||
|
// 浏览器 则直接打开
|
||||||
|
openWindow(href);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ export const isSecureEnvironment = (() => {
|
|||||||
return window.location.protocol === 'https:' || window.location.hostname === 'localhost';
|
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
|
// http base url
|
||||||
export const httpBaseUrl = (() => {
|
export const httpBaseUrl = (() => {
|
||||||
const configBase = import.meta.env.VITE_API_BASE_URL;
|
const configBase = import.meta.env.VITE_API_BASE_URL;
|
||||||
|
|||||||
Reference in New Issue
Block a user