feat: 1、新增水印文案自定义功能;2、input-item输入框组件新增清除功能(可用于快捷清除水印文案);3、偏好设置、主题切换、语言选择、是否全屏按钮新增动画效果 (#6800)

* feature: 新增水印文案自定义功能;

* chore: 偏好设置、主题切换、语言选择、是否全屏按钮新增动画效果
This commit is contained in:
zouawen 2025-10-07 06:15:41 +08:00 committed by GitHub
parent 33306a5aff
commit 2ce161e585
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 92 additions and 19 deletions

View File

@ -106,11 +106,16 @@ function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true)); notifications.value.forEach((item) => (item.isRead = true));
} }
watch( watch(
() => preferences.app.watermark, () => ({
async (enable) => { enable: preferences.app.watermark,
content: preferences.app.watermarkContent,
}),
async ({ enable, content }) => {
if (enable) { if (enable) {
await updateWatermark({ await updateWatermark({
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, content:
content ||
`${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
}); });
} else { } else {
destroyWatermark(); destroyWatermark();

View File

@ -106,11 +106,16 @@ function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true)); notifications.value.forEach((item) => (item.isRead = true));
} }
watch( watch(
() => preferences.app.watermark, () => ({
async (enable) => { enable: preferences.app.watermark,
content: preferences.app.watermarkContent,
}),
async ({ enable, content }) => {
if (enable) { if (enable) {
await updateWatermark({ await updateWatermark({
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, content:
content ||
`${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
}); });
} else { } else {
destroyWatermark(); destroyWatermark();

View File

@ -107,11 +107,16 @@ function handleMakeAll() {
} }
watch( watch(
() => preferences.app.watermark, () => ({
async (enable) => { enable: preferences.app.watermark,
content: preferences.app.watermarkContent,
}),
async ({ enable, content }) => {
if (enable) { if (enable) {
await updateWatermark({ await updateWatermark({
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, content:
content ||
`${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
}); });
} else { } else {
destroyWatermark(); destroyWatermark();

View File

@ -85,3 +85,17 @@
.z-popup { .z-popup {
z-index: var(--popup-z-index); z-index: var(--popup-z-index);
} }
@keyframes shrink {
0% {
transform: scale(1);
}
50% {
transform: scale(0.9);
}
100% {
transform: scale(1);
}
}

View File

@ -29,6 +29,7 @@ const defaultPreferences: Preferences = {
name: 'Vben Admin', name: 'Vben Admin',
preferencesButtonPosition: 'auto', preferencesButtonPosition: 'auto',
watermark: false, watermark: false,
watermarkContent: '',
zIndex: 200, zIndex: 200,
}, },
breadcrumb: { breadcrumb: {

View File

@ -75,6 +75,10 @@ interface AppPreferences {
* @zh_CN * @zh_CN
*/ */
watermark: boolean; watermark: boolean;
/**
* @zh_CN
*/
watermarkContent: string;
/** z-index */ /** z-index */
zIndex: number; zIndex: number;
} }

View File

@ -21,7 +21,10 @@ isFullscreen.value = !!(
); );
</script> </script>
<template> <template>
<VbenIconButton @click="toggle"> <VbenIconButton
class="hover:animate-[shrink_0.3s_ease-in-out]"
@click="toggle"
>
<Minimize v-if="isFullscreen" class="text-foreground size-4" /> <Minimize v-if="isFullscreen" class="text-foreground size-4" />
<Maximize v-else class="text-foreground size-4" /> <Maximize v-else class="text-foreground size-4" />
</VbenIconButton> </VbenIconButton>

View File

@ -31,7 +31,7 @@ async function handleUpdate(value: string | undefined) {
:model-value="preferences.app.locale" :model-value="preferences.app.locale"
@update:model-value="handleUpdate" @update:model-value="handleUpdate"
> >
<VbenIconButton> <VbenIconButton class="hover:animate-[shrink_0.3s_ease-in-out]">
<Languages class="text-foreground size-4" /> <Languages class="text-foreground size-4" />
</VbenIconButton> </VbenIconButton>
</VbenDropdownRadioMenu> </VbenDropdownRadioMenu>

View File

@ -2,6 +2,7 @@
import { SUPPORT_LANGUAGES } from '@vben/constants'; import { SUPPORT_LANGUAGES } from '@vben/constants';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import InputItem from '../input-item.vue';
import SelectItem from '../select-item.vue'; import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue'; import SwitchItem from '../switch-item.vue';
@ -12,6 +13,7 @@ defineOptions({
const appLocale = defineModel<string>('appLocale'); const appLocale = defineModel<string>('appLocale');
const appDynamicTitle = defineModel<boolean>('appDynamicTitle'); const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
const appWatermark = defineModel<boolean>('appWatermark'); const appWatermark = defineModel<boolean>('appWatermark');
const appWatermarkContent = defineModel<string>('appWatermarkContent');
const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates'); const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
</script> </script>
@ -22,9 +24,23 @@ const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
<SwitchItem v-model="appDynamicTitle"> <SwitchItem v-model="appDynamicTitle">
{{ $t('preferences.dynamicTitle') }} {{ $t('preferences.dynamicTitle') }}
</SwitchItem> </SwitchItem>
<SwitchItem v-model="appWatermark"> <SwitchItem
v-model="appWatermark"
@update:model-value="
(val) => {
if (!val) appWatermarkContent = '';
}
"
>
{{ $t('preferences.watermark') }} {{ $t('preferences.watermark') }}
</SwitchItem> </SwitchItem>
<InputItem
v-if="appWatermark"
v-model="appWatermarkContent"
:placeholder="$t('preferences.watermarkContent')"
>
{{ $t('preferences.watermarkContent') }}
</InputItem>
<SwitchItem v-model="appEnableCheckUpdates"> <SwitchItem v-model="appEnableCheckUpdates">
{{ $t('preferences.checkUpdates') }} {{ $t('preferences.checkUpdates') }}
</SwitchItem> </SwitchItem>

View File

@ -3,7 +3,7 @@ import type { SelectOption } from '@vben/types';
import { useSlots } from 'vue'; import { useSlots } from 'vue';
import { CircleHelp } from '@vben/icons'; import { CircleHelp, CircleX } from '@vben/icons';
import { Input, VbenTooltip } from '@vben-core/shadcn-ui'; import { Input, VbenTooltip } from '@vben-core/shadcn-ui';
@ -47,6 +47,17 @@ const slots = useSlots();
<slot name="tip"></slot> <slot name="tip"></slot>
</VbenTooltip> </VbenTooltip>
</span> </span>
<Input v-model="inputValue" class="h-8 w-[165px]" /> <div class="relative">
<Input
v-model="inputValue"
class="h-8 w-[165px]"
:placeholder="placeholder"
/>
<CircleX
v-if="inputValue"
class="hover:text-foreground text-foreground/60 absolute right-2 top-1/2 size-3 -translate-y-1/2 transform cursor-pointer"
@click="() => (inputValue = '')"
/>
</div>
</div> </div>
</template> </template>

View File

@ -13,7 +13,7 @@ function clearPreferencesAndLogout() {
</script> </script>
<template> <template>
<Preferences @clear-preferences-and-logout="clearPreferencesAndLogout"> <Preferences @clear-preferences-and-logout="clearPreferencesAndLogout">
<VbenIconButton> <VbenIconButton class="hover:animate-[shrink_0.3s_ease-in-out]">
<Settings class="text-foreground size-4" /> <Settings class="text-foreground size-4" />
</VbenIconButton> </VbenIconButton>
</Preferences> </Preferences>

View File

@ -67,6 +67,7 @@ const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
const appColorWeakMode = defineModel<boolean>('appColorWeakMode'); const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
const appContentCompact = defineModel<ContentCompactType>('appContentCompact'); const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
const appWatermark = defineModel<boolean>('appWatermark'); const appWatermark = defineModel<boolean>('appWatermark');
const appWatermarkContent = defineModel<string>('appWatermarkContent');
const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates'); const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
const appPreferencesButtonPosition = defineModel<PreferencesButtonPositionType>( const appPreferencesButtonPosition = defineModel<PreferencesButtonPositionType>(
'appPreferencesButtonPosition', 'appPreferencesButtonPosition',
@ -267,6 +268,7 @@ async function handleReset() {
v-model:app-enable-check-updates="appEnableCheckUpdates" v-model:app-enable-check-updates="appEnableCheckUpdates"
v-model:app-locale="appLocale" v-model:app-locale="appLocale"
v-model:app-watermark="appWatermark" v-model:app-watermark="appWatermark"
v-model:app-watermark-content="appWatermarkContent"
/> />
</Block> </Block>

View File

@ -88,7 +88,7 @@ function toggleTheme(event: MouseEvent) {
:aria-label="theme" :aria-label="theme"
:class="[`is-${theme}`]" :class="[`is-${theme}`]"
aria-live="polite" aria-live="polite"
class="theme-toggle cursor-pointer border-none bg-none" class="theme-toggle cursor-pointer border-none bg-none hover:animate-[shrink_0.3s_ease-in-out]"
v-bind="bindProps" v-bind="bindProps"
@click.stop="toggleTheme" @click.stop="toggleTheme"
> >

View File

@ -37,6 +37,7 @@
"language": "Language", "language": "Language",
"dynamicTitle": "Dynamic Title", "dynamicTitle": "Dynamic Title",
"watermark": "Watermark", "watermark": "Watermark",
"watermarkContent": "Please input Watermark content",
"checkUpdates": "Periodic update check", "checkUpdates": "Periodic update check",
"position": { "position": {
"title": "Preferences Postion", "title": "Preferences Postion",

View File

@ -37,6 +37,7 @@
"language": "语言", "language": "语言",
"dynamicTitle": "动态标题", "dynamicTitle": "动态标题",
"watermark": "水印", "watermark": "水印",
"watermarkContent": "请输入水印文案",
"checkUpdates": "定时检查更新", "checkUpdates": "定时检查更新",
"position": { "position": {
"title": "偏好设置位置", "title": "偏好设置位置",

View File

@ -122,11 +122,16 @@ function handleMakeAll() {
function handleClickLogo() {} function handleClickLogo() {}
watch( watch(
() => preferences.app.watermark, () => ({
async (enable) => { enable: preferences.app.watermark,
content: preferences.app.watermarkContent,
}),
async ({ enable, content }) => {
if (enable) { if (enable) {
await updateWatermark({ await updateWatermark({
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, content:
content ||
`${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
}); });
} else { } else {
destroyWatermark(); destroyWatermark();