Pre Merge pull request !26 from 玲娜贝er/dev

This commit is contained in:
玲娜贝er 2025-04-12 06:28:54 +00:00 committed by Gitee
commit 345721e7c8
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
15 changed files with 179 additions and 128 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/web-antd", "name": "@vben/web-antd",
"version": "1.3.0", "version": "1.3.1",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -2,6 +2,11 @@ import type { RouteRecordStringComponent } from '@vben/types';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
const {
version,
// vite inject-metadata 插件注入的全局变量
} = __VBEN_ADMIN_METADATA__ || {};
/** /**
* *
*/ */
@ -134,8 +139,8 @@ export const localMenuList: RouteRecordStringComponent[] = [
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
keepAlive: true, keepAlive: true,
title: '更新记录', title: '更新记录',
badge: '1.3.0', badge: `当前: ${version}`,
badgeVariants: '#CC0033', badgeVariants: 'bg-primary',
}, },
}, },
], ],

View File

@ -77,7 +77,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
const { dictCode, dictType } = drawerApi.getData() as DrawerProps; const { dictCode, dictType } = drawerApi.getData() as DrawerProps;
isUpdate.value = !!dictCode; isUpdate.value = !!dictCode;
formApi.setFieldValue('dictType', dictType); await formApi.setFieldValue('dictType', dictType);
if (dictCode && isUpdate.value) { if (dictCode && isUpdate.value) {
const record = await dictDetailInfo(dictCode); const record = await dictDetailInfo(dictCode);

View File

@ -8,6 +8,7 @@ import { addFullName, cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { postAdd, postInfo, postUpdate } from '#/api/system/post'; import { postAdd, postInfo, postUpdate } from '#/api/system/post';
import { getDeptTree } from '#/api/system/user'; import { getDeptTree } from '#/api/system/user';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data'; import { drawerSchema } from './data';
@ -50,8 +51,16 @@ async function setupDeptSelect() {
]); ]);
} }
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({ const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel, onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm, onConfirm: handleConfirm,
async onOpenChange(isOpen) { async onOpenChange(isOpen) {
if (!isOpen) { if (!isOpen) {
@ -67,31 +76,33 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
const record = await postInfo(id); const record = await postInfo(id);
await formApi.setValues(record); await formApi.setValues(record);
} }
await markInitialized();
drawerApi.drawerLoading(false); drawerApi.drawerLoading(false);
}, },
}); });
async function handleConfirm() { async function handleConfirm() {
try { try {
drawerApi.drawerLoading(true); drawerApi.lock(true);
const { valid } = await formApi.validate(); const { valid } = await formApi.validate();
if (!valid) { if (!valid) {
return; return;
} }
const data = cloneDeep(await formApi.getValues()); const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? postUpdate(data) : postAdd(data)); await (isUpdate.value ? postUpdate(data) : postAdd(data));
resetInitialized();
emit('reload'); emit('reload');
await handleCancel(); drawerApi.close();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally { } finally {
drawerApi.drawerLoading(false); drawerApi.lock(false);
} }
} }
async function handleCancel() { async function handleClosed() {
drawerApi.close();
await formApi.resetForm(); await formApi.resetForm();
resetInitialized();
} }
</script> </script>

View File

@ -94,7 +94,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right', fixed: 'right',
slots: { default: 'action' }, slots: { default: 'action' },
title: '操作', title: '操作',
width: 180, resizable: false,
width: 'auto',
}, },
]; ];

View File

@ -226,7 +226,11 @@ function handleAssignRole(record: Role) {
</MenuItem> </MenuItem>
</Menu> </Menu>
</template> </template>
<a-button size="small" type="link"> <a-button
size="small"
type="link"
v-access:code="'system:role:edit'"
>
{{ $t('pages.common.more') }} {{ $t('pages.common.more') }}
</a-button> </a-button>
</Dropdown> </Dropdown>

View File

@ -52,7 +52,7 @@ const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
const [BasicModal, modalApi] = useVbenModal({ const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false, fullscreenButton: false,
onBeforeClose, onBeforeClose,
onCancel: handleClosed, onClosed: handleClosed,
onConfirm: handleConfirm, onConfirm: handleConfirm,
onOpenChange: async (isOpen) => { onOpenChange: async (isOpen) => {
if (!isOpen) { if (!isOpen) {

View File

@ -87,7 +87,7 @@ export const columns: VxeGridProps['columns'] = [
slots: { default: 'action' }, slots: { default: 'action' },
title: '操作', title: '操作',
resizable: false, resizable: false,
width: 180, width: 'auto',
}, },
]; ];

View File

@ -75,7 +75,7 @@ const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
const [BasicDrawer, modalApi] = useVbenModal({ const [BasicDrawer, modalApi] = useVbenModal({
onBeforeClose, onBeforeClose,
onCancel: handleClosed, onClosed: handleClosed,
onConfirm: handleConfirm, onConfirm: handleConfirm,
async onOpenChange(isOpen) { async onOpenChange(isOpen) {
if (!isOpen) { if (!isOpen) {

View File

@ -2,14 +2,16 @@ import type { DeepPartial } from '@vben-core/typings';
import type { InitialOptions, Preferences } from './types'; import type { InitialOptions, Preferences } from './types';
import { markRaw, reactive, readonly, watch } from 'vue';
import { StorageManager } from '@vben-core/shared/cache'; import { StorageManager } from '@vben-core/shared/cache';
import { isMacOs, merge } from '@vben-core/shared/utils'; import { isMacOs, merge } from '@vben-core/shared/utils';
import { import {
breakpointsTailwind, breakpointsTailwind,
useBreakpoints, useBreakpoints,
useDebounceFn, useDebounceFn,
} from '@vueuse/core'; } from '@vueuse/core';
import { markRaw, reactive, readonly, watch } from 'vue';
import { defaultPreferences } from './config'; import { defaultPreferences } from './config';
import { updateCSSVariables } from './update-css-variables'; import { updateCSSVariables } from './update-css-variables';
@ -37,106 +39,6 @@ class PreferenceManager {
); );
} }
/**
*
* @param {Preferences} preference -
*/
private _savePreferences(preference: Preferences) {
this.cache?.setItem(STORAGE_KEY, preference);
this.cache?.setItem(STORAGE_KEY_LOCALE, preference.app.locale);
this.cache?.setItem(STORAGE_KEY_THEME, preference.theme.mode);
}
/**
*
*
* @param {DeepPartial<Preferences>} updates -
*/
private handleUpdates(updates: DeepPartial<Preferences>) {
const themeUpdates = updates.theme || {};
const appUpdates = updates.app || {};
if (themeUpdates && Object.keys(themeUpdates).length > 0) {
updateCSSVariables(this.state);
}
if (
Reflect.has(appUpdates, 'colorGrayMode') ||
Reflect.has(appUpdates, 'colorWeakMode')
) {
this.updateColorMode(this.state);
}
}
private initPlatform() {
const dom = document.documentElement;
dom.dataset.platform = isMacOs() ? 'macOs' : 'window';
}
/**
*
*/
private loadCachedPreferences() {
return this.cache?.getItem<Preferences>(STORAGE_KEY);
}
/**
*
* @returns {Preferences}
*/
private loadPreferences(): Preferences {
return this.loadCachedPreferences() || { ...defaultPreferences };
}
/**
*
*/
private setupWatcher() {
if (this.isInitialized) {
return;
}
// 监听断点,判断是否移动端
const breakpoints = useBreakpoints(breakpointsTailwind);
const isMobile = breakpoints.smaller('md');
watch(
() => isMobile.value,
(val) => {
this.updatePreferences({
app: { isMobile: val },
});
},
{ immediate: true },
);
// 监听系统主题偏好设置变化
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', ({ matches: isDark }) => {
this.updatePreferences({
theme: { mode: isDark ? 'dark' : 'light' },
});
});
}
/**
*
* @param preference
*/
private updateColorMode(preference: Preferences) {
if (preference.app) {
const { colorGrayMode, colorWeakMode } = preference.app;
const dom = document.documentElement;
const COLOR_WEAK = 'invert-mode';
const COLOR_GRAY = 'grayscale-mode';
colorWeakMode
? dom.classList.add(COLOR_WEAK)
: dom.classList.remove(COLOR_WEAK);
colorGrayMode
? dom.classList.add(COLOR_GRAY)
: dom.classList.remove(COLOR_GRAY);
}
}
clearCache() { clearCache() {
[STORAGE_KEY, STORAGE_KEY_LOCALE, STORAGE_KEY_THEME].forEach((key) => { [STORAGE_KEY, STORAGE_KEY_LOCALE, STORAGE_KEY_THEME].forEach((key) => {
this.cache?.removeItem(key); this.cache?.removeItem(key);
@ -220,6 +122,113 @@ class PreferenceManager {
this.handleUpdates(updates); this.handleUpdates(updates);
this.savePreferences(this.state); this.savePreferences(this.state);
} }
/**
*
* @param {Preferences} preference -
*/
private _savePreferences(preference: Preferences) {
this.cache?.setItem(STORAGE_KEY, preference);
this.cache?.setItem(STORAGE_KEY_LOCALE, preference.app.locale);
this.cache?.setItem(STORAGE_KEY_THEME, preference.theme.mode);
}
/**
*
*
* @param {DeepPartial<Preferences>} updates -
*/
private handleUpdates(updates: DeepPartial<Preferences>) {
const themeUpdates = updates.theme || {};
const appUpdates = updates.app || {};
if (themeUpdates && Object.keys(themeUpdates).length > 0) {
updateCSSVariables(this.state);
}
if (
Reflect.has(appUpdates, 'colorGrayMode') ||
Reflect.has(appUpdates, 'colorWeakMode')
) {
this.updateColorMode(this.state);
}
}
private initPlatform() {
const dom = document.documentElement;
dom.dataset.platform = isMacOs() ? 'macOs' : 'window';
}
/**
*
*/
private loadCachedPreferences() {
return this.cache?.getItem<Preferences>(STORAGE_KEY);
}
/**
*
* @returns {Preferences}
*/
private loadPreferences(): Preferences {
return this.loadCachedPreferences() || { ...defaultPreferences };
}
/**
*
*/
private setupWatcher() {
if (this.isInitialized) {
return;
}
// 监听断点,判断是否移动端
const breakpoints = useBreakpoints(breakpointsTailwind);
const isMobile = breakpoints.smaller('md');
watch(
() => isMobile.value,
(val) => {
this.updatePreferences({
app: { isMobile: val },
});
},
{ immediate: true },
);
// 监听系统主题偏好设置变化
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', ({ matches: isDark }) => {
// 如果偏好设置中主题模式为auto则跟随系统更新
if (this.state.theme.mode === 'auto') {
this.updatePreferences({
theme: { mode: isDark ? 'dark' : 'light' },
});
// 恢复为auto模式
this.updatePreferences({
theme: { mode: 'auto' },
});
}
});
}
/**
*
* @param preference
*/
private updateColorMode(preference: Preferences) {
if (preference.app) {
const { colorGrayMode, colorWeakMode } = preference.app;
const dom = document.documentElement;
const COLOR_WEAK = 'invert-mode';
const COLOR_GRAY = 'grayscale-mode';
colorWeakMode
? dom.classList.add(COLOR_WEAK)
: dom.classList.remove(COLOR_WEAK);
colorGrayMode
? dom.classList.add(COLOR_GRAY)
: dom.classList.remove(COLOR_GRAY);
}
}
} }
const preferencesManager = new PreferenceManager(); const preferencesManager = new PreferenceManager();

View File

@ -138,7 +138,7 @@ async function handleOpenChange(val: boolean) {
<div class="flex items-center"> <div class="flex items-center">
<component :is="getIconRender" class="mr-2" /> <component :is="getIconRender" class="mr-2" />
<span class="flex-auto">{{ $t(title) }}</span> <span class="flex-auto">{{ $t(title) }}</span>
<AlertDialogCancel v-if="showCancel"> <AlertDialogCancel v-if="showCancel" as-child>
<VbenButton <VbenButton
variant="ghost" variant="ghost"
size="icon" size="icon"
@ -158,16 +158,17 @@ async function handleOpenChange(val: boolean) {
<VbenLoading v-if="loading && contentMasking" :spinning="loading" /> <VbenLoading v-if="loading && contentMasking" :spinning="loading" />
</AlertDialogDescription> </AlertDialogDescription>
<div class="flex justify-end gap-x-2" :class="`justify-${buttonAlign}`"> <div class="flex justify-end gap-x-2" :class="`justify-${buttonAlign}`">
<AlertDialogCancel v-if="showCancel" :disabled="loading"> <AlertDialogCancel v-if="showCancel" as-child>
<component <component
:is="components.DefaultButton || VbenButton" :is="components.DefaultButton || VbenButton"
:disabled="loading"
variant="ghost" variant="ghost"
@click="handleCancel" @click="handleCancel"
> >
{{ cancelText || $t('cancel') }} {{ cancelText || $t('cancel') }}
</component> </component>
</AlertDialogCancel> </AlertDialogCancel>
<AlertDialogAction> <AlertDialogAction as-child>
<component <component
:is="components.PrimaryButton || VbenButton" :is="components.PrimaryButton || VbenButton"
:loading="loading" :loading="loading"

View File

@ -17,12 +17,15 @@ export * from '@vben-core/popup-ui';
// 给文档用 // 给文档用
export { export {
VbenAvatar,
VbenButton, VbenButton,
VbenButtonGroup, VbenButtonGroup,
VbenCheckButtonGroup, VbenCheckButtonGroup,
VbenCountToAnimator, VbenCountToAnimator,
VbenFullScreen,
VbenInputPassword, VbenInputPassword,
VbenLoading, VbenLoading,
VbenLogo,
VbenPinInput, VbenPinInput,
VbenSpinner, VbenSpinner,
VbenTree, VbenTree,

View File

@ -9,6 +9,8 @@ import { $t } from '@vben/locales';
import { BUILT_IN_THEME_PRESETS } from '@vben/preferences'; import { BUILT_IN_THEME_PRESETS } from '@vben/preferences';
import { convertToHsl, TinyColor } from '@vben/utils'; import { convertToHsl, TinyColor } from '@vben/utils';
import { useThrottleFn } from '@vueuse/core';
defineOptions({ defineOptions({
name: 'PreferenceBuiltinTheme', name: 'PreferenceBuiltinTheme',
}); });
@ -19,6 +21,15 @@ const colorInput = ref();
const modelValue = defineModel<BuiltinThemeType>({ default: 'default' }); const modelValue = defineModel<BuiltinThemeType>({ default: 'default' });
const themeColorPrimary = defineModel<string>('themeColorPrimary'); const themeColorPrimary = defineModel<string>('themeColorPrimary');
const updateThemeColorPrimary = useThrottleFn(
(value: string) => {
themeColorPrimary.value = value;
},
300,
true,
true,
);
const inputValue = computed(() => { const inputValue = computed(() => {
return new TinyColor(themeColorPrimary.value || '').toHexString(); return new TinyColor(themeColorPrimary.value || '').toHexString();
}); });
@ -84,7 +95,7 @@ function handleSelect(theme: BuiltinThemePreset) {
function handleInputChange(e: Event) { function handleInputChange(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
themeColorPrimary.value = convertToHsl(target.value); updateThemeColorPrimary(convertToHsl(target.value));
} }
function selectColor() { function selectColor() {

View File

@ -1,8 +1,11 @@
import type { SetupVxeTable } from './types'; import type { SetupVxeTable } from './types';
import { usePreferences } from '@vben/preferences';
import { useVbenForm } from '@vben-core/form-ui';
import { defineComponent, watch } from 'vue'; import { defineComponent, watch } from 'vue';
import { usePreferences } from '@vben/preferences';
import { useVbenForm } from '@vben-core/form-ui';
import { import {
VxeButton, VxeButton,
VxeCheckbox, VxeCheckbox,
@ -103,7 +106,7 @@ export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
initVxeTable(); initVxeTable();
useTableForm = useVbenForm; useTableForm = useVbenForm;
const preference = usePreferences(); const { isDark, locale } = usePreferences();
const localMap = { const localMap = {
'zh-CN': zhCN, 'zh-CN': zhCN,
@ -111,11 +114,11 @@ export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
}; };
watch( watch(
[() => preference.theme.value, () => preference.locale.value], [() => isDark.value, () => locale.value],
([theme, locale]) => { ([isDarkValue, localeValue]) => {
VxeUI.setTheme(theme === 'dark' ? 'dark' : 'light'); VxeUI.setTheme(isDarkValue ? 'dark' : 'light');
VxeUI.setI18n(locale, localMap[locale]); VxeUI.setI18n(localeValue, localMap[localeValue]);
VxeUI.setLanguage(locale); VxeUI.setLanguage(localeValue);
}, },
{ {
immediate: true, immediate: true,

View File

@ -261,6 +261,9 @@ async function openPrompt() {
</template> </template>
</Card> </Card>
<Card class="w-[300px]" title="轻量提示弹窗"> <Card class="w-[300px]" title="轻量提示弹窗">
<template #extra>
<DocButton path="/components/common-ui/vben-alert" />
</template>
<p>通过快捷方法创建动态提示弹窗适合一些轻量的提示和确认输入等</p> <p>通过快捷方法创建动态提示弹窗适合一些轻量的提示和确认输入等</p>
<template #actions> <template #actions>
<Button type="primary" @click="openAlert">Alert</Button> <Button type="primary" @click="openAlert">Alert</Button>