refactor: refacotr preference

This commit is contained in:
vben
2024-06-01 23:15:29 +08:00
parent f7b97e8a83
commit fed47f5e05
139 changed files with 2205 additions and 1450 deletions

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
interface Props {
title?: string;
}
defineOptions({
name: 'PreferenceBlock',
});
withDefaults(defineProps<Props>(), {
title: '',
});
</script>
<template>
<div class="flex flex-col py-4">
<h3 class="mb-3 font-semibold leading-none tracking-tight">
{{ title }}
</h3>
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceAnimation',
});
const pageProgress = defineModel<boolean>('pageProgress', {
// 默认值
default: false,
});
const pageTransition = defineModel<string>('pageTransition');
const pageTransitionEnable = defineModel<boolean>('pageTransitionEnable');
const transitionPreset = ['fade', 'fade-slide', 'fade-up', 'fade-down'];
function handleClick(value: string) {
pageTransition.value = value;
}
</script>
<template>
<SwitchItem v-model="pageProgress">
{{ $t('preference.page-progress') }}
</SwitchItem>
<SwitchItem v-model="pageTransitionEnable">
{{ $t('preference.page-transition') }}
</SwitchItem>
<div
v-if="pageTransitionEnable"
class="mb-2 mt-3 flex justify-between gap-3 px-2"
>
<div
v-for="item in transitionPreset"
:key="item"
class="outline-box p-2"
:class="{
'outline-box-active': pageTransition === item,
}"
@click="handleClick(item)"
>
<div class="bg-accent h-10 w-12 rounded-md" :class="`${item}-slow`"></div>
</div>
</div>
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { SUPPORT_LANGUAGES } from '@vben-core/preferences';
import { $t } from '@vben/locales';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceGeneralConfig',
});
const locale = defineModel<string>('locale');
const dynamicTitle = defineModel<boolean>('dynamicTitle');
const shortcutKeys = defineModel<boolean>('shortcutKeys');
const localeItems: SelectListItem[] = SUPPORT_LANGUAGES.map((item) => ({
label: item.text,
value: item.key,
}));
</script>
<template>
<SelectItem v-model="locale" :items="localeItems">
{{ $t('preference.language') }}
</SelectItem>
<SwitchItem v-model="dynamicTitle">
{{ $t('preference.dynamic-title') }}
</SwitchItem>
<SwitchItem v-model="shortcutKeys">
{{ $t('preference.shortcut-key') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,15 @@
export { default as Block } from './block.vue';
export { default as Animation } from './general/animation.vue';
export { default as General } from './general/general.vue';
export { default as Breadcrumb } from './layout/breadcrumb.vue';
export { default as Content } from './layout/content.vue';
export { default as Footer } from './layout/footer.vue';
export { default as Header } from './layout/header.vue';
export { default as Layout } from './layout/layout.vue';
export { default as Navigation } from './layout/navigation.vue';
export { default as Sidebar } from './layout/sidebar.vue';
export { default as Tabs } from './layout/tabs.vue';
export { default as SwitchItem } from './switch-item.vue';
export { default as ThemeColor } from './theme/color.vue';
export { default as ColorMode } from './theme/color-mode.vue';
export { default as Theme } from './theme/theme.vue';

View File

@@ -0,0 +1,56 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
import ToggleItem from '../toggle-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
defineProps<{ disabled: boolean }>();
const breadcrumbVisible = defineModel<boolean>('breadcrumbVisible');
const breadcrumbIcon = defineModel<boolean>('breadcrumbIcon');
const breadcrumbStyle = defineModel<string>('breadcrumbStyle');
const breadcrumbHome = defineModel<boolean>('breadcrumbHome');
const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
const typeItems: SelectListItem[] = [
{ label: $t('preference.normal'), value: 'normal' },
{ label: $t('preference.breadcrumb-background'), value: 'background' },
];
</script>
<template>
<SwitchItem v-model="breadcrumbVisible" :disabled="disabled">
{{ $t('preference.breadcrumb-enable') }}
</SwitchItem>
<SwitchItem
v-model="breadcrumbHideOnlyOne"
:disabled="!breadcrumbVisible || disabled"
>
{{ $t('preference.breadcrumb-hide-only-one') }}
</SwitchItem>
<SwitchItem
v-model="breadcrumbHome"
:disabled="!breadcrumbVisible || disabled"
>
{{ $t('preference.breadcrumb-home') }}
</SwitchItem>
<SwitchItem
v-model="breadcrumbIcon"
:disabled="!breadcrumbVisible || disabled"
>
{{ $t('preference.breadcrumb-icon') }}
</SwitchItem>
<ToggleItem
v-model="breadcrumbStyle"
:items="typeItems"
:disabled="!breadcrumbVisible || disabled"
>
{{ $t('preference.breadcrumb-style') }}
</ToggleItem>
</template>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import { type Component, computed } from 'vue';
import { ContentCompact, ContentWide } from '../../icons';
defineOptions({
name: 'PreferenceLayoutContent',
});
const modelValue = defineModel<string>({ default: 'wide' });
const components: Record<string, Component> = {
compact: ContentCompact,
wide: ContentWide,
};
const PRESET = computed(() => [
{
name: $t('preference.wide'),
type: 'wide',
},
{
name: '定宽',
type: 'compact',
},
]);
function activeClass(theme: string): string[] {
return theme === modelValue.value ? ['outline-box-active'] : [];
}
</script>
<template>
<div class="flex w-full gap-5">
<template v-for="theme in PRESET" :key="theme.name">
<div
class="flex w-[100px] cursor-pointer flex-col"
@click="modelValue = theme.type"
>
<div :class="activeClass(theme.type)" class="outline-box flex-center">
<component :is="components[theme.type]" />
</div>
<div class="text-muted-foreground mt-2 text-center text-xs">
{{ theme.name }}
</div>
</div>
</template>
</div>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
const footerVisible = defineModel<boolean>('footerVisible');
const footerFixed = defineModel<boolean>('footerFixed');
</script>
<template>
<SwitchItem v-model="footerVisible">
{{ $t('preference.footer-visible') }}
</SwitchItem>
<SwitchItem v-model="footerFixed" :disabled="!footerVisible">
{{ $t('preference.footer-fixed') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import type { LayoutHeaderModeType, SelectListItem } from '@vben/types';
import { $t } from '@vben/locales';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
defineProps<{ disabled: boolean }>();
const headerVisible = defineModel<boolean>('headerVisible');
const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
const localeItems: SelectListItem[] = [
{
label: $t('preference.header-mode-static'),
value: 'static',
},
{
label: $t('preference.header-mode-fixed'),
value: 'fixed',
},
{
label: $t('preference.header-mode-auto'),
value: 'auto',
},
{
label: $t('preference.header-mode-auto-scroll'),
value: 'auto-scroll',
},
];
</script>
<template>
<SwitchItem v-model="headerVisible" :disabled="disabled">
{{ $t('preference.header-visible') }}
</SwitchItem>
<SelectItem
v-model="headerMode"
:items="localeItems"
:disabled="!headerVisible"
>
{{ $t('preference.mode') }}
</SelectItem>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceInterfaceControl',
});
const tabsVisible = defineModel<boolean>('tabsVisible');
const logoVisible = defineModel<boolean>('logoVisible');
</script>
<template>
<SwitchItem v-model="tabsVisible">
{{ $t('preference.tabs-visible') }}
</SwitchItem>
<SwitchItem v-model="logoVisible">
{{ $t('preference.logo-visible') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
import type { LayoutType } from '@vben/types';
import { MdiQuestionMarkCircleOutline } from '@vben-core/iconify';
import { VbenTooltip } from '@vben-core/shadcn-ui';
import { $t } from '@vben/locales';
import { type Component, computed } from 'vue';
import {
FullContent,
HeaderNav,
MixedNav,
SideMixedNav,
SideNav,
} from '../../icons';
interface PresetItem {
name: string;
tip: string;
type: LayoutType;
}
defineOptions({
name: 'PreferenceLayout',
});
const modelValue = defineModel<LayoutType>({ default: 'side-nav' });
const components: Record<LayoutType, Component> = {
'full-content': FullContent,
'header-nav': HeaderNav,
'mixed-nav': MixedNav,
'side-mixed-nav': SideMixedNav,
'side-nav': SideNav,
};
const PRESET = computed((): PresetItem[] => [
{
name: $t('preference.vertical'),
tip: $t('preference.vertical-tip'),
type: 'side-nav',
},
{
name: $t('preference.two-column'),
tip: $t('preference.two-column-tip'),
type: 'side-mixed-nav',
},
{
name: $t('preference.horizontal'),
tip: $t('preference.vertical-tip'),
type: 'header-nav',
},
{
name: $t('preference.mixed-menu'),
tip: $t('preference.mixed-menu-tip'),
type: 'mixed-nav',
},
{
name: $t('preference.full-content'),
tip: $t('preference.full-content-tip'),
type: 'full-content',
},
]);
function activeClass(theme: string): string[] {
return theme === modelValue.value ? ['outline-box-active'] : [];
}
</script>
<template>
<div class="flex w-full flex-wrap gap-5">
<template v-for="theme in PRESET" :key="theme.name">
<div
class="flex w-[100px] cursor-pointer flex-col"
@click="modelValue = theme.type"
>
<div :class="activeClass(theme.type)" class="outline-box flex-center">
<component :is="components[theme.type]" />
</div>
<div
class="text-muted-foreground flex-center hover:text-foreground mt-2 text-center text-xs"
>
{{ theme.name }}
<VbenTooltip v-if="theme.tip" side="bottom">
<template #trigger>
<MdiQuestionMarkCircleOutline class="ml-1 cursor-help" />
</template>
{{ theme.tip }}
</VbenTooltip>
</div>
</div>
</template>
</div>
</template>

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
import ToggleItem from '../toggle-item.vue';
defineOptions({
name: 'PreferenceNavigationConfig',
});
defineProps<{ disabled?: boolean; disabledNavigationSplit?: boolean }>();
const navigationStyle = defineModel<string>('navigationStyle');
const navigationSplit = defineModel<boolean>('navigationSplit');
const navigationAccordion = defineModel<boolean>('navigationAccordion');
const stylesItems: SelectListItem[] = [
{ label: $t('preference.rounded'), value: 'rounded' },
{ label: $t('preference.plain'), value: 'plain' },
];
</script>
<template>
<ToggleItem
v-model="navigationStyle"
:items="stylesItems"
:disabled="disabled"
>
{{ $t('preference.navigation-style') }}
</ToggleItem>
<SwitchItem
v-model="navigationSplit"
:disabled="disabledNavigationSplit || disabled"
>
{{ $t('preference.navigation-split') }}
<template #tip>
{{ $t('preference.navigation-split-tip') }}
</template>
</SwitchItem>
<SwitchItem v-model="navigationAccordion" :disabled="disabled">
{{ $t('preference.navigation-accordion') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceBreadcrumbConfig',
});
defineProps<{ disabled: boolean }>();
const sideVisible = defineModel<boolean>('sideVisible');
const sideCollapseShowTitle = defineModel<boolean>('sideCollapseShowTitle');
const sideCollapse = defineModel<boolean>('sideCollapse');
</script>
<template>
<SwitchItem v-model="sideVisible" :disabled="disabled">
{{ $t('preference.side-visible') }}
</SwitchItem>
<SwitchItem v-model="sideCollapse" :disabled="!sideVisible || disabled">
{{ $t('preference.collapse') }}
</SwitchItem>
<SwitchItem
v-model="sideCollapseShowTitle"
:disabled="!sideVisible || disabled"
>
{{ $t('preference.collapse-show-title') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceTabsConfig',
});
defineProps<{ disabled?: boolean }>();
const tabsVisible = defineModel<boolean>('tabsVisible');
const tabsIcon = defineModel<boolean>('tabsIcon');
</script>
<template>
<SwitchItem v-model="tabsVisible" :disabled="disabled">
{{ $t('preference.tabs-visible') }}
</SwitchItem>
<SwitchItem v-model="tabsIcon" :disabled="!tabsVisible">
{{ $t('preference.tabs-icon') }}
</SwitchItem>
<!-- <SwitchItem v-model="sideCollapseShowTitle" :disabled="!tabsVisible">
{{ $t('preference.collapse-show-title') }}
</SwitchItem> -->
</template>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { MdiQuestionMarkCircleOutline } from '@vben-core/iconify';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
VbenTooltip,
} from '@vben-core/shadcn-ui';
import { useSlots } from 'vue';
defineOptions({
name: 'PreferenceSelectItem',
});
withDefaults(
defineProps<{
disabled?: boolean;
items?: SelectListItem[];
placeholder?: string;
}>(),
{
disabled: false,
placeholder: '',
items: () => [],
},
);
const selectValue = defineModel<string>();
const slots = useSlots();
</script>
<template>
<div
class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1"
:class="{
'hover:bg-accent': !slots.tip,
'pointer-events-none opacity-50': disabled,
}"
>
<span class="flex items-center text-sm">
<slot></slot>
<VbenTooltip v-if="slots.tip" side="bottom">
<template #trigger>
<MdiQuestionMarkCircleOutline class="ml-1 cursor-help" />
</template>
<slot name="tip"></slot>
</VbenTooltip>
</span>
<Select v-model="selectValue">
<SelectTrigger class="h-7 w-[140px]">
<SelectValue :placeholder="placeholder" />
</SelectTrigger>
<SelectContent>
<template v-for="item in items" :key="item.value">
<SelectItem :value="item.value"> {{ item.label }} </SelectItem>
</template>
</SelectContent>
</Select>
</div>
</template>

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import { MdiQuestionMarkCircleOutline } from '@vben-core/iconify';
import { Switch, VbenTooltip } from '@vben-core/shadcn-ui';
import { useSlots } from 'vue';
defineOptions({
name: 'PreferenceSwitchItem',
});
withDefaults(defineProps<{ disabled?: boolean }>(), {
disabled: false,
});
const checked = defineModel<boolean>();
const slots = useSlots();
function handleClick() {
checked.value = !checked.value;
}
</script>
<template>
<div
class="hover:bg-accent my-1 flex w-full items-center justify-between rounded-md px-2 py-2"
:class="{
'pointer-events-none opacity-50': disabled,
}"
@click="handleClick"
>
<span class="flex items-center text-sm">
<slot></slot>
<VbenTooltip v-if="slots.tip" side="bottom">
<template #trigger>
<MdiQuestionMarkCircleOutline class="ml-1 cursor-help" />
</template>
<slot name="tip"></slot>
</VbenTooltip>
</span>
<Switch v-model:checked="checked" @click.stop />
</div>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceColorMode',
});
const colorWeakMode = defineModel<boolean>('colorWeakMode', {
default: false,
});
const colorGrayMode = defineModel<boolean>('colorGrayMode', {
default: false,
});
</script>
<template>
<SwitchItem v-model="colorWeakMode">
{{ $t('preference.weak-mode') }}
</SwitchItem>
<SwitchItem v-model="colorGrayMode">
{{ $t('preference.gray-mode') }}
</SwitchItem>
</template>

View File

@@ -0,0 +1,91 @@
<script setup lang="ts">
import { MdiEditBoxOutline } from '@vben-core/iconify';
import { TinyColor, convertToHsl } from '@vben-core/toolkit';
import type { CSSProperties } from 'vue';
import { computed, ref, watch, watchEffect } from 'vue';
defineOptions({
name: 'PreferenceColor',
});
const props = withDefaults(defineProps<{ colorPrimaryPresets: string[] }>(), {
colorPrimaryPresets: () => [],
});
const colorInput = ref();
const currentColor = ref(props.colorPrimaryPresets?.[0]);
const modelValue = defineModel<string>();
const activeColor = computed((): CSSProperties => {
return {
outlineColor: currentColor.value,
outlineWidth: '2px',
};
});
function isActive(color: string): string[] {
return color === currentColor.value ? ['outline-box-active'] : [];
}
const inputStyle = computed((): CSSProperties => {
return props.colorPrimaryPresets.includes(currentColor.value)
? {}
: activeColor.value;
});
const inputValue = computed(() => {
return new TinyColor(modelValue.value).toHexString();
});
function selectColor() {
colorInput.value.click();
}
function handleInputChange(e: Event) {
const target = e.target as HTMLInputElement;
modelValue.value = convertToHsl(target.value);
}
// 监听颜色变化,转成系统可识别的 hsl 格式
watch(currentColor, (val) => {
modelValue.value = convertToHsl(val);
});
watchEffect(() => {
if (modelValue.value) {
currentColor.value = modelValue.value;
}
});
</script>
<template>
<div class="flex w-full flex-wrap justify-between">
<template v-for="color in colorPrimaryPresets" :key="color">
<div
:class="isActive(color)"
class="outline-box p-2"
@click="currentColor = color"
>
<div
:style="{ backgroundColor: color }"
class="h-6 w-6 rounded-md"
></div>
</div>
</template>
<div :style="inputStyle" class="outline-box p-2" @click="selectColor">
<div class="flex-center bg-accent relative h-6 w-6 rounded-md">
<MdiEditBoxOutline class="absolute z-10" />
<input
ref="colorInput"
:value="inputValue"
class="absolute inset-0 opacity-0"
type="color"
@input="handleInputChange"
/>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import {
IcRoundMotionPhotosAuto,
IcRoundWbSunny,
MdiMoonAndStars,
} from '@vben-core/iconify';
import { $t } from '@vben/locales';
import SwitchItem from '../switch-item.vue';
defineOptions({
name: 'PreferenceTheme',
});
const modelValue = defineModel<string>({ default: 'auto' });
const semiDarkMenu = defineModel<boolean>('semiDarkMenu', {
default: true,
});
const THEME_PRESET = [
{
icon: IcRoundWbSunny,
name: 'light',
},
{
icon: MdiMoonAndStars,
name: 'dark',
},
{
icon: IcRoundMotionPhotosAuto,
name: 'auto',
},
];
function activeClass(theme: string): string[] {
return theme === modelValue.value ? ['outline-box-active'] : [];
}
function nameView(name: string) {
switch (name) {
case 'light': {
return $t('preference.light');
}
case 'dark': {
return $t('preference.dark');
}
case 'auto': {
return $t('preference.follow-system');
}
}
}
</script>
<template>
<div class="flex w-full flex-wrap justify-between">
<template v-for="theme in THEME_PRESET" :key="theme.name">
<div
class="flex cursor-pointer flex-col"
@click="modelValue = theme.name"
>
<div
:class="activeClass(theme.name)"
class="outline-box flex-center py-4"
>
<component :is="theme.icon" class="mx-9 size-5" />
</div>
<div class="text-muted-foreground mt-2 text-center text-xs">
{{ nameView(theme.name) }}
</div>
</div>
</template>
<SwitchItem
v-model="semiDarkMenu"
:disabled="modelValue !== 'light'"
class="mt-6"
>
{{ $t('preference.dark-menu') }}
</SwitchItem>
</div>
</template>

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import type { SelectListItem } from '@vben/types';
import { ToggleGroup, ToggleGroupItem } from '@vben-core/shadcn-ui';
defineOptions({
name: 'PreferenceToggleItem',
});
withDefaults(defineProps<{ disabled?: boolean; items: SelectListItem[] }>(), {
disabled: false,
items: () => [],
});
const modelValue = defineModel<string>();
</script>
<template>
<div
disabled
class="hover:bg-accent flex w-full items-center justify-between rounded-md px-2 py-2"
:class="{
'pointer-events-none opacity-50': disabled,
}"
>
<span class="text-sm"><slot></slot></span>
<ToggleGroup
v-model="modelValue"
type="single"
variant="outline"
size="sm"
class="gap-2"
>
<template v-for="item in items" :key="item.value">
<ToggleGroupItem
:value="item.value"
class="data-[state=on]:bg-primary data-[state=on]:text-primary-foreground h-7 rounded-sm"
>
{{ item.label }}
</ToggleGroupItem>
</template>
</ToggleGroup>
</div>
</template>