refactor(adapter): separate form and component adapters so that components adapt to components other than the form (#4628)

* refactor: global components can be customized

* refactor: remove use Toast and reconstruct the form adapter
This commit is contained in:
Vben
2024-10-13 18:33:43 +08:00
committed by GitHub
parent 8b961a9d7f
commit 24d14c2841
63 changed files with 1798 additions and 2136 deletions

View File

@@ -94,7 +94,6 @@
}
/* 只有非mac下才进行调整mac下使用默认滚动条 */
html:not([data-platform='macOs']) {
::-webkit-scrollbar {
@apply h-[10px] w-[10px];

View File

@@ -1,4 +1,4 @@
import './default/index.css';
import './dark/index.css';
import './default.css';
import './dark.css';
export {};

View File

@@ -9,5 +9,6 @@ export default defineBuildConfig({
'src/utils/index',
'src/color/index',
'src/cache/index',
'src/global-state',
],
});

View File

@@ -11,7 +11,8 @@
"license": "MIT",
"type": "module",
"scripts": {
"build": "pnpm unbuild"
"build": "pnpm unbuild",
"stub": "pnpm unbuild --stub"
},
"files": [
"dist"
@@ -42,6 +43,11 @@
"types": "./src/store.ts",
"development": "./src/store.ts",
"default": "./dist/store.mjs"
},
"./global-state": {
"types": "./dist/global-state.d.ts",
"development": "./src/global-state.ts",
"default": "./dist/global-state.mjs"
}
},
"publishConfig": {
@@ -63,8 +69,12 @@
"default": "./dist/cache/index.mjs"
},
"./store": {
"types": "./dist/store/index.d.ts",
"types": "./dist/store.d.ts",
"default": "./dist/store.mjs"
},
"./global-state": {
"types": "./dist/global-state.d.ts",
"default": "./dist/global-state.mjs"
}
}
},

View File

@@ -0,0 +1,45 @@
/**
* 全局复用的变量、组件、配置,各个模块之间共享
* 通过单例模式实现,单例必须注意不受请求影响例如用户信息这些需要根据请求获取的。后续如果有ssr需求也不会影响
*/
interface ComponentsState {
[key: string]: any;
}
interface MessageState {
copyPreferencesSuccess?: (title: string, content?: string) => void;
}
export interface IGlobalSharedState {
components: ComponentsState;
message: MessageState;
}
class GlobalShareState {
#components: ComponentsState = {};
#message: MessageState = {};
/**
* 定义框架内部各个场景的消息提示
*/
public defineMessage({ copyPreferencesSuccess }: MessageState) {
this.#message = {
copyPreferencesSuccess,
};
}
public getComponents(): ComponentsState {
return this.#components;
}
public getMessage(): MessageState {
return this.#message;
}
public setComponents(value: ComponentsState) {
this.#components = value;
}
}
export const globalShareState = new GlobalShareState();

View File

@@ -10,13 +10,6 @@ function useSortable<T extends HTMLElement>(
// @ts-expect-error - This is a dynamic import
'sortablejs/modular/sortable.complete.esm.js'
);
// const { AutoScroll } = await import(
// // @ts-expect-error - This is a dynamic import
// 'sortablejs/modular/sortable.core.esm.js'
// );
// Sortable?.default?.mount?.(AutoScroll);
const sortable = Sortable?.default?.create?.(sortableContainer, {
animation: 300,
delay: 400,

View File

@@ -84,7 +84,7 @@ watch(
:style="queryFormStyle"
>
<component
:is="COMPONENT_MAP.DefaultResetActionButton"
:is="COMPONENT_MAP.DefaultButton"
v-if="resetButtonOptions.show"
class="mr-3"
type="button"
@@ -95,7 +95,7 @@ watch(
</component>
<component
:is="COMPONENT_MAP.DefaultSubmitActionButton"
:is="COMPONENT_MAP.PrimaryButton"
v-if="submitButtonOptions.show"
type="button"
@click="handleSubmit"

View File

@@ -15,6 +15,7 @@ import {
VbenPinInput,
VbenSelect,
} from '@vben-core/shadcn-ui';
import { globalShareState } from '@vben-core/shared/global-state';
import { defineRule } from 'vee-validate';
@@ -23,8 +24,8 @@ const DEFAULT_MODEL_PROP_NAME = 'modelValue';
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
DefaultButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
PrimaryButton: h(VbenButton, { size: 'sm', variant: 'default' }),
VbenCheckbox,
VbenInput,
VbenInputPassword,
@@ -41,7 +42,7 @@ export const COMPONENT_BIND_EVENT_MAP: Partial<
export function setupVbenForm<
T extends BaseFormComponentType = BaseFormComponentType,
>(options: VbenFormAdapterOptions<T>) {
const { components, config, defineRules } = options;
const { config, defineRules } = options;
const { disabledOnChangeListener = false, emptyStateValue = undefined } =
(config || {}) as FormCommonConfig;
@@ -63,6 +64,8 @@ export function setupVbenForm<
| Record<BaseFormComponentType, string>
| undefined;
const components = globalShareState.getComponents();
for (const component of Object.keys(components)) {
const key = component as BaseFormComponentType;
COMPONENT_MAP[key] = components[component as never];

View File

@@ -9,8 +9,8 @@ import type { Component, HtmlHTMLAttributes, Ref } from 'vue';
export type FormLayout = 'horizontal' | 'vertical';
export type BaseFormComponentType =
| 'DefaultResetActionButton'
| 'DefaultSubmitActionButton'
| 'DefaultButton'
| 'PrimaryButton'
| 'VbenCheckbox'
| 'VbenInput'
| 'VbenInputPassword'
@@ -341,7 +341,6 @@ export type ExtendedFormApi = {
export interface VbenFormAdapterOptions<
T extends BaseFormComponentType = BaseFormComponentType,
> {
components: Partial<Record<T, Component>>;
config?: {
baseModelPropName?: string;
disabledOnChangeListener?: boolean;

View File

@@ -23,6 +23,7 @@ import {
VbenLoading,
VisuallyHidden,
} from '@vben-core/shadcn-ui';
import { globalShareState } from '@vben-core/shared/global-state';
import { cn } from '@vben-core/shared/utils';
interface Props extends DrawerProps {
@@ -33,6 +34,8 @@ const props = withDefaults(defineProps<Props>(), {
drawerApi: undefined,
});
const components = globalShareState.getComponents();
const id = useId();
provide('DISMISSABLE_DRAWER_ID', id);
@@ -187,7 +190,8 @@ function handleFocusOutside(e: Event) {
>
<slot name="prepend-footer"></slot>
<slot name="footer">
<VbenButton
<component
:is="components.DefaultButton || VbenButton"
v-if="showCancelButton"
variant="ghost"
@click="() => drawerApi?.onCancel()"
@@ -195,8 +199,10 @@ function handleFocusOutside(e: Event) {
<slot name="cancelText">
{{ cancelText || $t('cancel') }}
</slot>
</VbenButton>
<VbenButton
</component>
<component
:is="components.PrimaryButton || VbenButton"
v-if="showConfirmButton"
:loading="confirmLoading"
@click="() => drawerApi?.onConfirm()"
@@ -204,7 +210,7 @@ function handleFocusOutside(e: Event) {
<slot name="confirmText">
{{ confirmText || $t('confirm') }}
</slot>
</VbenButton>
</component>
</slot>
<slot name="append-footer"></slot>
</SheetFooter>

View File

@@ -22,6 +22,7 @@ import {
VbenLoading,
VisuallyHidden,
} from '@vben-core/shadcn-ui';
import { globalShareState } from '@vben-core/shared/global-state';
import { cn } from '@vben-core/shared/utils';
import { useModalDraggable } from './use-modal-draggable';
@@ -34,6 +35,8 @@ const props = withDefaults(defineProps<Props>(), {
modalApi: undefined,
});
const components = globalShareState.getComponents();
const contentRef = ref();
const wrapperRef = ref<HTMLElement>();
const dialogRef = ref();
@@ -256,7 +259,8 @@ function handleFocusOutside(e: Event) {
>
<slot name="prepend-footer"></slot>
<slot name="footer">
<VbenButton
<component
:is="components.DefaultButton || VbenButton"
v-if="showCancelButton"
variant="ghost"
@click="() => modalApi?.onCancel()"
@@ -264,8 +268,10 @@ function handleFocusOutside(e: Event) {
<slot name="cancelText">
{{ cancelText || $t('cancel') }}
</slot>
</VbenButton>
<VbenButton
</component>
<component
:is="components.PrimaryButton || VbenButton"
v-if="showConfirmButton"
:loading="confirmLoading"
@click="() => modalApi?.onConfirm()"
@@ -273,7 +279,7 @@ function handleFocusOutside(e: Event) {
<slot name="confirmText">
{{ confirmText || $t('confirm') }}
</slot>
</VbenButton>
</component>
</slot>
<slot name="append-footer"></slot>
</DialogFooter>

View File

@@ -23,7 +23,6 @@ export * from './sheet';
export * from './switch';
export * from './tabs';
export * from './textarea';
export * from './toast';
export * from './toggle';
export * from './toggle-group';
export * from './tooltip';

View File

@@ -1,35 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import {
ToastRoot,
type ToastRootEmits,
useForwardPropsEmits,
} from 'radix-vue';
import { type ToastProps, toastVariants } from './toast';
const props = defineProps<ToastProps>();
const emits = defineEmits<ToastRootEmits>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<ToastRoot
v-bind="forwarded"
:class="cn(toastVariants({ variant }), props.class)"
@update:open="onOpenChange"
>
<slot></slot>
</ToastRoot>
</template>

View File

@@ -1,29 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ToastAction, type ToastActionProps } from 'radix-vue';
const props = defineProps<{ class?: any } & ToastActionProps>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<ToastAction
v-bind="delegatedProps"
:class="
cn(
'hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive border-border inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:outline-none focus:ring-1 disabled:pointer-events-none disabled:opacity-50',
props.class,
)
"
>
<slot></slot>
</ToastAction>
</template>

View File

@@ -1,34 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { X } from 'lucide-vue-next';
import { ToastClose, type ToastCloseProps } from 'radix-vue';
const props = defineProps<
{
class?: any;
} & ToastCloseProps
>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<ToastClose
v-bind="delegatedProps"
:class="
cn(
'text-foreground/50 hover:text-foreground absolute right-1 top-1 rounded-md p-1 opacity-0 transition-opacity focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
props.class,
)
"
>
<X class="size-4" />
</ToastClose>
</template>

View File

@@ -1,24 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ToastDescription, type ToastDescriptionProps } from 'radix-vue';
const props = defineProps<{ class?: any } & ToastDescriptionProps>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<ToastDescription
:class="cn('text-sm opacity-90', props.class)"
v-bind="delegatedProps"
>
<slot></slot>
</ToastDescription>
</template>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
import { ToastProvider, type ToastProviderProps } from 'radix-vue';
const props = defineProps<ToastProviderProps>();
</script>
<template>
<ToastProvider v-bind="props">
<slot></slot>
</ToastProvider>
</template>

View File

@@ -1,24 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ToastTitle, type ToastTitleProps } from 'radix-vue';
const props = defineProps<{ class?: any } & ToastTitleProps>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<ToastTitle
v-bind="delegatedProps"
:class="cn('text-sm font-semibold [&+div]:text-xs', props.class)"
>
<slot></slot>
</ToastTitle>
</template>

View File

@@ -1,27 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ToastViewport, type ToastViewportProps } from 'radix-vue';
const props = defineProps<{ class?: any } & ToastViewportProps>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<ToastViewport
v-bind="delegatedProps"
:class="
cn(
'fixed top-0 z-[1200] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
props.class,
)
"
/>
</template>

View File

@@ -1,36 +0,0 @@
<script setup lang="ts">
import { isVNode } from 'vue';
import Toast from './Toast.vue';
import ToastClose from './ToastClose.vue';
import ToastDescription from './ToastDescription.vue';
import ToastProvider from './ToastProvider.vue';
import ToastTitle from './ToastTitle.vue';
import ToastViewport from './ToastViewport.vue';
import { useToast } from './use-toast';
const { toasts } = useToast();
</script>
<template>
<ToastProvider swipe-direction="down">
<Toast v-for="toast in toasts" :key="toast.id" v-bind="toast">
<div class="grid gap-1">
<ToastTitle v-if="toast.title">
{{ toast.title }}
</ToastTitle>
<template v-if="toast.description">
<ToastDescription v-if="isVNode(toast.description)">
<component :is="toast.description" />
</ToastDescription>
<ToastDescription v-else>
{{ toast.description }}
</ToastDescription>
</template>
<ToastClose />
</div>
<component :is="toast.action" />
</Toast>
<ToastViewport />
</ToastProvider>
</template>

View File

@@ -1,11 +0,0 @@
export * from './toast';
export { default as Toast } from './Toast.vue';
export { default as ToastAction } from './ToastAction.vue';
export { default as ToastClose } from './ToastClose.vue';
export { default as ToastDescription } from './ToastDescription.vue';
export { default as Toaster } from './Toaster.vue';
export { default as ToastProvider } from './ToastProvider.vue';
export { default as ToastTitle } from './ToastTitle.vue';
export { default as ToastViewport } from './ToastViewport.vue';
export { toast, useToast } from './use-toast';

View File

@@ -1,27 +0,0 @@
import type { ToastRootProps } from 'radix-vue';
import { cva, type VariantProps } from 'class-variance-authority';
export const toastVariants = cva(
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
{
defaultVariants: {
variant: 'default',
},
variants: {
variant: {
default: 'border bg-background border-border text-foreground',
destructive:
'destructive group border-destructive bg-destructive text-destructive-foreground',
},
},
},
);
type ToastVariants = VariantProps<typeof toastVariants>;
export interface ToastProps extends ToastRootProps {
class?: any;
onOpenChange?: ((value: boolean) => void) | undefined;
variant?: ToastVariants['variant'];
}

View File

@@ -1,168 +0,0 @@
import type { ToastProps } from './toast';
import { computed, ref } from 'vue';
import type { Component, VNode } from 'vue';
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1_000_000;
export type StringOrVNode = (() => VNode) | string | VNode;
type ToasterToast = {
action?: Component;
description?: StringOrVNode;
id: string;
title?: string;
} & ToastProps;
const actionTypes = {
ADD_TOAST: 'ADD_TOAST',
DISMISS_TOAST: 'DISMISS_TOAST',
REMOVE_TOAST: 'REMOVE_TOAST',
UPDATE_TOAST: 'UPDATE_TOAST',
} as const;
let count = 0;
function genId() {
count = (count + 1) % Number.MAX_VALUE;
return count.toString();
}
type ActionType = typeof actionTypes;
type Action =
| {
toast: Partial<ToasterToast>;
type: ActionType['UPDATE_TOAST'];
}
| {
toast: ToasterToast;
type: ActionType['ADD_TOAST'];
}
| {
toastId?: ToasterToast['id'];
type: ActionType['DISMISS_TOAST'];
}
| {
toastId?: ToasterToast['id'];
type: ActionType['REMOVE_TOAST'];
};
interface State {
toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
function addToRemoveQueue(toastId: string) {
if (toastTimeouts.has(toastId)) return;
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({
toastId,
type: actionTypes.REMOVE_TOAST,
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);
}
const state = ref<State>({
toasts: [],
});
function dispatch(action: Action) {
switch (action.type) {
case actionTypes.ADD_TOAST: {
state.value.toasts = [action.toast, ...state.value.toasts].slice(
0,
TOAST_LIMIT,
);
break;
}
case actionTypes.UPDATE_TOAST: {
state.value.toasts = state.value.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t,
);
break;
}
case actionTypes.DISMISS_TOAST: {
const { toastId } = action;
if (toastId) {
addToRemoveQueue(toastId);
} else {
state.value.toasts.forEach((toast) => {
addToRemoveQueue(toast.id);
});
}
state.value.toasts = state.value.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t,
);
break;
}
case actionTypes.REMOVE_TOAST: {
state.value.toasts =
action.toastId === undefined
? []
: state.value.toasts.filter((t) => t.id !== action.toastId);
break;
}
}
}
function useToast() {
return {
dismiss: (toastId?: string) =>
dispatch({ toastId, type: actionTypes.DISMISS_TOAST }),
toast,
toasts: computed(() => state.value.toasts),
};
}
type Toast = Omit<ToasterToast, 'id'>;
function toast(props: Toast) {
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
toast: { ...props, id },
type: actionTypes.UPDATE_TOAST,
});
const dismiss = () =>
dispatch({ toastId: id, type: actionTypes.DISMISS_TOAST });
dispatch({
toast: {
...props,
id,
onOpenChange: (open: boolean) => {
if (!open) dismiss();
},
open: true,
},
type: actionTypes.ADD_TOAST,
});
return {
dismiss,
id,
update,
};
}
export { toast, useToast };

View File

@@ -13,3 +13,5 @@ export {
VbenPinInput,
VbenSpinner,
} from '@vben-core/shadcn-ui';
export { globalShareState } from '@vben-core/shared/global-state';

View File

@@ -1,3 +1,2 @@
export * from './components';
export * from './ui';
export { useToast } from '@vben-core/shadcn-ui';

View File

@@ -13,7 +13,7 @@ import {
import { useLockStore } from '@vben/stores';
import { deepToRaw, mapTree } from '@vben/utils';
import { VbenAdminLayout } from '@vben-core/layout-ui';
import { Toaster, VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
import { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
import { Breadcrumb, CheckUpdates, Preferences } from '../widgets';
import { LayoutContent, LayoutContentSpinner } from './content';
@@ -312,7 +312,6 @@ const headerSlots = computed(() => {
<template #extra>
<slot name="extra"></slot>
<Toaster />
<CheckUpdates
v-if="preferences.app.enableCheckUpdates"
:check-updates-interval="preferences.app.checkUpdatesInterval"

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { h, onMounted, onUnmounted, ref } from 'vue';
import { onMounted, onUnmounted, ref } from 'vue';
import { $t } from '@vben/locales';
import { ToastAction, useToast } from '@vben-core/shadcn-ui';
import { useVbenModal } from '@vben-core/popup-ui';
interface Props {
// 轮训时间,分钟
@@ -18,10 +18,21 @@ const props = withDefaults(defineProps<Props>(), {
checkUpdateUrl: import.meta.env.BASE_URL || '/',
});
const lastVersionTag = ref('');
let isCheckingUpdates = false;
const currentVersionTag = ref('');
const lastVersionTag = ref('');
const timer = ref<ReturnType<typeof setInterval>>();
const { toast } = useToast();
const [UpdateNoticeModal, modalApi] = useVbenModal({
closable: false,
closeOnPressEscape: false,
closeOnClickModal: false,
onConfirm() {
lastVersionTag.value = currentVersionTag.value;
window.location.reload();
// handleSubmitLogout();
},
});
async function getVersionTag() {
try {
@@ -63,38 +74,8 @@ async function checkForUpdates() {
}
}
function handleNotice(versionTag: string) {
const { dismiss } = toast({
action: h('div', { class: 'inline-flex items-center' }, [
h(
ToastAction,
{
altText: $t('common.cancel'),
onClick: () => dismiss(),
},
{
default: () => $t('common.cancel'),
},
),
h(
ToastAction,
{
altText: $t('common.refresh'),
class:
'bg-primary text-primary-foreground hover:bg-primary-hover mx-1',
onClick: () => {
lastVersionTag.value = versionTag;
window.location.reload();
},
},
{
default: () => $t('common.refresh'),
},
),
]),
description: $t('widgets.checkUpdatesDescription'),
duration: 0,
title: $t('widgets.checkUpdatesTitle'),
});
currentVersionTag.value = versionTag;
modalApi.open();
}
function start() {
@@ -138,5 +119,16 @@ onUnmounted(() => {
});
</script>
<template>
<slot></slot>
<UpdateNoticeModal
:cancel-text="$t('common.cancel')"
:confirm-text="$t('common.refresh')"
:fullscreen-button="false"
:title="$t('widgets.checkUpdatesTitle')"
centered
content-class="px-8 min-h-10"
footer-class="border-none mb-3 mr-3"
header-class="border-none"
>
{{ $t('widgets.checkUpdatesDescription') }}
</UpdateNoticeModal>
</template>

View File

@@ -24,11 +24,11 @@ import {
} from '@vben/preferences';
import { useVbenDrawer } from '@vben-core/popup-ui';
import {
useToast,
VbenButton,
VbenIconButton,
VbenSegmented,
} from '@vben-core/shadcn-ui';
import { globalShareState } from '@vben-core/shared/global-state';
import { useClipboard } from '@vueuse/core';
@@ -54,7 +54,9 @@ import {
} from './blocks';
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
const { toast } = useToast();
const message = globalShareState.getMessage();
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
const appLayout = defineModel<LayoutType>('appLayout');
@@ -196,10 +198,10 @@ const showBreadcrumbConfig = computed(() => {
async function handleCopy() {
await copy(JSON.stringify(diffPreference.value, null, 2));
toast({
description: $t('preferences.copyPreferences'),
title: $t('preferences.copyPreferencesSuccess'),
});
message.copyPreferencesSuccess?.(
$t('preferences.copyPreferencesSuccessTitle'),
$t('preferences.copyPreferencesSuccess'),
);
}
async function handleClearCache() {
@@ -214,10 +216,6 @@ async function handleReset() {
}
resetPreferences();
await loadLocaleMessages(preferences.app.locale);
toast({
description: $t('preferences.resetTitle'),
title: $t('preferences.resetSuccess'),
});
}
</script>

View File

@@ -149,7 +149,7 @@ if (enableShortcutKey.value) {
:title="$t('common.prompt')"
centered
content-class="px-8 min-h-10"
footer-class="border-none mb-4 mr-4"
footer-class="border-none mb-3 mr-3"
header-class="border-none"
>
{{ $t('widgets.logoutTip') }}

View File

@@ -35,7 +35,7 @@ function loadLocalesMap(modules: Record<string, () => Promise<unknown>>) {
const localesMap: Record<Locale, ImportLocaleFn> = {};
for (const [path, loadLocale] of Object.entries(modules)) {
const key = path.match(/([\w-]*)\.(yaml|yml|json)/)?.[1];
const key = path.match(/([\w-]*)\.(json)/)?.[1];
if (key) {
localesMap[key] = loadLocale as ImportLocaleFn;
}

View File

@@ -178,6 +178,7 @@
"plain": "Plain",
"rounded": "Rounded",
"copyPreferences": "Copy Preferences",
"copyPreferencesSuccessTitle": "Copy successful",
"copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
"clearAndLogout": "Clear Cache & Logout",
"mode": "Mode",

View File

@@ -178,6 +178,7 @@
"plain": "朴素",
"rounded": "圆润",
"copyPreferences": "复制偏好设置",
"copyPreferencesSuccessTitle": "复制成功",
"copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
"clearAndLogout": "清空缓存 & 退出登录",
"mode": "模式",

View File

@@ -523,7 +523,8 @@ function isAffixTab(tab: TabDefinition) {
* @param tab
*/
function isTabShown(tab: TabDefinition) {
return !tab.meta.hideInTab;
const matched = tab?.matched ?? [];
return !tab.meta.hideInTab && matched.every((item) => !item.meta.hideInTab);
}
/**

View File

@@ -37,7 +37,6 @@ export function resetAllStores() {
return;
}
const allStores = (pinia as any)._s;
for (const [_key, store] of allStores) {
store.$reset();
}