feat: Regular monitoring page update [deploy]
This commit is contained in:
@@ -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,
|
||||
|
@@ -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;
|
||||
/** 是否移动端 */
|
||||
|
@@ -1,9 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Toaster } from '@vben-core/shadcn-ui';
|
||||
|
||||
defineOptions({ name: 'GlobalProvider' });
|
||||
</script>
|
||||
<template>
|
||||
<Toaster />
|
||||
<slot></slot>
|
||||
</template>
|
@@ -1 +0,0 @@
|
||||
export { default as GlobalProvider } from './global-provider.vue';
|
@@ -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';
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
@@ -0,0 +1 @@
|
||||
export { default as CheckUpdates } from './check-updates.vue';
|
@@ -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';
|
||||
|
@@ -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>
|
||||
|
@@ -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"
|
||||
/>
|
||||
|
@@ -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",
|
||||
|
@@ -60,6 +60,8 @@
|
||||
"notifications": "通知",
|
||||
"markAllAsRead": "全部标记为已读",
|
||||
"clearNotifications": "清空",
|
||||
"checkUpdatesTitle": "新版本可用",
|
||||
"checkUpdatesDescription": "点击刷新以获取最新版本",
|
||||
"search": {
|
||||
"title": "搜索",
|
||||
"searchNavigate": "搜索导航菜单",
|
||||
@@ -166,6 +168,7 @@
|
||||
"language": "语言",
|
||||
"dynamicTitle": "动态标题",
|
||||
"watermark": "水印",
|
||||
"checkUpdates": "定时检查更新",
|
||||
"sidebar": {
|
||||
"title": "侧边栏",
|
||||
"width": "宽度",
|
||||
|
Reference in New Issue
Block a user