feat: support pwa
parent
222c73963d
commit
382652e0f4
|
@ -7,6 +7,7 @@ coverage
|
|||
**/.vitepress/cache
|
||||
.cache
|
||||
.turbo
|
||||
dev-dist
|
||||
.stylelintcache
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="hello" />
|
||||
</template>
|
|
@ -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: {
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -10,8 +10,8 @@ import { type PluginOption } from 'vite';
|
|||
* 为多app提供loading样式,无需在每个 app -> index.html单独引入
|
||||
*/
|
||||
async function viteInjectAppLoadingPlugin(
|
||||
isBuild: string,
|
||||
env: Record<string, any>,
|
||||
isBuild: boolean,
|
||||
env: Record<string, any> = {},
|
||||
): Promise<PluginOption | undefined> {
|
||||
const loadingHtml = await getLoadingRawByHtmlTemplate();
|
||||
const envRaw = isBuild ? 'prod' : 'dev';
|
||||
|
|
|
@ -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<PwaPluginOptions>;
|
||||
/** turbo-console 插件配置 */
|
||||
turboConsole?: Parameters<typeof viteTurboConsolePlugin>[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 = {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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%;
|
||||
|
|
@ -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%;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import './default/index.scss';
|
||||
import './dark/index.scss';
|
||||
import './default/index.css';
|
||||
import './dark/index.css';
|
||||
|
||||
export {};
|
||||
|
|
|
@ -296,43 +296,3 @@ function handleMouseleave() {
|
|||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// @include b('sidebar') {
|
||||
// --color-surface: var(--color-menu);
|
||||
|
||||
// @include is('dark') {
|
||||
// --color-surface: var(--color-menu-dark);
|
||||
// }
|
||||
|
||||
// @include e('shadow') {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// z-index: 1;
|
||||
// inline-size: 100%;
|
||||
// block-size: 40px;
|
||||
// height: 50px;
|
||||
// pointer-events: none;
|
||||
// background: linear-gradient(
|
||||
// to bottom,
|
||||
// hsl(var(--color-surface)),
|
||||
// transparent
|
||||
// );
|
||||
// opacity: 0;
|
||||
// transition: opacity 0.15s ease-in-out;
|
||||
// will-change: opacity;
|
||||
|
||||
// &.scrolled {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
// @include is('dark') {
|
||||
// .#{$namespace}-side__extra {
|
||||
// &-content {
|
||||
// border-color: hsl(var(--color-dark-border)) !important;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
|
|
@ -68,7 +68,7 @@ function handlerSubmit() {
|
|||
<SheetContent :style="contentStyle" class="!w-full pb-12 sm:rounded-l-lg">
|
||||
<SheetHeader
|
||||
:class="description ? 'h-16' : 'h-12'"
|
||||
class="border-border flex flex-row items-center justify-between border-b pl-5 pr-3"
|
||||
class="border-border flex flex-row items-center justify-between border-b pl-3 pr-3"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div>
|
||||
|
|
|
@ -49,6 +49,9 @@ const titleText = computed(() => {
|
|||
case 'offline': {
|
||||
return $t('fallback.offline-error');
|
||||
}
|
||||
case 'hello': {
|
||||
return $t('fallback.coming-soon');
|
||||
}
|
||||
default: {
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="z-100 fixed right-0 top-1/3">
|
||||
<div class="z-100 fixed right-0 top-2/3">
|
||||
<VbenSheet
|
||||
v-model:open="openPreferences"
|
||||
:description="$t('preferences.preferences-subtitle')"
|
||||
|
@ -194,8 +195,8 @@ function handleReset() {
|
|||
</VbenIconButton>
|
||||
</template>
|
||||
|
||||
<div class="p-5 pt-4">
|
||||
<VbenSegmented :tabs="tabs" default-value="appearance">
|
||||
<div class="p-4 pt-4">
|
||||
<VbenSegmented :tabs="tabs" default-value="general">
|
||||
<template #appearance>
|
||||
<Block :title="$t('preferences.theme')">
|
||||
<Theme
|
||||
|
|
|
@ -12,8 +12,8 @@ defineOptions({
|
|||
<template>
|
||||
<VbenButton
|
||||
:title="$t('preferences.preferences')"
|
||||
class="bg-primary flex-col-center h-9 w-9 cursor-pointer rounded-l-md rounded-r-none border-none"
|
||||
class="bg-primary flex-col-center h-12 w-12 cursor-pointer rounded-l-lg rounded-r-none border-none"
|
||||
>
|
||||
<IconSetting class="text-lg" />
|
||||
<IconSetting class="duration-3000 animate-spin text-2xl" />
|
||||
</VbenButton>
|
||||
</template>
|
||||
|
|
|
@ -67,7 +67,6 @@ function showSpinning(index: number) {
|
|||
</script>
|
||||
<template>
|
||||
<template v-if="showIframe">
|
||||
{{ iframeRoutes.length }}
|
||||
<template v-for="(item, index) in iframeRoutes" :key="item.fullPath">
|
||||
<div
|
||||
v-if="canRender(item)"
|
||||
|
|
|
@ -7,5 +7,5 @@ const VBEN_GITHUB_URL = 'https://github.com/vbenjs/vue-vben-admin';
|
|||
* @zh_CN Vben Logo
|
||||
*/
|
||||
const VBEN_LOGO =
|
||||
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.0/source/logo-v1.webp';
|
||||
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.1/source/logo-v1.webp';
|
||||
export { VBEN_GITHUB_URL, VBEN_LOGO };
|
||||
|
|
|
@ -25,6 +25,7 @@ fallback:
|
|||
offline: Offline Page
|
||||
offline-error: Oops! Network Error
|
||||
offline-error-desc: Sorry, can't connect to the internet. Check your connection.
|
||||
coming-soon: Coming soon
|
||||
|
||||
widgets:
|
||||
document: Document
|
||||
|
|
|
@ -24,6 +24,7 @@ fallback:
|
|||
offline: 离线页面
|
||||
offline-error: 哎呀!网络错误
|
||||
offline-error-desc: 抱歉,无法连接到互联网,请检查您的网络连接并重试。
|
||||
coming-soon: 即将推出
|
||||
|
||||
widgets:
|
||||
document: 文档
|
||||
|
|
1683
pnpm-lock.yaml
1683
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue