From 382652e0f40fcbeea7d3ed83ada12d3015726901 Mon Sep 17 00:00:00 2001 From: vben Date: Sun, 16 Jun 2024 15:45:15 +0800 Subject: [PATCH] feat: support pwa --- .gitignore | 1 + .../views/_essential/fallback/coming-soon.vue | 7 + apps/web-antd/vite.config.mts | 59 +- internal/tailwind-config/src/index.ts | 4 + internal/vite-config/package.json | 1 + .../vite-config/src/config/application.ts | 13 +- internal/vite-config/src/config/library.ts | 10 +- internal/vite-config/src/plugins/index.ts | 22 +- .../src/plugins/inject-app-loading/index.ts | 4 +- internal/vite-config/src/typing.ts | 17 +- .../forward/preferences/src/preferences.ts | 6 +- .../src/dark/{index.scss => index.css} | 5 - .../src/default/{index.scss => index.css} | 10 - .../@core/shared/design-tokens/src/index.ts | 4 +- .../src/components/layout-sidebar.vue | 40 - .../shadcn-ui/src/components/sheet/sheet.vue | 2 +- .../common-ui/src/fallback/fallback.vue | 3 + .../common-ui/src/preferences/preferences.vue | 15 +- .../common-ui/src/preferences/trigger.vue | 4 +- .../layouts/src/iframe/iframe-router-view.vue | 1 - packages/constants/src/vben.ts | 2 +- packages/locales/src/langs/en-US.yaml | 1 + packages/locales/src/langs/zh-CN.yaml | 1 + pnpm-lock.yaml | 1683 ++++++++++++++++- 24 files changed, 1787 insertions(+), 128 deletions(-) create mode 100644 apps/web-antd/src/views/_essential/fallback/coming-soon.vue rename packages/@core/shared/design-tokens/src/dark/{index.scss => index.css} (92%) rename packages/@core/shared/design-tokens/src/default/{index.scss => index.css} (86%) diff --git a/.gitignore b/.gitignore index 5c142273..29459d50 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ coverage **/.vitepress/cache .cache .turbo +dev-dist .stylelintcache yarn.lock package-lock.json diff --git a/apps/web-antd/src/views/_essential/fallback/coming-soon.vue b/apps/web-antd/src/views/_essential/fallback/coming-soon.vue new file mode 100644 index 00000000..f771cdab --- /dev/null +++ b/apps/web-antd/src/views/_essential/fallback/coming-soon.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/web-antd/vite.config.mts b/apps/web-antd/vite.config.mts index 39453cdd..511b29ae 100644 --- a/apps/web-antd/vite.config.mts +++ b/apps/web-antd/vite.config.mts @@ -1,24 +1,47 @@ import { defineConfig } from '@vben/vite-config'; export default defineConfig({ - application: { - compress: false, - compressTypes: ['brotli', 'gzip'], - importmap: false, - importmapOptions: { - // 通过 Importmap CDN 方式引入, - // 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高 - defaultProvider: 'esm.sh', - importmap: [ - { name: 'vue' }, - { name: 'pinia' }, - { name: 'vue-router' }, - { name: 'vue-i18n' }, - { name: 'dayjs' }, - { name: 'vue-demi' }, - ], - }, - visualizer: false, + application: ({ mode }) => { + return { + compress: false, + compressTypes: ['brotli', 'gzip'], + importmap: false, + importmapOptions: { + // 通过 Importmap CDN 方式引入, + // 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高 + defaultProvider: 'esm.sh', + importmap: [ + { name: 'vue' }, + { name: 'pinia' }, + { name: 'vue-router' }, + { name: 'vue-i18n' }, + { name: 'dayjs' }, + { name: 'vue-demi' }, + ], + }, + pwa: false, + pwaOptions: { + manifest: { + description: + 'Vben Admin Pro is a modern admin dashboard template based on Vue 3. ', + icons: [ + { + sizes: '192x192', + src: 'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.1/source/pwa-icon-192.png', + type: 'image/png', + }, + { + sizes: '512x512', + src: 'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.1/source/pwa-icon-512.png', + type: 'image/png', + }, + ], + name: `Vben Admin Pro ${mode}`, + short_name: `Vben Admin Pro ${mode}`, + }, + }, + visualizer: false, + }; }, vite: { server: { diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts index ef2ee70d..00a7259b 100644 --- a/internal/tailwind-config/src/index.ts +++ b/internal/tailwind-config/src/index.ts @@ -57,6 +57,10 @@ export default { 'collapsible-up': 'collapsible-up 0.2s ease-in-out', float: 'float 5s linear 0ms infinite', }, + animationDuration: { + '2000': '2000ms', + '3000': '3000ms', + }, borderRadius: { lg: 'var(--radius-base)', md: 'calc(var(--radius-base) - 2px)', diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json index 25d7e7b8..849e6f85 100644 --- a/internal/vite-config/package.json +++ b/internal/vite-config/package.json @@ -36,6 +36,7 @@ "html-minifier-terser": "^7.2.0", "resolve.exports": "^2.0.2", "vite-plugin-lib-inject-css": "^2.1.1", + "vite-plugin-pwa": "^0.20.0", "vite-plugin-vue-devtools": "^7.2.1" }, "devDependencies": { diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts index 52254196..ceb7bd52 100644 --- a/internal/vite-config/src/config/application.ts +++ b/internal/vite-config/src/config/application.ts @@ -10,7 +10,8 @@ import { getApplicationConditionPlugins } from '../plugins'; import { getCommonConfig } from './common'; function defineApplicationConfig(options: DefineApplicationOptions = {}) { - return defineConfig(async ({ command, mode }) => { + return defineConfig(async (config) => { + const { command, mode } = config; const { application = {}, vite = {} } = options; const root = process.cwd(); const isBuild = command === 'build'; @@ -28,8 +29,11 @@ function defineApplicationConfig(options: DefineApplicationOptions = {}) { isBuild, mock: true, mode, + pwa: true, turboConsole: false, - ...application, + ...(typeof application === 'function' + ? application(config) + : application), }); const applicationConfig: UserConfig = { @@ -91,7 +95,10 @@ function defineApplicationConfig(options: DefineApplicationOptions = {}) { await getCommonConfig(), applicationConfig, ); - return mergeConfig(mergedConfig, vite); + return mergeConfig( + mergedConfig, + typeof vite === 'function' ? vite(config) : vite, + ); }); } diff --git a/internal/vite-config/src/config/library.ts b/internal/vite-config/src/config/library.ts index fca61d14..f4144a1c 100644 --- a/internal/vite-config/src/config/library.ts +++ b/internal/vite-config/src/config/library.ts @@ -10,7 +10,8 @@ import { getLibraryConditionPlugins } from '../plugins'; import { getCommonConfig } from './common'; function defineLibraryConfig(options: DefineLibraryOptions = {}) { - return defineConfig(async ({ command, mode }) => { + return defineConfig(async (config) => { + const { command, mode } = config; const root = process.cwd(); const { library = {}, vite = {} } = options; const isBuild = command === 'build'; @@ -20,7 +21,7 @@ function defineLibraryConfig(options: DefineLibraryOptions = {}) { injectLibCss: true, isBuild, mode, - ...library, + ...(typeof library === 'function' ? library(config) : library), }); const { dependencies = {}, peerDependencies = {} } = @@ -45,7 +46,10 @@ function defineLibraryConfig(options: DefineLibraryOptions = {}) { }; const commonConfig = await getCommonConfig(); const mergedConfig = mergeConfig(commonConfig, packageConfig); - return mergeConfig(mergedConfig, vite); + return mergeConfig( + mergedConfig, + typeof vite === 'function' ? vite(config) : vite, + ); }); } diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts index 49495ae0..87787baf 100644 --- a/internal/vite-config/src/plugins/index.ts +++ b/internal/vite-config/src/plugins/index.ts @@ -21,6 +21,7 @@ import viteDtsPlugin from 'vite-plugin-dts'; import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html'; import { libInjectCss as viteLibInjectCss } from 'vite-plugin-lib-inject-css'; import { viteMockServe as viteMockPlugin } from 'vite-plugin-mock'; +import { VitePWA } from 'vite-plugin-pwa'; import viteVueDevTools from 'vite-plugin-vue-devtools'; import { viteExtraAppConfigPlugin } from './extra-app-config'; @@ -100,6 +101,8 @@ async function getApplicationConditionPlugins( importmapOptions, injectAppLoading, mock, + pwa, + pwaOptions, turboConsole, ...commonOptions } = options; @@ -125,7 +128,24 @@ async function getApplicationConditionPlugins( }, { condition: injectAppLoading, - plugins: async () => [await viteInjectAppLoadingPlugin(isBuild, env)], + plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)], + }, + { + condition: pwa, + plugins: () => + VitePWA({ + injectRegister: false, + workbox: { + globPatterns: [], + }, + ...pwaOptions, + manifest: { + display: 'standalone', + start_url: '/', + theme_color: '#ffffff', + ...pwaOptions?.manifest, + }, + }), }, { condition: isBuild && !!compress, diff --git a/internal/vite-config/src/plugins/inject-app-loading/index.ts b/internal/vite-config/src/plugins/inject-app-loading/index.ts index 7aedc3e9..731caeac 100644 --- a/internal/vite-config/src/plugins/inject-app-loading/index.ts +++ b/internal/vite-config/src/plugins/inject-app-loading/index.ts @@ -10,8 +10,8 @@ import { type PluginOption } from 'vite'; * 为多app提供loading样式,无需在每个 app -> index.html单独引入 */ async function viteInjectAppLoadingPlugin( - isBuild: string, - env: Record, + isBuild: boolean, + env: Record = {}, ): Promise { const loadingHtml = await getLoadingRawByHtmlTemplate(); const envRaw = isBuild ? 'prod' : 'dev'; diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts index a37e5237..56c466c9 100644 --- a/internal/vite-config/src/typing.ts +++ b/internal/vite-config/src/typing.ts @@ -1,6 +1,7 @@ import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'; -import type { PluginOption, UserConfig } from 'vite'; +import type { ConfigEnv, PluginOption, UserConfig } from 'vite'; import type { PluginOptions } from 'vite-plugin-dts'; +import type { Options as PwaPluginOptions } from 'vite-plugin-pwa'; import viteTurboConsolePlugin from 'unplugin-turbo-console/vite'; @@ -68,6 +69,10 @@ interface ApplicationPluginOptions extends CommonPluginOptions { injectAppLoading?: boolean; /** mock 插件配置 */ mock?: boolean; + /** 是否开启pwa */ + pwa?: boolean; + /** pwa 插件配置 */ + pwaOptions?: Partial; /** turbo-console 插件配置 */ turboConsole?: Parameters[0] | boolean; } @@ -85,13 +90,15 @@ interface ApplicationOptions extends ApplicationPluginOptions {} interface LibraryOptions extends LibraryPluginOptions {} interface DefineApplicationOptions { - application?: ApplicationOptions; - vite?: UserConfig; + application?: + | ((config: ConfigEnv) => ApplicationOptions) + | ApplicationOptions; + vite?: ((config: ConfigEnv) => UserConfig) | UserConfig; } interface DefineLibraryOptions { - library?: LibraryOptions; - vite?: UserConfig; + library?: ((config: ConfigEnv) => LibraryOptions) | LibraryOptions; + vite?: ((config: ConfigEnv) => UserConfig) | UserConfig; } type DefineConfig = { diff --git a/packages/@core/forward/preferences/src/preferences.ts b/packages/@core/forward/preferences/src/preferences.ts index 5baf1532..2085b495 100644 --- a/packages/@core/forward/preferences/src/preferences.ts +++ b/packages/@core/forward/preferences/src/preferences.ts @@ -245,7 +245,11 @@ class PreferenceManager { this.initialPreferences = merge({}, overrides, defaultPreferences); // 加载并合并当前存储的偏好设置 - const mergedPreference = merge({}, this.loadCachedPreferences(), overrides); + const mergedPreference = merge( + {}, + this.loadCachedPreferences(), + this.initialPreferences, + ); // 更新偏好设置 this.updatePreferences(mergedPreference); diff --git a/packages/@core/shared/design-tokens/src/dark/index.scss b/packages/@core/shared/design-tokens/src/dark/index.css similarity index 92% rename from packages/@core/shared/design-tokens/src/dark/index.scss rename to packages/@core/shared/design-tokens/src/dark/index.css index a7627eb6..9cb97cef 100644 --- a/packages/@core/shared/design-tokens/src/dark/index.scss +++ b/packages/@core/shared/design-tokens/src/dark/index.css @@ -1,13 +1,8 @@ :root.dark { /* 基础背景颜色颜色 */ - /* --color-background: 240 6% 18%; */ - // --color-body: 220deg 13.04% 8%; - // --color-body: hsl(240deg 11% 4%); --color-background: 220deg 13.04% 8%; - /* --color-background: 219 42% 11%; */ - /* 基础文本颜色 */ --color-foreground: 220 13% 91%; diff --git a/packages/@core/shared/design-tokens/src/default/index.scss b/packages/@core/shared/design-tokens/src/default/index.css similarity index 86% rename from packages/@core/shared/design-tokens/src/default/index.scss rename to packages/@core/shared/design-tokens/src/default/index.css index 588c6b14..87d0c2c5 100644 --- a/packages/@core/shared/design-tokens/src/default/index.scss +++ b/packages/@core/shared/design-tokens/src/default/index.css @@ -1,13 +1,6 @@ /* https://gavin-yyc.github.io/colorconvert/ */ :root { - /* 基础背景颜色颜色 */ - - /* --color-background: 210deg 25% 96.86%; */ - // --color-main: 210deg 25% 96.86%; --color-background: 0 0 100%; - // --color-darken-background: 220deg 13.04% 8%; - - /* --color-background: 220 14% 95%; */ /* 基础文本颜色 */ --color-foreground: 210 6% 21%; @@ -85,12 +78,9 @@ /* menu */ --color-menu-dark: 225deg 12% 13%; --color-menu-dark-darken: 223deg 11% 10%; - // --color-menu-darken: var(--color-background); - // --color-menu-opened-dark: 225deg 12.12% 11%; --color-menu: 0deg 0% 100%; --color-menu-darken: 0deg 0% 95%; accent-color: var(--color-primary); color-scheme: light; - // --color-menu-opened: 0deg 0% 100%; } diff --git a/packages/@core/shared/design-tokens/src/index.ts b/packages/@core/shared/design-tokens/src/index.ts index e19aea0e..9d27b4e8 100644 --- a/packages/@core/shared/design-tokens/src/index.ts +++ b/packages/@core/shared/design-tokens/src/index.ts @@ -1,4 +1,4 @@ -import './default/index.scss'; -import './dark/index.scss'; +import './default/index.css'; +import './dark/index.css'; export {}; diff --git a/packages/@core/uikit/layout-ui/src/components/layout-sidebar.vue b/packages/@core/uikit/layout-ui/src/components/layout-sidebar.vue index a5c8cb7b..815b58b1 100644 --- a/packages/@core/uikit/layout-ui/src/components/layout-sidebar.vue +++ b/packages/@core/uikit/layout-ui/src/components/layout-sidebar.vue @@ -296,43 +296,3 @@ function handleMouseleave() { - - diff --git a/packages/@core/uikit/shadcn-ui/src/components/sheet/sheet.vue b/packages/@core/uikit/shadcn-ui/src/components/sheet/sheet.vue index 14377f81..d1e0567d 100644 --- a/packages/@core/uikit/shadcn-ui/src/components/sheet/sheet.vue +++ b/packages/@core/uikit/shadcn-ui/src/components/sheet/sheet.vue @@ -68,7 +68,7 @@ function handlerSubmit() {
diff --git a/packages/business/common-ui/src/fallback/fallback.vue b/packages/business/common-ui/src/fallback/fallback.vue index ee79f797..6dba4758 100644 --- a/packages/business/common-ui/src/fallback/fallback.vue +++ b/packages/business/common-ui/src/fallback/fallback.vue @@ -49,6 +49,9 @@ const titleText = computed(() => { case 'offline': { return $t('fallback.offline-error'); } + case 'hello': { + return $t('fallback.coming-soon'); + } default: { return ''; } diff --git a/packages/business/common-ui/src/preferences/preferences.vue b/packages/business/common-ui/src/preferences/preferences.vue index f9962315..a6dbc52b 100644 --- a/packages/business/common-ui/src/preferences/preferences.vue +++ b/packages/business/common-ui/src/preferences/preferences.vue @@ -125,6 +125,10 @@ const { copy } = useClipboard(); const tabs = computed((): SegmentedItem[] => { return [ + { + label: $t('preferences.general'), + value: 'general', + }, { label: $t('preferences.appearance'), value: 'appearance', @@ -133,10 +137,7 @@ const tabs = computed((): SegmentedItem[] => { label: $t('preferences.layout'), value: 'layout', }, - { - label: $t('preferences.general'), - value: 'general', - }, + { label: $t('preferences.shortcut-keys.title'), value: 'shortcutKey', @@ -171,7 +172,7 @@ function handleReset() { -
- +
+ diff --git a/packages/business/layouts/src/iframe/iframe-router-view.vue b/packages/business/layouts/src/iframe/iframe-router-view.vue index 572befa6..2adb2e06 100644 --- a/packages/business/layouts/src/iframe/iframe-router-view.vue +++ b/packages/business/layouts/src/iframe/iframe-router-view.vue @@ -67,7 +67,6 @@ function showSpinning(index: number) {