feat: Regular monitoring page update [deploy]

pull/48/MERGE
vben 2024-07-29 22:11:22 +08:00
parent 66fd052709
commit cd10eb9471
37 changed files with 491 additions and 261 deletions

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ coverage
**/.vitepress/cache
.cache
.turbo
.temp
dev-dist
.stylelintcache
yarn.lock

View File

@ -2,3 +2,4 @@ dist
public
__tests__
coverage
ui-kit

View File

@ -7,7 +7,7 @@ VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
VITE_COMPRESS=none
# 是否开启 PWA
VITE_PWA=false
VITE_PWA=true
# vue-router 的模式
VITE_ROUTER_HISTORY=hash

View File

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { GlobalProvider } from '@vben/common-ui';
import { useDesignTokens } from '@vben/hooks';
import { preferences, usePreferences } from '@vben/preferences';
@ -26,17 +25,15 @@ const tokenTheme = computed(() => {
return {
algorithm,
token: antDesignTokens.value,
token: antDesignTokens,
};
});
</script>
<template>
<GlobalProvider>
<ConfigProvider :locale="antdLocale" :theme="tokenTheme">
<App>
<RouterView />
</App>
</ConfigProvider>
</GlobalProvider>
<ConfigProvider :locale="antdLocale" :theme="tokenTheme">
<App>
<RouterView />
</App>
</ConfigProvider>
</template>

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { LOGIN_PATH } from '@vben/constants';
import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
import {
BasicLayout,
@ -57,7 +57,7 @@ const showDot = computed(() =>
const menus = computed(() => [
{
handler: () => {
openWindow('https://github.com/vbenjs/vue-vben-admin', {
openWindow(VBEN_DOC_URL, {
target: '_blank',
});
},
@ -66,7 +66,7 @@ const menus = computed(() => [
},
{
handler: () => {
openWindow('https://github.com/vbenjs/vue-vben-admin', {
openWindow(VBEN_GITHUB_URL, {
target: '_blank',
});
},
@ -75,7 +75,7 @@ const menus = computed(() => [
},
{
handler: () => {
openWindow('https://github.com/vbenjs/vue-vben-admin/issues', {
openWindow(`${VBEN_GITHUB_URL}/issues`, {
target: '_blank',
});
},

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { VBEN_GITHUB_URL, VBEN_LOGO_URL } from '@vben/constants';
import { VBEN_DOC_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL } from '@vben/constants';
import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales';
@ -35,7 +35,7 @@ const routes: RouteRecordRaw[] = [
component: IFrameView,
meta: {
icon: 'lucide:book-open-text',
iframeSrc: 'https://doc.vvbin.cn/',
iframeSrc: VBEN_DOC_URL,
keepAlive: true,
title: $t('page.vben.document'),
},

View File

@ -42,7 +42,8 @@
"ependencies",
"vite",
"echarts",
"sortablejs"
"sortablejs",
"etag"
],
"ignorePaths": [
"**/node_modules/**",

View File

@ -39,7 +39,7 @@
"eslint": "^9.8.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-jsdoc": "^48.8.3",
"eslint-plugin-jsdoc": "^48.9.2",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-n": "^17.10.1",
"eslint-plugin-no-only-tests": "^3.1.0",

View File

@ -43,11 +43,6 @@ const customConfig: Linter.FlatConfig[] = [
message:
'The #/stores package cannot be imported, please use the @core package itself',
},
{
group: ['#/forward/*'],
message:
'The #/forward package cannot be imported, please use the @core package itself',
},
],
},
],
@ -99,6 +94,9 @@ const customConfig: Linter.FlatConfig[] = [
'packages/icons/**/**',
'packages/constants/**/**',
'packages/styles/**/**',
'packages/stores/**/**',
'packages/preferences/**/**',
'packages/locales/**/**',
],
ignores: restrictedImportIgnores,
rules: {
@ -118,11 +116,12 @@ const customConfig: Linter.FlatConfig[] = [
},
// 后端模拟代码,不需要太多规则
{
files: ['apps/backend-mock/**/**'],
files: ['apps/backend-mock/**/**', 'website/**/**'],
rules: {
'@typescript-eslint/no-extraneous-class': 'off',
'n/no-extraneous-import': 'off',
'n/prefer-global/buffer': 'off',
'n/prefer-global/process': 'off',
'no-console': 'off',
'unicorn/prefer-module': 'off',
},

View File

@ -37,7 +37,7 @@
"postcss-html": "^1.7.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.3.3",
"stylelint": "^16.7.0",
"stylelint": "^16.8.0",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-recommended-scss": "^14.1.0",
"stylelint-config-recommended-vue": "^1.5.0",

View File

@ -46,7 +46,7 @@
"tailwindcss": "^3.4.3"
},
"dependencies": {
"@iconify/json": "^2.2.231",
"@iconify/json": "^2.2.232",
"@iconify/tailwind": "^1.1.2",
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
"@tailwindcss/typography": "^0.5.13",

View File

@ -13,7 +13,7 @@ import { loadApplicationPlugins } from '../plugins';
import { loadAndConvertEnv } from '../utils/env';
import { getCommonConfig } from './common';
function defineApplicationConfig(userConfigPromise: DefineApplicationOptions) {
function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
return defineConfig(async (config) => {
const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv();
const options = await userConfigPromise?.(config);

View File

@ -9,7 +9,7 @@ import { defineConfig, mergeConfig } from 'vite';
import { loadLibraryPlugins } from '../plugins';
import { getCommonConfig } from './common';
function defineLibraryConfig(userConfigPromise: DefineLibraryOptions) {
function defineLibraryConfig(userConfigPromise?: DefineLibraryOptions) {
return defineConfig(async (config: ConfigEnv) => {
const options = await userConfigPromise?.(config);
const { command, mode } = config;

View File

@ -20,7 +20,7 @@ const getDefaultPwaOptions = (name: string): Partial<PwaPluginOptions> => ({
type: 'image/png',
},
],
name: `${name}o${isDevelopment ? ' dev' : ''}`,
name: `${name}${isDevelopment ? ' dev' : ''}`,
short_name: `${name}${isDevelopment ? ' dev' : ''}`,
},
});

View File

@ -150,10 +150,6 @@ async function loadApplicationPlugins(
condition: pwa,
plugins: () =>
VitePWA({
devOptions: {
enabled: true,
type: 'module',
},
injectRegister: false,
workbox: {
globPatterns: [],

View File

@ -4,6 +4,7 @@ const defaultPreferences: Preferences = {
app: {
accessMode: 'frontend',
authPageLayout: 'panel-right',
checkUpdatesPollingTime: 1,
colorGrayMode: false,
colorWeakMode: false,
compact: false,
@ -11,6 +12,7 @@ const defaultPreferences: Preferences = {
defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.5/source/avatar-v1.webp',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
isMobile: false,
layout: 'sidebar-nav',
@ -27,7 +29,7 @@ const defaultPreferences: Preferences = {
styleType: 'normal',
},
copyright: {
companyName: 'Vben Admin',
companyName: 'Vben',
companySiteLink: 'https://www.vben.pro',
date: '2024',
enable: true,

View File

@ -21,6 +21,8 @@ interface AppPreferences {
accessMode: AccessModeType;
/** 登录注册页面布局 */
authPageLayout: AuthPageLayoutType;
/** 检查更新轮询时间 */
checkUpdatesPollingTime: number;
/** 是否开启灰色模式 */
colorGrayMode: boolean;
/** 是否开启色弱模式 */
@ -33,6 +35,8 @@ interface AppPreferences {
defaultAvatar: string;
// /** 开启动态标题 */
dynamicTitle: boolean;
/** 是否开启检查更新 */
enableCheckUpdates: boolean;
/** 是否显示偏好设置 */
enablePreferences: boolean;
/** 是否移动端 */

View File

@ -1,9 +0,0 @@
<script setup lang="ts">
import { Toaster } from '@vben-core/shadcn-ui';
defineOptions({ name: 'GlobalProvider' });
</script>
<template>
<Toaster />
<slot></slot>
</template>

View File

@ -1 +0,0 @@
export { default as GlobalProvider } from './global-provider.vue';

View File

@ -2,5 +2,4 @@ export * from './about';
export * from './authentication';
export * from './dashboard';
export * from './fallback';
export * from './global-provider';
export { useToast } from '@vben-core/shadcn-ui';

View File

@ -1,23 +1,25 @@
import { computed, ref, watch } from 'vue';
import { reactive, watch } from 'vue';
import { preferences } from '@vben/preferences';
export function useDesignTokens() {
const rootStyles = getComputedStyle(document.documentElement);
const colorPrimary = ref('');
const colorError = ref('');
const colorSuccess = ref('');
const colorWarning = ref('');
const colorInfo = ref('');
const colorBgBase = ref('');
const colorTextBase = ref('');
const colorBgContainer = ref('');
const colorBgElevated = ref('');
const colorBgLayout = ref('');
const colorBgMask = ref('');
const colorBorder = ref('');
const borderRadius = ref<any>('');
const antDesignTokens = reactive({
borderRadius: '' as any,
colorBgBase: '',
colorBgContainer: '',
colorBgElevated: '',
colorBgLayout: '',
colorBgMask: '',
colorBorder: '',
colorError: '',
colorInfo: '',
colorPrimary: '',
colorSuccess: '',
colorTextBase: '',
colorWarning: '',
});
const getCssVariableValue = (variable: string, isColor: boolean = true) => {
const value = rootStyles.getPropertyValue(variable);
@ -27,52 +29,23 @@ export function useDesignTokens() {
watch(
() => preferences.theme,
() => {
colorInfo.value = colorPrimary.value = getCssVariableValue('--primary');
colorError.value = getCssVariableValue('--destructive');
colorWarning.value = getCssVariableValue('--warning');
colorSuccess.value = getCssVariableValue('--success');
colorBgBase.value = getCssVariableValue('--background');
colorBgLayout.value = getCssVariableValue('--background-deep');
colorBgMask.value = getCssVariableValue('--overlay');
colorBorder.value = getCssVariableValue('--border');
colorTextBase.value = getCssVariableValue('--foreground');
colorBgElevated.value = getCssVariableValue('--popover');
colorBgContainer.value = getCssVariableValue('--card');
borderRadius.value = getCssVariableValue('--radius', false);
antDesignTokens.colorPrimary = getCssVariableValue('--primary');
antDesignTokens.colorError = getCssVariableValue('--destructive');
antDesignTokens.colorWarning = getCssVariableValue('--warning');
antDesignTokens.colorSuccess = getCssVariableValue('--success');
antDesignTokens.colorBgBase = getCssVariableValue('--background');
antDesignTokens.colorBgLayout = getCssVariableValue('--background-deep');
antDesignTokens.colorBgMask = getCssVariableValue('--overlay');
antDesignTokens.colorBorder = getCssVariableValue('--border');
antDesignTokens.colorTextBase = getCssVariableValue('--foreground');
antDesignTokens.colorBgElevated = getCssVariableValue('--popover');
antDesignTokens.colorBgContainer = getCssVariableValue('--card');
antDesignTokens.borderRadius = getCssVariableValue('--radius', false);
},
{ immediate: true },
);
const antDesignTokens = computed(() => {
return {
borderRadius: borderRadius.value,
colorBgBase: colorBgBase.value,
colorBgContainer: colorBgContainer.value,
colorBgElevated: colorBgElevated.value,
colorBgLayout: colorBgLayout.value,
colorBgMask: colorBgMask.value,
colorBorder: colorBorder.value,
colorError: colorError.value,
colorInfo: colorInfo.value,
colorPrimary: colorPrimary.value,
colorSuccess: colorSuccess.value,
colorTextBase: colorTextBase.value,
colorWarning: colorWarning.value,
};
});
return {
antDesignTokens,
borderRadius,
colorBgBase,
colorBgContainer,
colorBgElevated,
colorBorder,
colorError,
colorInfo,
colorPrimary,
colorSuccess,
colorTextBase,
colorWarning,
};
}

View File

@ -12,9 +12,9 @@ import { useCoreAccessStore, useCoreLockStore } from '@vben/stores';
import { MenuRecordRaw } from '@vben/types';
import { mapTree } from '@vben/utils';
import { VbenAdminLayout } from '@vben-core/layout-ui';
import { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
import { Toaster, VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
import { Breadcrumb, Preferences } from '../widgets';
import { Breadcrumb, CheckUpdates, Preferences } from '../widgets';
import { LayoutContent } from './content';
import { Copyright } from './copyright';
import { LayoutFooter } from './footer';
@ -310,6 +310,12 @@ watch(
<template #extra>
<slot name="extra"></slot>
<Toaster />
<CheckUpdates
v-if="preferences.app.enableCheckUpdates"
:polling-time="preferences.app.checkUpdatesPollingTime"
/>
<Transition v-if="preferences.widget.lockScreen" name="slide-up">
<slot v-if="coreLockStore.isLockScreen" name="lock-screen"></slot>
</Transition>

View File

@ -0,0 +1,126 @@
<script setup lang="ts">
import { h, onMounted, onUnmounted, ref } from 'vue';
import { $t } from '@vben/locales';
import { ToastAction, useToast } from '@vben-core/shadcn-ui';
interface Props {
//
pollingTime?: number;
}
defineOptions({ name: 'CheckUpdates' });
const props = withDefaults(defineProps<Props>(), {
pollingTime: 1,
});
const lastVersionTag = ref('');
let isCheckingUpdates = false;
const timer = ref<ReturnType<typeof setInterval>>();
const { toast } = useToast();
async function getVersionTag() {
try {
const response = await fetch('/', {
cache: 'no-cache',
method: 'HEAD',
});
return (
response.headers.get('etag') || response.headers.get('last-modified')
);
} catch {
console.error('Failed to fetch version tag');
return null;
}
}
async function checkForUpdates() {
const versionTag = await getVersionTag();
if (!versionTag) {
return;
}
//
if (!lastVersionTag.value) {
lastVersionTag.value = versionTag;
return;
}
if (lastVersionTag.value !== versionTag) {
lastVersionTag.value = versionTag;
clearInterval(timer.value);
handleNotice();
}
}
function handleNotice() {
const { dismiss } = toast({
action: h('div', [
h(
ToastAction,
{
altText: $t('common.cancel'),
onClick: () => dismiss(),
},
{
default: () => $t('common.cancel'),
},
),
h(
ToastAction,
{
altText: $t('common.refresh'),
class: 'bg-primary hover:bg-primary-hover mx-1',
onClick: () => {
window.location.reload();
},
},
{
default: () => $t('common.refresh'),
},
),
]),
description: $t('widgets.checkUpdatesDescription'),
duration: 0,
title: $t('widgets.checkUpdatesTitle'),
});
}
function start() {
// 5
timer.value = setInterval(checkForUpdates, props.pollingTime * 60 * 1000);
}
function handleVisibilitychange() {
if (document.hidden) {
stop();
} else {
if (!isCheckingUpdates) {
isCheckingUpdates = true;
checkForUpdates().finally(() => {
isCheckingUpdates = false;
start();
});
}
}
}
function stop() {
clearInterval(timer.value);
}
onMounted(() => {
start();
document.addEventListener('visibilitychange', handleVisibilitychange);
});
onUnmounted(() => {
stop();
document.removeEventListener('visibilitychange', handleVisibilitychange);
});
</script>
<template>
<slot></slot>
</template>

View File

@ -0,0 +1 @@
export { default as CheckUpdates } from './check-updates.vue';

View File

@ -1,4 +1,5 @@
export { default as Breadcrumb } from './breadcrumb.vue';
export * from './check-updates';
export { default as AuthenticationColorToggle } from './color-toggle.vue';
export * from './global-search';
export { default as LanguageToggle } from './language-toggle.vue';

View File

@ -12,6 +12,7 @@ defineOptions({
const appLocale = defineModel<string>('appLocale');
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
const appWatermark = defineModel<boolean>('appWatermark');
const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
</script>
<template>
@ -24,4 +25,7 @@ const appWatermark = defineModel<boolean>('appWatermark');
<SwitchItem v-model="appWatermark">
{{ $t('preferences.watermark') }}
</SwitchItem>
<SwitchItem v-model="appEnableCheckUpdates">
{{ $t('preferences.checkUpdates') }}
</SwitchItem>
</template>

View File

@ -62,6 +62,7 @@ const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
const appWatermark = defineModel<boolean>('appWatermark');
const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
const transitionProgress = defineModel<boolean>('transitionProgress');
const transitionName = defineModel<string>('transitionName');
@ -254,6 +255,7 @@ async function handleReset() {
<Block :title="$t('preferences.general')">
<General
v-model:app-dynamic-title="appDynamicTitle"
v-model:app-enable-check-updates="appEnableCheckUpdates"
v-model:app-locale="appLocale"
v-model:app-watermark="appWatermark"
/>

View File

@ -60,6 +60,8 @@
"notifications": "Notifications",
"markAllAsRead": "Make All as Read",
"clearNotifications": "Clear",
"checkUpdatesTitle": "New Version Available",
"checkUpdatesDescription": "Click to refresh and get the latest version",
"search": {
"title": "Search",
"searchNavigate": "Search Navigation",
@ -166,6 +168,7 @@
"language": "Language",
"dynamicTitle": "Dynamic Title",
"watermark": "Watermark",
"checkUpdates": "Periodic update check",
"sidebar": {
"title": "Sidebar",
"width": "Width",

View File

@ -60,6 +60,8 @@
"notifications": "通知",
"markAllAsRead": "全部标记为已读",
"clearNotifications": "清空",
"checkUpdatesTitle": "新版本可用",
"checkUpdatesDescription": "点击刷新以获取最新版本",
"search": {
"title": "搜索",
"searchNavigate": "搜索导航菜单",
@ -166,6 +168,7 @@
"language": "语言",
"dynamicTitle": "动态标题",
"watermark": "水印",
"checkUpdates": "定时检查更新",
"sidebar": {
"title": "侧边栏",
"width": "宽度",

View File

@ -219,8 +219,8 @@ importers:
specifier: ^3.2.0
version: 3.2.0(eslint@9.8.0)
eslint-plugin-jsdoc:
specifier: ^48.8.3
version: 48.8.3(eslint@9.8.0)
specifier: ^48.9.2
version: 48.9.2(eslint@9.8.0)
eslint-plugin-jsonc:
specifier: ^2.16.0
version: 2.16.0(eslint@9.8.0)
@ -280,13 +280,13 @@ importers:
dependencies:
'@stylistic/stylelint-plugin':
specifier: ^2.1.2
version: 2.1.2(stylelint@16.7.0(typescript@5.5.4))
version: 2.1.2(stylelint@16.8.0(typescript@5.5.4))
stylelint-config-recess-order:
specifier: ^5.0.1
version: 5.0.1(stylelint@16.7.0(typescript@5.5.4))
version: 5.0.1(stylelint@16.8.0(typescript@5.5.4))
stylelint-scss:
specifier: ^6.4.1
version: 6.4.1(stylelint@16.7.0(typescript@5.5.4))
version: 6.4.1(stylelint@16.8.0(typescript@5.5.4))
devDependencies:
postcss:
specifier: ^8.4.40
@ -301,26 +301,26 @@ importers:
specifier: ^3.3.3
version: 3.3.3
stylelint:
specifier: ^16.7.0
version: 16.7.0(typescript@5.5.4)
specifier: ^16.8.0
version: 16.8.0(typescript@5.5.4)
stylelint-config-recommended:
specifier: ^14.0.1
version: 14.0.1(stylelint@16.7.0(typescript@5.5.4))
version: 14.0.1(stylelint@16.8.0(typescript@5.5.4))
stylelint-config-recommended-scss:
specifier: ^14.1.0
version: 14.1.0(postcss@8.4.40)(stylelint@16.7.0(typescript@5.5.4))
version: 14.1.0(postcss@8.4.40)(stylelint@16.8.0(typescript@5.5.4))
stylelint-config-recommended-vue:
specifier: ^1.5.0
version: 1.5.0(postcss-html@1.7.0)(stylelint@16.7.0(typescript@5.5.4))
version: 1.5.0(postcss-html@1.7.0)(stylelint@16.8.0(typescript@5.5.4))
stylelint-config-standard:
specifier: ^36.0.1
version: 36.0.1(stylelint@16.7.0(typescript@5.5.4))
version: 36.0.1(stylelint@16.8.0(typescript@5.5.4))
stylelint-order:
specifier: ^6.0.4
version: 6.0.4(stylelint@16.7.0(typescript@5.5.4))
version: 6.0.4(stylelint@16.8.0(typescript@5.5.4))
stylelint-prettier:
specifier: ^5.0.2
version: 5.0.2(prettier@3.3.3)(stylelint@16.7.0(typescript@5.5.4))
version: 5.0.2(prettier@3.3.3)(stylelint@16.8.0(typescript@5.5.4))
internal/node-utils:
dependencies:
@ -358,8 +358,8 @@ importers:
internal/tailwind-config:
dependencies:
'@iconify/json':
specifier: ^2.2.231
version: 2.2.231
specifier: ^2.2.232
version: 2.2.232
'@iconify/tailwind':
specifier: ^1.1.2
version: 1.1.2
@ -944,6 +944,9 @@ importers:
specifier: ^1.1.0
version: 1.1.0
devDependencies:
'@vite-pwa/vitepress':
specifier: ^0.5.0
version: 0.5.0(vite-plugin-pwa@0.20.1(vite@5.3.5(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3))(workbox-build@7.1.1)(workbox-window@7.1.0))
vitepress:
specifier: ^1.3.1
version: 1.3.1(@algolia/client-search@4.24.0)(@types/node@22.0.0)(async-validator@4.2.5)(axios@1.7.2)(nprogress@0.2.0)(postcss@8.4.40)(qrcode@1.5.3)(sass@1.77.8)(search-insights@2.15.0)(sortablejs@1.15.2)(terser@5.31.3)(typescript@5.5.4)
@ -2988,8 +2991,8 @@ packages:
resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
engines: {node: '>=18.18'}
'@iconify/json@2.2.231':
resolution: {integrity: sha512-+KlOkI3CuwSuG8H3EIeC7f5LTsm73aggoh1GA9Uh4YCl65zvTgYyFwCxJXnR2vVeCoAlO2UtCtjHjNwOWchf4g==}
'@iconify/json@2.2.232':
resolution: {integrity: sha512-o1W5bNDuXTd3ugywfX9uRmhuJTKYOLhH4qk+eUpkR8WEYdTmMW+FSiFO5R2KqD94Tzoi8Ef8u/R7felU5xk3eQ==}
'@iconify/tailwind@1.1.2':
resolution: {integrity: sha512-ZgToKxxd7zF5T9NXPnY9APRF06ZjFF21H/bINzcbKTdeJzLrNLIoVaoePIUbWVQ2HAac5cAYEHPZO8ILSUe3bQ==}
@ -3780,6 +3783,15 @@ packages:
engines: {node: '>=16'}
hasBin: true
'@vite-pwa/vitepress@0.5.0':
resolution: {integrity: sha512-a+BnACLMYOf/u2o6RhOJIdJgOW9wym9mTJGWbOLzFHE+fPjg+z1t/Xqm9LvOBQYEDIkrGrf+KxN4COQ0B8hbHg==}
peerDependencies:
'@vite-pwa/assets-generator': ^0.2.4
vite-plugin-pwa: '>=0.20.0 <1'
peerDependenciesMeta:
'@vite-pwa/assets-generator':
optional: true
'@vitejs/plugin-vue-jsx@4.0.0':
resolution: {integrity: sha512-A+6wL2AdQhDsLsDnY+2v4rRDI1HLJGIMc97a8FURO9tqKsH5QvjWrzsa5DH3NlZsM742W2wODl2fF+bfcTWtXw==}
engines: {node: ^18.0.0 || >=20.0.0}
@ -4880,6 +4892,15 @@ packages:
supports-color:
optional: true
debug@4.3.6:
resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
@ -5247,8 +5268,8 @@ packages:
peerDependencies:
eslint: ^8.56.0 || ^9.0.0-0
eslint-plugin-jsdoc@48.8.3:
resolution: {integrity: sha512-AtIvwwW9D17MRkM0Z0y3/xZYaa9mdAvJrkY6fU/HNUwGbmMtHVvK4qRM9CDixGVtfNrQitb8c6zQtdh6cTOvLg==}
eslint-plugin-jsdoc@48.9.2:
resolution: {integrity: sha512-ydqg2lEY/WxhMXEb1ZAn+yRbc43DlKYdMP/nUreF5ODE1P9mgeff8atL16lYNNKOvYxNOzL85/5gFVeGylSioA==}
engines: {node: '>=18'}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
@ -7451,6 +7472,9 @@ packages:
postcss-resolve-nested-selector@0.1.1:
resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==}
postcss-resolve-nested-selector@0.1.4:
resolution: {integrity: sha512-R6vHqZWgVnTAPq0C+xjyHfEZqfIYboCBVSy24MjxEDm+tIh1BU4O6o7DP7AA7kHzf136d+Qc5duI4tlpHjixDw==}
postcss-safe-parser@6.0.0:
resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==}
engines: {node: '>=12.0'}
@ -8310,8 +8334,8 @@ packages:
peerDependencies:
stylelint: ^16.0.2
stylelint@16.7.0:
resolution: {integrity: sha512-Q1ATiXlz+wYr37a7TGsfvqYn2nSR3T/isw3IWlZQzFzCNoACHuGBb6xBplZXz56/uDRJHIygxjh7jbV/8isewA==}
stylelint@16.8.0:
resolution: {integrity: sha512-Jjr40w3PXDiJVW6c9swLM0a1e0DgDm/XkFozc4XgAcREFas+/nchzmDmeVxazbzXgpDrwm+cW6W6iGtZqYUh+g==}
engines: {node: '>=18.12.0'}
hasBin: true
@ -11402,7 +11426,7 @@ snapshots:
'@humanwhocodes/retry@0.3.0': {}
'@iconify/json@2.2.231':
'@iconify/json@2.2.232':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
@ -12046,7 +12070,7 @@ snapshots:
'@sindresorhus/merge-streams@2.3.0': {}
'@stylistic/stylelint-plugin@2.1.2(stylelint@16.7.0(typescript@5.5.4))':
'@stylistic/stylelint-plugin@2.1.2(stylelint@16.8.0(typescript@5.5.4))':
dependencies:
'@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1)
'@csstools/css-tokenizer': 2.4.1
@ -12055,7 +12079,7 @@ snapshots:
postcss-selector-parser: 6.1.1
postcss-value-parser: 4.2.0
style-search: 0.1.0
stylelint: 16.7.0(typescript@5.5.4)
stylelint: 16.8.0(typescript@5.5.4)
'@surma/rollup-plugin-off-main-thread@2.2.3':
dependencies:
@ -12310,6 +12334,10 @@ snapshots:
- encoding
- supports-color
'@vite-pwa/vitepress@0.5.0(vite-plugin-pwa@0.20.1(vite@5.3.5(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3))(workbox-build@7.1.1)(workbox-window@7.1.0))':
dependencies:
vite-plugin-pwa: 0.20.1(vite@5.3.5(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3))(workbox-build@7.1.1)(workbox-window@7.1.0)
'@vitejs/plugin-vue-jsx@4.0.0(vite@5.3.5(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))':
dependencies:
'@babel/core': 7.24.9
@ -13635,6 +13663,10 @@ snapshots:
dependencies:
ms: 2.1.2
debug@4.3.6:
dependencies:
ms: 2.1.2
decamelize@1.2.0: {}
decimal.js@10.4.3: {}
@ -14119,7 +14151,7 @@ snapshots:
- supports-color
- typescript
eslint-plugin-jsdoc@48.8.3(eslint@9.8.0):
eslint-plugin-jsdoc@48.9.2(eslint@9.8.0):
dependencies:
'@es-joy/jsdoccomment': 0.46.0
are-docs-informative: 0.0.2
@ -16487,6 +16519,8 @@ snapshots:
postcss-resolve-nested-selector@0.1.1: {}
postcss-resolve-nested-selector@0.1.4: {}
postcss-safe-parser@6.0.0(postcss@8.4.40):
dependencies:
postcss: 8.4.40
@ -17296,64 +17330,64 @@ snapshots:
postcss: 8.4.40
postcss-selector-parser: 6.1.1
stylelint-config-html@1.1.0(postcss-html@1.7.0)(stylelint@16.7.0(typescript@5.5.4)):
stylelint-config-html@1.1.0(postcss-html@1.7.0)(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
postcss-html: 1.7.0
stylelint: 16.7.0(typescript@5.5.4)
stylelint: 16.8.0(typescript@5.5.4)
stylelint-config-recess-order@5.0.1(stylelint@16.7.0(typescript@5.5.4)):
stylelint-config-recess-order@5.0.1(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
stylelint: 16.7.0(typescript@5.5.4)
stylelint-order: 6.0.4(stylelint@16.7.0(typescript@5.5.4))
stylelint: 16.8.0(typescript@5.5.4)
stylelint-order: 6.0.4(stylelint@16.8.0(typescript@5.5.4))
stylelint-config-recommended-scss@14.1.0(postcss@8.4.40)(stylelint@16.7.0(typescript@5.5.4)):
stylelint-config-recommended-scss@14.1.0(postcss@8.4.40)(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
postcss-scss: 4.0.9(postcss@8.4.40)
stylelint: 16.7.0(typescript@5.5.4)
stylelint-config-recommended: 14.0.1(stylelint@16.7.0(typescript@5.5.4))
stylelint-scss: 6.4.1(stylelint@16.7.0(typescript@5.5.4))
stylelint: 16.8.0(typescript@5.5.4)
stylelint-config-recommended: 14.0.1(stylelint@16.8.0(typescript@5.5.4))
stylelint-scss: 6.4.1(stylelint@16.8.0(typescript@5.5.4))
optionalDependencies:
postcss: 8.4.40
stylelint-config-recommended-vue@1.5.0(postcss-html@1.7.0)(stylelint@16.7.0(typescript@5.5.4)):
stylelint-config-recommended-vue@1.5.0(postcss-html@1.7.0)(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
postcss-html: 1.7.0
semver: 7.6.3
stylelint: 16.7.0(typescript@5.5.4)
stylelint-config-html: 1.1.0(postcss-html@1.7.0)(stylelint@16.7.0(typescript@5.5.4))
stylelint-config-recommended: 14.0.1(stylelint@16.7.0(typescript@5.5.4))
stylelint: 16.8.0(typescript@5.5.4)
stylelint-config-html: 1.1.0(postcss-html@1.7.0)(stylelint@16.8.0(typescript@5.5.4))
stylelint-config-recommended: 14.0.1(stylelint@16.8.0(typescript@5.5.4))
stylelint-config-recommended@14.0.1(stylelint@16.7.0(typescript@5.5.4)):
stylelint-config-recommended@14.0.1(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
stylelint: 16.7.0(typescript@5.5.4)
stylelint: 16.8.0(typescript@5.5.4)
stylelint-config-standard@36.0.1(stylelint@16.7.0(typescript@5.5.4)):
stylelint-config-standard@36.0.1(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
stylelint: 16.7.0(typescript@5.5.4)
stylelint-config-recommended: 14.0.1(stylelint@16.7.0(typescript@5.5.4))
stylelint: 16.8.0(typescript@5.5.4)
stylelint-config-recommended: 14.0.1(stylelint@16.8.0(typescript@5.5.4))
stylelint-order@6.0.4(stylelint@16.7.0(typescript@5.5.4)):
stylelint-order@6.0.4(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
postcss: 8.4.40
postcss-sorting: 8.0.2(postcss@8.4.40)
stylelint: 16.7.0(typescript@5.5.4)
stylelint: 16.8.0(typescript@5.5.4)
stylelint-prettier@5.0.2(prettier@3.3.3)(stylelint@16.7.0(typescript@5.5.4)):
stylelint-prettier@5.0.2(prettier@3.3.3)(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
prettier: 3.3.3
prettier-linter-helpers: 1.0.0
stylelint: 16.7.0(typescript@5.5.4)
stylelint: 16.8.0(typescript@5.5.4)
stylelint-scss@6.4.1(stylelint@16.7.0(typescript@5.5.4)):
stylelint-scss@6.4.1(stylelint@16.8.0(typescript@5.5.4)):
dependencies:
known-css-properties: 0.34.0
postcss-media-query-parser: 0.2.3
postcss-resolve-nested-selector: 0.1.1
postcss-selector-parser: 6.1.1
postcss-value-parser: 4.2.0
stylelint: 16.7.0(typescript@5.5.4)
stylelint: 16.8.0(typescript@5.5.4)
stylelint@16.7.0(typescript@5.5.4):
stylelint@16.8.0(typescript@5.5.4):
dependencies:
'@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1)
'@csstools/css-tokenizer': 2.4.1
@ -17365,7 +17399,7 @@ snapshots:
cosmiconfig: 9.0.0(typescript@5.5.4)
css-functions-list: 3.2.2
css-tree: 2.3.1
debug: 4.3.5
debug: 4.3.6
fast-glob: 3.3.2
fastest-levenshtein: 1.0.16
file-entry-cache: 9.0.0
@ -17383,7 +17417,7 @@ snapshots:
normalize-path: 3.0.0
picocolors: 1.0.1
postcss: 8.4.40
postcss-resolve-nested-selector: 0.1.1
postcss-resolve-nested-selector: 0.1.4
postcss-safe-parser: 7.0.0(postcss@8.4.40)
postcss-selector-parser: 6.1.1
postcss-value-parser: 4.2.0

View File

@ -1,109 +1,116 @@
import type { DefaultTheme, HeadConfig } from 'vitepress';
import { resolve } from 'node:path';
import { type PwaOptions, withPwa } from '@vite-pwa/vitepress';
import { defineConfigWithTheme } from 'vitepress';
import { version } from '../../package.json';
export default defineConfigWithTheme({
description: 'Vben Admin& 企业级管理系统框架',
head: head(),
lang: 'zh',
srcDir: 'src',
// locales: {
// en: {
// label: 'English',
// lang: 'en',
// link: '/en/',
// },
// root: {
// label: '简体中文',
// lang: 'zh-CN',
// },
themeConfig: {
darkModeSwitchLabel: '主题',
darkModeSwitchTitle: '切换到深色模式',
docFooter: {
next: '下一页',
prev: '上一页',
},
editLink: {
pattern: 'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/:path',
text: '在 GitHub 上编辑此页面',
},
footer: {
copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`,
message: '基于 MIT 许可发布.',
},
i18nRouting: true,
langMenuLabel: '多语言',
lastUpdated: {
formatOptions: {
dateStyle: 'short',
timeStyle: 'medium',
export default withPwa(
defineConfigWithTheme({
description: 'Vben Admin& 企业级管理系统框架',
head: head(),
lang: 'zh',
pwa: pwa(),
// locales: {
// en: {
// label: 'English',
// lang: 'en',
// link: '/en/',
// },
// root: {
// label: '简体中文',
// lang: 'zh-CN',
srcDir: 'src',
// },
themeConfig: {
darkModeSwitchLabel: '主题',
darkModeSwitchTitle: '切换到深色模式',
docFooter: {
next: '下一页',
prev: '上一页',
},
text: '最后更新于',
},
lightModeSwitchTitle: '切换到浅色模式',
logo: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp',
nav: nav(),
outline: {
label: '页面导航',
},
returnToTopLabel: '回到顶部',
search: {
options: {
locales: {
zh: {
translations: {
button: {
buttonAriaLabel: '搜索文档',
buttonText: '搜索文档',
},
modal: {
footer: {
navigateText: '切换',
selectText: '选择',
editLink: {
pattern:
'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/:path',
text: '在 GitHub 上编辑此页面',
},
footer: {
copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`,
message: '基于 MIT 许可发布.',
},
i18nRouting: true,
langMenuLabel: '多语言',
lastUpdated: {
formatOptions: {
dateStyle: 'short',
timeStyle: 'medium',
},
text: '最后更新于',
},
lightModeSwitchTitle: '切换到浅色模式',
logo: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp',
nav: nav(),
outline: {
label: '页面导航',
},
returnToTopLabel: '回到顶部',
search: {
options: {
locales: {
zh: {
translations: {
button: {
buttonAriaLabel: '搜索文档',
buttonText: '搜索文档',
},
modal: {
footer: {
navigateText: '切换',
selectText: '选择',
},
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
},
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
},
},
},
},
provider: 'local',
},
provider: 'local',
},
sidebar: {
'/commercial/': { base: '/commercial/', items: sidebarCommercial() },
'/guide/': { base: '/guide/', items: sidebarGuide() },
},
sidebarMenuLabel: '菜单',
siteTitle: 'Vben Admin',
socialLinks: [
{ icon: 'github', link: 'https://github.com/vbenjs/vue-vben-admin' },
],
},
title: 'Vben Admin',
vite: {
build: {
chunkSizeWarningLimit: Infinity,
minify: 'terser',
},
json: {
stringify: true,
},
server: {
fs: {
allow: ['../..'],
sidebar: {
'/commercial/': { base: '/commercial/', items: sidebarCommercial() },
'/guide/': { base: '/guide/', items: sidebarGuide() },
},
host: true,
port: 6173,
sidebarMenuLabel: '菜单',
siteTitle: 'Vben Admin',
socialLinks: [
{ icon: 'github', link: 'https://github.com/vbenjs/vue-vben-admin' },
],
},
ssr: {
external: ['@vue/repl'],
title: 'Vben Admin',
vite: {
build: {
chunkSizeWarningLimit: Infinity,
minify: 'terser',
},
json: {
stringify: true,
},
server: {
fs: {
allow: ['../..'],
},
host: true,
port: 6173,
},
ssr: {
external: ['@vue/repl'],
},
},
},
});
}),
);
function nav(): DefaultTheme.NavItem[] {
return [
@ -306,18 +313,36 @@ function head(): HeadConfig[] {
// src: 'https://cdn.tailwindcss.com',
// },
// ],
[
'script',
{},
`
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?2e443a834727c065877c01d89921545e";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
`,
],
];
}
function pwa(): PwaOptions {
return {
includeManifestIcons: false,
manifest: {
description:
'Vben Admin is a modern admin dashboard template based on Vue 3. ',
icons: [
{
sizes: '192x192',
src: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/pwa-icon-192.png',
type: 'image/png',
},
{
sizes: '512x512',
src: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/pwa-icon-512.png',
type: 'image/png',
},
],
id: '/',
name: 'Vben Admin Doc',
short_name: 'vben_admin_doc',
theme_color: '#ffffff',
},
outDir: resolve(process.cwd(), '.vitepress/dist'),
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
},
};
}

View File

@ -5,14 +5,17 @@ import DefaultTheme from 'vitepress/theme';
import SiteLayout from './components/site-layout.vue';
import VbenContributors from './components/vben-contributors.vue';
import { initHmPlugin } from './plugins/hm';
import './styles';
export default {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
enhanceApp({ app, router, siteData }) {
enhanceApp({ app }) {
// ...
app.component('VbenContributors', VbenContributors);
// 百度统计
initHmPlugin();
},
extends: DefaultTheme,
Layout: SiteLayout,

View File

@ -0,0 +1,28 @@
import { inBrowser } from 'vitepress';
const SITE_ID = '2e443a834727c065877c01d89921545e';
declare global {
interface Window {
_hmt: any;
}
}
function registerAnalytics() {
window._hmt = window._hmt || [];
const script = document.createElement('script');
script.innerHTML = `var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?${SITE_ID}";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})()`;
document.querySelector('head')?.append(script);
}
export function initHmPlugin() {
if (inBrowser && import.meta.env.PROD) {
registerAnalytics(SITE_ID);
}
}

View File

@ -11,6 +11,7 @@
"medium-zoom": "^1.1.0"
},
"devDependencies": {
"@vite-pwa/vitepress": "^0.5.0",
"vitepress": "^1.3.1",
"vue": "^3.4.34"
}

View File

@ -174,6 +174,7 @@ const defaultPreferences: Preferences = {
app: {
accessMode: 'frontend',
authPageLayout: 'panel-right',
checkUpdatesPollingTime: 1,
colorGrayMode: false,
colorWeakMode: false,
compact: false,
@ -181,11 +182,12 @@ const defaultPreferences: Preferences = {
defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.5/source/avatar-v1.webp',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
isMobile: false,
layout: 'sidebar-nav',
locale: 'zh-CN',
loginExpiredMode: 'page',
loginExpiredMode: 'modal',
name: 'Vben Admin',
watermark: false,
},
@ -288,6 +290,8 @@ interface AppPreferences {
accessMode: AccessModeType;
/** 登录注册页面布局 */
authPageLayout: AuthPageLayoutType;
/** 检查更新轮询时间 */
checkUpdatesPollingTime: number;
/** 是否开启灰色模式 */
colorGrayMode: boolean;
/** 是否开启色弱模式 */
@ -300,6 +304,8 @@ interface AppPreferences {
defaultAvatar: string;
// /** 开启动态标题 */
dynamicTitle: boolean;
/** 是否开启检查更新 */
enableCheckUpdates: boolean;
/** 是否显示偏好设置 */
enablePreferences: boolean;
/** 是否移动端 */

View File

@ -16,7 +16,7 @@
4. 在下面列表找不到问题可以到 issue 提问 [issues](https://github.com/vbenjs/vue-vben-admin/issues)
5. 如果不是问题类型的,需要讨论的,请到 [discussions](https://github.com/vbenjs/vue-vben-admin/discussions) 讨论
## 依赖安装问题
## 依赖问题
`Monorepo` 项目下,需要养成每次 `git pull`代码都要执行`pnpm install`的习惯,因为经常会有新的依赖包加入,项目在`.husky/git-merge`已经配置了自动执行`pnpm install`,但是有时候会出现问题,如果没有自动执行,建议手动执行一次。
@ -135,3 +135,21 @@ at Object.extractor (vue-vben-admin-main\node_modules@purge-icons\core\dist\inde
at Extract (vue-vben-admin-main\node_modules@purge-icons\core\dist\index.js:173:54)
```
## nginx 部署
部署到 `nginx`后,可能会出现以下错误:
```bash
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.
```
解决方式:
```bash
http {
types {
application/javascript js mjs;
}
}
```

6
website/tsconfig.json Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": [".vitepress/*.mts", ".vitepress/**/*.ts", ".vitepress/**/*.vue"],
"exclude": ["node_modules"]
}