mirror of
https://github.com/imdap/ruoyi-plus-vben5.git
synced 2026-05-09 03:51:25 +08:00
Merge branch 'main' into main
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
CustomPreferencesField,
|
||||
CustomPreferencesRecord,
|
||||
} from '@vben/preferences';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import InputItem from '../input-item.vue';
|
||||
import NumberFieldItem from '../number-field-item.vue';
|
||||
import SelectItem from '../select-item.vue';
|
||||
import SwitchItem from '../switch-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceCustomFields',
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
fields: Array<CustomPreferencesField>;
|
||||
values: CustomPreferencesRecord;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
update: [updates: CustomPreferencesRecord];
|
||||
}>();
|
||||
|
||||
function handleUpdate(key: string, value: boolean | number | string) {
|
||||
emit('update', { [key]: value });
|
||||
}
|
||||
|
||||
function handleBooleanUpdate(key: string, value: boolean | undefined) {
|
||||
handleUpdate(key, value ?? false);
|
||||
}
|
||||
|
||||
function resolveNumberValue(value: unknown) {
|
||||
return typeof value === 'number' && Number.isFinite(value)
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function handleNumberUpdate(key: string, value: number | undefined) {
|
||||
const resolvedValue = resolveNumberValue(value);
|
||||
|
||||
if (resolvedValue !== undefined) {
|
||||
handleUpdate(key, resolvedValue);
|
||||
}
|
||||
}
|
||||
|
||||
function handleStringUpdate(key: string, value: string | undefined) {
|
||||
handleUpdate(key, value ?? '');
|
||||
}
|
||||
|
||||
const resolvedFields = computed(() => {
|
||||
return props.fields.map((field) => {
|
||||
return {
|
||||
...field,
|
||||
label: $t(field.label),
|
||||
options:
|
||||
field.component === 'select'
|
||||
? field.options.map((option) => ({
|
||||
...option,
|
||||
label: $t(option.label),
|
||||
}))
|
||||
: undefined,
|
||||
placeholder: field.placeholder ? $t(field.placeholder) : '',
|
||||
tip: field.tip ? $t(field.tip) : '',
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-for="field in resolvedFields" :key="field.key">
|
||||
<SwitchItem
|
||||
v-if="field.component === 'switch'"
|
||||
:disabled="field.disabled"
|
||||
:model-value="Boolean(values[field.key])"
|
||||
:tip="field.tip"
|
||||
v-bind="field.componentProps"
|
||||
@update:model-value="handleBooleanUpdate(field.key, $event)"
|
||||
>
|
||||
{{ field.label }}
|
||||
</SwitchItem>
|
||||
<NumberFieldItem
|
||||
v-else-if="field.component === 'number'"
|
||||
:disabled="field.disabled"
|
||||
:model-value="resolveNumberValue(values[field.key])"
|
||||
:placeholder="field.placeholder"
|
||||
:tip="field.tip"
|
||||
v-bind="field.componentProps"
|
||||
@update:model-value="handleNumberUpdate(field.key, $event)"
|
||||
>
|
||||
{{ field.label }}
|
||||
</NumberFieldItem>
|
||||
<SelectItem
|
||||
v-else-if="field.component === 'select'"
|
||||
:disabled="field.disabled"
|
||||
:items="field.options"
|
||||
:model-value="String(values[field.key] ?? '')"
|
||||
:placeholder="field.placeholder"
|
||||
:tip="field.tip"
|
||||
v-bind="field.componentProps"
|
||||
@update:model-value="handleStringUpdate(field.key, $event)"
|
||||
>
|
||||
{{ field.label }}
|
||||
</SelectItem>
|
||||
<InputItem
|
||||
v-else
|
||||
:disabled="field.disabled"
|
||||
:model-value="String(values[field.key] ?? '')"
|
||||
:placeholder="field.placeholder"
|
||||
:tip="field.tip"
|
||||
v-bind="field.componentProps"
|
||||
@update:model-value="handleStringUpdate(field.key, $event)"
|
||||
>
|
||||
{{ field.label }}
|
||||
</InputItem>
|
||||
</template>
|
||||
</template>
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as Block } from './block.vue';
|
||||
export { default as Custom } from './custom/custom.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';
|
||||
|
||||
@@ -16,10 +16,12 @@ withDefaults(
|
||||
disabled?: boolean;
|
||||
items?: SelectOption[];
|
||||
placeholder?: string;
|
||||
tip?: string;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
placeholder: '',
|
||||
tip: '',
|
||||
items: () => [],
|
||||
},
|
||||
);
|
||||
@@ -32,7 +34,7 @@ const slots = useSlots();
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'hover:bg-accent': !slots.tip,
|
||||
'hover:bg-accent': !(slots.tip || tip),
|
||||
'pointer-events-none opacity-50': disabled,
|
||||
}"
|
||||
class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1"
|
||||
@@ -40,11 +42,17 @@ const slots = useSlots();
|
||||
<span class="flex items-center text-sm">
|
||||
<slot></slot>
|
||||
|
||||
<VbenTooltip v-if="slots.tip" side="bottom">
|
||||
<VbenTooltip v-if="slots.tip || tip" side="bottom">
|
||||
<template #trigger>
|
||||
<CircleHelp class="ml-1 size-3 cursor-help" />
|
||||
</template>
|
||||
<slot name="tip"></slot>
|
||||
<slot name="tip">
|
||||
<template v-if="tip">
|
||||
<p v-for="(line, index) in tip.split('\n')" :key="index">
|
||||
{{ line }}
|
||||
</p>
|
||||
</template>
|
||||
</slot>
|
||||
</VbenTooltip>
|
||||
</span>
|
||||
<div class="relative">
|
||||
|
||||
@@ -23,10 +23,12 @@ withDefaults(
|
||||
disabled?: boolean;
|
||||
items?: SelectOption[];
|
||||
placeholder?: string;
|
||||
tip?: string;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
placeholder: '',
|
||||
tip: '',
|
||||
items: () => [],
|
||||
},
|
||||
);
|
||||
@@ -39,7 +41,7 @@ const slots = useSlots();
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'hover:bg-accent': !slots.tip,
|
||||
'hover:bg-accent': !(slots.tip || tip),
|
||||
'pointer-events-none opacity-50': disabled,
|
||||
}"
|
||||
class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1"
|
||||
@@ -47,11 +49,17 @@ const slots = useSlots();
|
||||
<span class="flex items-center text-sm">
|
||||
<slot></slot>
|
||||
|
||||
<VbenTooltip v-if="slots.tip" side="bottom">
|
||||
<VbenTooltip v-if="slots.tip || tip" side="bottom">
|
||||
<template #trigger>
|
||||
<CircleHelp class="ml-1 size-3 cursor-help" />
|
||||
</template>
|
||||
<slot name="tip"></slot>
|
||||
<slot name="tip">
|
||||
<template v-if="tip">
|
||||
<p v-for="(line, index) in tip.split('\n')" :key="index">
|
||||
{{ line }}
|
||||
</p>
|
||||
</template>
|
||||
</slot>
|
||||
</VbenTooltip>
|
||||
</span>
|
||||
<Select v-model="selectValue">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { SupportedLanguagesType } from '@vben/locales';
|
||||
import type { CustomPreferencesRecord } from '@vben/preferences';
|
||||
import type {
|
||||
BreadcrumbStyleType,
|
||||
BuiltinThemeType,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
clearCache,
|
||||
preferences,
|
||||
resetPreferences,
|
||||
updateCustomPreferences,
|
||||
usePreferences,
|
||||
} from '@vben/preferences';
|
||||
|
||||
@@ -43,6 +45,7 @@ import {
|
||||
ColorMode,
|
||||
Content,
|
||||
Copyright,
|
||||
Custom,
|
||||
FontSize,
|
||||
Footer,
|
||||
General,
|
||||
@@ -177,12 +180,15 @@ const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
|
||||
const widgetRefresh = defineModel<boolean>('widgetRefresh');
|
||||
|
||||
const {
|
||||
customPreferences,
|
||||
diffCustomPreference,
|
||||
diffPreference,
|
||||
isDark,
|
||||
isFullContent,
|
||||
isHeaderNav,
|
||||
isHeaderSidebarNav,
|
||||
isMixedNav,
|
||||
preferencesExtension,
|
||||
isSideMixedNav,
|
||||
isSideMode,
|
||||
isSideNav,
|
||||
@@ -193,8 +199,42 @@ const [Drawer] = useVbenDrawer();
|
||||
|
||||
const activeTab = ref('appearance');
|
||||
|
||||
const customPreferencesTab = computed(() => {
|
||||
return preferencesExtension.value;
|
||||
});
|
||||
|
||||
const customTabLabel = computed(() => {
|
||||
return customPreferencesTab.value?.tabLabel
|
||||
? $t(customPreferencesTab.value.tabLabel)
|
||||
: '';
|
||||
});
|
||||
|
||||
const customTabTitle = computed(() => {
|
||||
const title =
|
||||
customPreferencesTab.value?.title || customPreferencesTab.value?.tabLabel;
|
||||
return title ? $t(title) : '';
|
||||
});
|
||||
|
||||
const mergedDiffPreference = computed(() => {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
if (diffPreference.value) {
|
||||
Object.assign(result, diffPreference.value);
|
||||
}
|
||||
|
||||
if (diffCustomPreference.value) {
|
||||
result.custom = diffCustomPreference.value;
|
||||
}
|
||||
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
});
|
||||
|
||||
const showCustomTab = computed(() => {
|
||||
return (customPreferencesTab.value?.fields.length ?? 0) > 0;
|
||||
});
|
||||
|
||||
const tabs = computed((): SegmentedItem[] => {
|
||||
return [
|
||||
const items: SegmentedItem[] = [
|
||||
{
|
||||
label: $t('preferences.appearance'),
|
||||
value: 'appearance',
|
||||
@@ -212,6 +252,15 @@ const tabs = computed((): SegmentedItem[] => {
|
||||
value: 'general',
|
||||
},
|
||||
];
|
||||
|
||||
if (showCustomTab.value) {
|
||||
items.push({
|
||||
label: customTabLabel.value,
|
||||
value: 'custom',
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
const showBreadcrumbConfig = computed(() => {
|
||||
@@ -224,7 +273,7 @@ const showBreadcrumbConfig = computed(() => {
|
||||
});
|
||||
|
||||
async function handleCopy() {
|
||||
await copy(JSON.stringify(diffPreference.value, null, 2));
|
||||
await copy(JSON.stringify(mergedDiffPreference.value, null, 2));
|
||||
|
||||
message.copyPreferencesSuccess?.(
|
||||
$t('preferences.copyPreferencesSuccessTitle'),
|
||||
@@ -239,12 +288,16 @@ async function handleClearCache() {
|
||||
}
|
||||
|
||||
async function handleReset() {
|
||||
if (!diffPreference.value) {
|
||||
if (!mergedDiffPreference.value) {
|
||||
return;
|
||||
}
|
||||
resetPreferences();
|
||||
await loadLocaleMessages(preferences.app.locale);
|
||||
}
|
||||
|
||||
function handleCustomPreferencesUpdate(updates: CustomPreferencesRecord) {
|
||||
updateCustomPreferences(updates);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -257,13 +310,13 @@ async function handleReset() {
|
||||
<template #extra>
|
||||
<div class="flex items-center">
|
||||
<VbenIconButton
|
||||
:disabled="!diffPreference"
|
||||
:disabled="!mergedDiffPreference"
|
||||
:tooltip="$t('preferences.resetTip')"
|
||||
class="relative"
|
||||
@click="handleReset"
|
||||
>
|
||||
<span
|
||||
v-if="diffPreference"
|
||||
v-if="mergedDiffPreference"
|
||||
class="absolute top-0.5 right-0.5 size-2 rounded-sm bg-primary"
|
||||
></span>
|
||||
<RotateCw class="size-4" />
|
||||
@@ -466,13 +519,22 @@ async function handleReset() {
|
||||
/>
|
||||
</Block>
|
||||
</template>
|
||||
<template #custom>
|
||||
<Block :title="customTabTitle">
|
||||
<Custom
|
||||
:fields="customPreferencesTab?.fields || []"
|
||||
:values="customPreferences"
|
||||
@update="handleCustomPreferencesUpdate"
|
||||
/>
|
||||
</Block>
|
||||
</template>
|
||||
</VbenSegmented>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<VbenButton
|
||||
v-if="appEnableCopyPreferences"
|
||||
:disabled="!diffPreference"
|
||||
:disabled="!mergedDiffPreference"
|
||||
class="mx-4 w-full"
|
||||
size="sm"
|
||||
variant="default"
|
||||
@@ -482,7 +544,7 @@ async function handleReset() {
|
||||
{{ $t('preferences.copyPreferences') }}
|
||||
</VbenButton>
|
||||
<VbenButton
|
||||
:disabled="!diffPreference"
|
||||
:disabled="!mergedDiffPreference"
|
||||
class="mr-4 w-full"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
|
||||
Reference in New Issue
Block a user