Merge branch 'vbenjs:main' into main
This commit is contained in:
@@ -43,18 +43,20 @@ const close = () => {
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const handleViewAll = () => {
|
||||
function handleViewAll() {
|
||||
emit('viewAll');
|
||||
close();
|
||||
};
|
||||
}
|
||||
|
||||
const handleMakeAll = () => {
|
||||
function handleMakeAll() {
|
||||
emit('makeAll');
|
||||
};
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
function handleClear() {
|
||||
emit('clear');
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({ toggle });
|
||||
</script>
|
||||
<template>
|
||||
<VbenPopover v-model:open="open" content-class="relative right-2 w-90 p-0">
|
||||
|
||||
@@ -8,6 +8,8 @@ import { $t } from '@vben/locales';
|
||||
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
import SelectItem from './select-item.vue';
|
||||
|
||||
interface Item {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -67,14 +69,13 @@ onUnmounted(() => {
|
||||
sortableInstance = null;
|
||||
});
|
||||
|
||||
function setPosition(key: string, event: Event) {
|
||||
const value = (event.target as HTMLSelectElement).value as
|
||||
| 'auto'
|
||||
| 'fixed'
|
||||
| 'header'
|
||||
| 'none'
|
||||
| 'user-dropdown';
|
||||
emit('updatePosition', key, value);
|
||||
function setPosition(key: string, value: string | undefined) {
|
||||
if (!value) return;
|
||||
emit(
|
||||
'updatePosition',
|
||||
key,
|
||||
value as 'auto' | 'fixed' | 'header' | 'none' | 'user-dropdown',
|
||||
);
|
||||
nextTick(() => {
|
||||
emit(
|
||||
'updateOrder',
|
||||
@@ -91,25 +92,21 @@ function setPosition(key: string, event: Event) {
|
||||
v-for="item in sortableList"
|
||||
:key="item.key"
|
||||
:data-key="item.key"
|
||||
class="bg-accent flex items-center gap-2 rounded-md px-2 py-1.5"
|
||||
class="bg-accent flex items-center rounded-md pl-2"
|
||||
>
|
||||
<GripVertical
|
||||
class="drag-handle size-4 shrink-0 cursor-grab text-muted-foreground active:cursor-grabbing"
|
||||
/>
|
||||
<span class="min-w-0 flex-1 truncate text-sm">{{ item.label }}</span>
|
||||
<select
|
||||
:value="item.position"
|
||||
class="bg-background h-7 w-28 shrink-0 rounded border px-1 text-xs"
|
||||
@change="(e) => setPosition(item.key, e)"
|
||||
<SelectItem
|
||||
:items="item.positionItems ?? positionItems"
|
||||
:model-value="item.position"
|
||||
class="min-w-0 flex-1"
|
||||
@update:model-value="
|
||||
(v: string | undefined) => setPosition(item.key, v)
|
||||
"
|
||||
>
|
||||
<option
|
||||
v-for="opt in item.positionItems ?? positionItems"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
</SelectItem>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hiddenList.length > 0" class="pt-2">
|
||||
@@ -119,26 +116,22 @@ function setPosition(key: string, event: Event) {
|
||||
<div
|
||||
v-for="item in hiddenList"
|
||||
:key="item.key"
|
||||
class="flex items-center gap-2 rounded-md px-2 py-1"
|
||||
class="flex items-center rounded-md"
|
||||
>
|
||||
<span
|
||||
class="text-muted-foreground min-w-0 flex-1 truncate text-sm line-through decoration-dotted"
|
||||
<SelectItem
|
||||
:items="item.positionItems ?? positionItems"
|
||||
:model-value="item.position"
|
||||
class="min-w-0 flex-1"
|
||||
@update:model-value="
|
||||
(v: string | undefined) => setPosition(item.key, v)
|
||||
"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<select
|
||||
:value="item.position"
|
||||
class="bg-background h-7 w-28 shrink-0 rounded border px-1 text-xs"
|
||||
@change="(e) => setPosition(item.key, e)"
|
||||
>
|
||||
<option
|
||||
v-for="opt in item.positionItems ?? positionItems"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
<span
|
||||
class="text-muted-foreground truncate line-through decoration-dotted"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</SelectItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,10 +94,10 @@ const positionMap: Record<string, string> = {
|
||||
languageToggle: 'widgetLanguageToggleButtonPosition',
|
||||
timezone: 'widgetTimezoneButtonPosition',
|
||||
fullscreen: 'widgetFullscreenButtonPosition',
|
||||
refresh: 'widgetRefreshButtonPosition',
|
||||
notification: 'widgetNotificationButtonPosition',
|
||||
lockScreenBtn: 'widgetLockScreenButtonPosition',
|
||||
logoutBtn: 'widgetLogoutButtonPosition',
|
||||
refresh: 'widgetRefreshButtonPosition',
|
||||
};
|
||||
|
||||
const labelMap: Record<string, string> = {
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
VbenIconButton,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
withDefaults(defineProps<{ showButton?: boolean }>(), { showButton: true });
|
||||
|
||||
const TimezoneIcon = createIconifyIcon('fluent-mdl2:world-clock');
|
||||
|
||||
const timezoneStore = useTimezoneStore();
|
||||
@@ -47,17 +49,20 @@ const [Modal, modalApi] = useVbenModal({
|
||||
},
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
function open() {
|
||||
modalApi.open();
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VbenIconButton
|
||||
v-if="showButton"
|
||||
:tooltip="$t('ui.widgets.timezone.setTimezone')"
|
||||
class="hover:animate-[shrink_0.3s_ease-in-out]"
|
||||
@click="handleClick"
|
||||
@click="open"
|
||||
>
|
||||
<TimezoneIcon class="size-4 text-foreground" />
|
||||
</VbenIconButton>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||
import { SUPPORT_LANGUAGES } from '@vben/constants';
|
||||
import { useHoverToggle, useRefresh } from '@vben/hooks';
|
||||
import {
|
||||
createIconifyIcon,
|
||||
Languages,
|
||||
LockKeyhole,
|
||||
LogOut,
|
||||
@@ -40,7 +41,7 @@ import {
|
||||
VbenIconButton,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useMagicKeys, whenever } from '@vueuse/core';
|
||||
import { useFullscreen, useMagicKeys, whenever } from '@vueuse/core';
|
||||
|
||||
import { GlobalSearch } from '../global-search';
|
||||
import { LockScreenModal } from '../lock-screen';
|
||||
@@ -101,9 +102,11 @@ const {
|
||||
globalLogoutShortcutKey,
|
||||
globalLockScreenShortcutKey,
|
||||
globalSearchShortcutKey,
|
||||
isDark,
|
||||
preferencesButtonPosition,
|
||||
} = usePreferences();
|
||||
const { refresh } = useRefresh();
|
||||
const { toggle: toggleFullscreen } = useFullscreen();
|
||||
const accessStore = useAccessStore();
|
||||
const [LockModal, lockModalApi] = useVbenModal({
|
||||
connectedComponent: LockScreenModal,
|
||||
@@ -118,6 +121,10 @@ const refTrigger = useTemplateRef('refTrigger');
|
||||
const refContent = useTemplateRef('refContent');
|
||||
const refPreferences = useTemplateRef('refPreferences');
|
||||
const refGlobalSearch = useTemplateRef('refGlobalSearch');
|
||||
const refTimezone = useTemplateRef('refTimezone');
|
||||
const refNotification = useTemplateRef('refNotification');
|
||||
|
||||
const TimezoneIcon = createIconifyIcon('fluent-mdl2:world-clock');
|
||||
const [openPopover, hoverWatcher] = useHoverToggle(
|
||||
[refTrigger, refContent],
|
||||
() => props.hoverDelay,
|
||||
@@ -247,6 +254,28 @@ function handleGlobalSearch() {
|
||||
refGlobalSearch.value?.open();
|
||||
}
|
||||
|
||||
// 主题切换
|
||||
function handleThemeToggleSelect(event?: Event) {
|
||||
event?.preventDefault();
|
||||
updatePreferences({ theme: { mode: isDark.value ? 'light' : 'dark' } });
|
||||
}
|
||||
|
||||
// 时区
|
||||
function handleTimezoneSelect() {
|
||||
refTimezone.value?.open();
|
||||
}
|
||||
|
||||
// 全屏切换
|
||||
function handleFullscreenSelect() {
|
||||
toggleFullscreen();
|
||||
}
|
||||
|
||||
// 通知
|
||||
function handleNotificationSelect(event?: Event) {
|
||||
event?.preventDefault();
|
||||
refNotification.value?.toggle();
|
||||
}
|
||||
|
||||
// 语言切换 - 阻止 Radix 默认关闭外层 dropdown,就地展开/收起 locale 列表
|
||||
const showLanguageList = ref(false);
|
||||
function handleLanguageToggleSelect(event?: Event) {
|
||||
@@ -322,6 +351,12 @@ if (preferences.shortcutKeys.enable) {
|
||||
:show-button="false"
|
||||
/>
|
||||
|
||||
<TimezoneButton
|
||||
v-if="showTimezoneInDropdown"
|
||||
ref="refTimezone"
|
||||
:show-button="false"
|
||||
/>
|
||||
|
||||
<DropdownMenu v-model:open="openPopover" :modal="false">
|
||||
<DropdownMenuTrigger ref="refTrigger" :disabled="props.trigger === 'hover'">
|
||||
<div class="mr-2 ml-1 cursor-pointer rounded-full p-1.5 hover:bg-accent">
|
||||
@@ -428,6 +463,7 @@ if (preferences.shortcutKeys.enable) {
|
||||
<DropdownMenuItem
|
||||
v-if="showThemeToggleInDropdown"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@select="handleThemeToggleSelect"
|
||||
>
|
||||
<ThemeToggle class="mr-2" />
|
||||
{{ $t('preferences.theme.title') }}
|
||||
@@ -461,22 +497,27 @@ if (preferences.shortcutKeys.enable) {
|
||||
<DropdownMenuItem
|
||||
v-if="showTimezoneInDropdown"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@select="handleTimezoneSelect"
|
||||
>
|
||||
<TimezoneButton class="mr-2" />
|
||||
<VbenIconButton class="mr-2" @click="handleTimezoneSelect">
|
||||
<TimezoneIcon class="size-4" />
|
||||
</VbenIconButton>
|
||||
{{ $t('ui.widgets.timezone.setTimezone') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
v-if="showFullscreenInDropdown"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@select="handleFullscreenSelect"
|
||||
>
|
||||
<VbenFullScreen class="mr-2" />
|
||||
<VbenFullScreen class="mr-2" @click.stop />
|
||||
{{ $t('preferences.widget.fullscreen') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
v-if="showNotificationInDropdown"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@select="handleNotificationSelect"
|
||||
>
|
||||
<Notification class="mr-2" />
|
||||
<Notification ref="refNotification" class="mr-2" />
|
||||
{{ $t('preferences.widget.notification') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { VxeGridInstance } from 'vxe-table';
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
ExtendedFormApi,
|
||||
FormValues,
|
||||
} from '@vben-core/form-ui';
|
||||
|
||||
import type { VxeGridProps } from './types';
|
||||
@@ -34,14 +35,16 @@ export class VxeGridApi<
|
||||
T extends Record<string, any> = any,
|
||||
D extends BaseFormComponentType = BaseFormComponentType,
|
||||
P extends Record<string, any> = Record<never, never>,
|
||||
TFormValues extends FormValues = FormValues,
|
||||
TSubmitValues extends FormValues = TFormValues,
|
||||
> {
|
||||
public formApi = {} as ExtendedFormApi;
|
||||
public formApi = {} as ExtendedFormApi<TFormValues, D, P, TSubmitValues>;
|
||||
|
||||
// private prevState: null | VxeGridProps = null;
|
||||
public grid = {} as VxeGridInstance<T>;
|
||||
public state: null | VxeGridProps<T, D, P> = null;
|
||||
public state: null | VxeGridProps<T, D, P, TFormValues, TSubmitValues> = null;
|
||||
|
||||
public store: Store<VxeGridProps<T, D, P>>;
|
||||
public store: Store<VxeGridProps<T, D, P, TFormValues, TSubmitValues>>;
|
||||
|
||||
/**
|
||||
* 已读行 helper(在 mount 中初始化,业务能力全部封装在 useViewedRow 中)
|
||||
@@ -52,12 +55,26 @@ export class VxeGridApi<
|
||||
|
||||
private stateHandler: StateHandler;
|
||||
|
||||
constructor(options: VxeGridProps<T, D, P> = {} as VxeGridProps<T, D, P>) {
|
||||
constructor(
|
||||
options: VxeGridProps<
|
||||
T,
|
||||
D,
|
||||
P,
|
||||
TFormValues,
|
||||
TSubmitValues
|
||||
> = {} as VxeGridProps<T, D, P, TFormValues, TSubmitValues>,
|
||||
) {
|
||||
const storeState = { ...options };
|
||||
|
||||
const defaultState = getDefaultState();
|
||||
this.store = new Store<VxeGridProps<T, D, P>>(
|
||||
mergeWithArrayOverride(storeState, defaultState) as VxeGridProps<T, D, P>,
|
||||
this.store = new Store<VxeGridProps<T, D, P, TFormValues, TSubmitValues>>(
|
||||
mergeWithArrayOverride(storeState, defaultState) as VxeGridProps<
|
||||
T,
|
||||
D,
|
||||
P,
|
||||
TFormValues,
|
||||
TSubmitValues
|
||||
>,
|
||||
);
|
||||
|
||||
this.store.subscribe((state) => {
|
||||
@@ -106,7 +123,10 @@ export class VxeGridApi<
|
||||
this.viewedRowHelper?.markAsViewed(record);
|
||||
}
|
||||
|
||||
mount(instance: null | VxeGridInstance, formApi: ExtendedFormApi) {
|
||||
mount(
|
||||
instance: null | VxeGridInstance,
|
||||
formApi: ExtendedFormApi<TFormValues, D, P, TSubmitValues>,
|
||||
) {
|
||||
if (!this.isMounted && instance) {
|
||||
this.grid = instance;
|
||||
this.formApi = formApi;
|
||||
@@ -138,7 +158,11 @@ export class VxeGridApi<
|
||||
this.viewedRowHelper?.removeKeys(keys);
|
||||
}
|
||||
|
||||
setGridOptions(options: Partial<VxeGridProps<T, D, P>['gridOptions']>) {
|
||||
setGridOptions(
|
||||
options: Partial<
|
||||
VxeGridProps<T, D, P, TFormValues, TSubmitValues>['gridOptions']
|
||||
>,
|
||||
) {
|
||||
this.setState({
|
||||
gridOptions: options,
|
||||
});
|
||||
@@ -154,8 +178,10 @@ export class VxeGridApi<
|
||||
|
||||
setState(
|
||||
stateOrFn:
|
||||
| ((prev: VxeGridProps<T, D, P>) => Partial<VxeGridProps<T, D, P>>)
|
||||
| Partial<VxeGridProps<T, D, P>>,
|
||||
| ((
|
||||
prev: VxeGridProps<T, D, P, TFormValues, TSubmitValues>,
|
||||
) => Partial<VxeGridProps<T, D, P, TFormValues, TSubmitValues>>)
|
||||
| Partial<VxeGridProps<T, D, P, TFormValues, TSubmitValues>>,
|
||||
) {
|
||||
if (isFunction(stateOrFn)) {
|
||||
this.store.setState((prev) => {
|
||||
|
||||
@@ -10,7 +10,11 @@ import type { Ref } from 'vue';
|
||||
|
||||
import type { ClassType, DeepPartial } from '@vben/types';
|
||||
|
||||
import type { BaseFormComponentType, VbenFormProps } from '@vben-core/form-ui';
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
FormValues,
|
||||
VbenFormProps,
|
||||
} from '@vben-core/form-ui';
|
||||
|
||||
import type { VxeGridApi } from './api';
|
||||
|
||||
@@ -124,6 +128,8 @@ export interface VxeGridProps<
|
||||
T extends Record<string, any> = any,
|
||||
D extends BaseFormComponentType = BaseFormComponentType,
|
||||
P extends Record<string, any> = Record<never, never>,
|
||||
TFormValues extends FormValues = FormValues,
|
||||
TSubmitValues extends FormValues = TFormValues,
|
||||
> {
|
||||
/**
|
||||
* 数据
|
||||
@@ -156,7 +162,7 @@ export interface VxeGridProps<
|
||||
/**
|
||||
* 表单配置
|
||||
*/
|
||||
formOptions?: VbenFormProps<D, P>;
|
||||
formOptions?: VbenFormProps<D, P, TFormValues, TSubmitValues>;
|
||||
/**
|
||||
* 显示搜索表单
|
||||
*/
|
||||
@@ -175,9 +181,13 @@ export type ExtendedVxeGridApi<
|
||||
D extends Record<string, any> = any,
|
||||
F extends BaseFormComponentType = BaseFormComponentType,
|
||||
P extends Record<string, any> = Record<never, never>,
|
||||
> = VxeGridApi<D, F, P> & {
|
||||
useStore: <S = NoInfer<VxeGridProps<D, F, P>>>(
|
||||
selector?: (state: NoInfer<VxeGridProps<D, F, P>>) => S,
|
||||
TFormValues extends FormValues = FormValues,
|
||||
TSubmitValues extends FormValues = TFormValues,
|
||||
> = VxeGridApi<D, F, P, TFormValues, TSubmitValues> & {
|
||||
useStore: <S = NoInfer<VxeGridProps<D, F, P, TFormValues, TSubmitValues>>>(
|
||||
selector?: (
|
||||
state: NoInfer<VxeGridProps<D, F, P, TFormValues, TSubmitValues>>,
|
||||
) => S,
|
||||
) => Readonly<Ref<S>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { VxeGridSlots, VxeGridSlotTypes } from 'vxe-table';
|
||||
|
||||
import type { SlotsType } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben-core/form-ui';
|
||||
import type { BaseFormComponentType, FormValues } from '@vben-core/form-ui';
|
||||
|
||||
import type { ExtendedVxeGridApi, VxeGridProps } from './types';
|
||||
|
||||
@@ -23,24 +23,28 @@ export function useVbenVxeGrid<
|
||||
T extends Record<string, any> = any,
|
||||
D extends BaseFormComponentType = BaseFormComponentType,
|
||||
P extends Record<string, any> = Record<never, never>,
|
||||
>(options: VxeGridProps<T, D, P>) {
|
||||
TFormValues extends FormValues = FormValues,
|
||||
TSubmitValues extends FormValues = TFormValues,
|
||||
>(options: VxeGridProps<T, D, P, TFormValues, TSubmitValues>) {
|
||||
// const IS_REACTIVE = isReactive(options);
|
||||
const api = new VxeGridApi<T, D, P>(options);
|
||||
const extendedApi: ExtendedVxeGridApi<T, D, P> = api as ExtendedVxeGridApi<
|
||||
T,
|
||||
D,
|
||||
P
|
||||
>;
|
||||
const api = new VxeGridApi<T, D, P, TFormValues, TSubmitValues>(options);
|
||||
const extendedApi: ExtendedVxeGridApi<T, D, P, TFormValues, TSubmitValues> =
|
||||
api as ExtendedVxeGridApi<T, D, P, TFormValues, TSubmitValues>;
|
||||
extendedApi.useStore = (selector) => {
|
||||
return useStore(api.store, selector);
|
||||
};
|
||||
|
||||
const Grid = defineComponent(
|
||||
(props: VxeGridProps<T, D, P>, { attrs, slots }) => {
|
||||
(
|
||||
props: VxeGridProps<T, D, P, TFormValues, TSubmitValues>,
|
||||
{ attrs, slots },
|
||||
) => {
|
||||
onBeforeUnmount(() => {
|
||||
api.unmount();
|
||||
});
|
||||
api.setState({ ...props, ...attrs } as Partial<VxeGridProps<T, D, P>>);
|
||||
api.setState({ ...props, ...attrs } as Partial<
|
||||
VxeGridProps<T, D, P, TFormValues, TSubmitValues>
|
||||
>);
|
||||
return () =>
|
||||
h(
|
||||
VxeGrid,
|
||||
@@ -48,7 +52,7 @@ export function useVbenVxeGrid<
|
||||
...props,
|
||||
...attrs,
|
||||
api: extendedApi as ExtendedVxeGridApi,
|
||||
},
|
||||
} as any,
|
||||
slots,
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user