feat: new interface pendant can be configured to display hidden
This commit is contained in:
@@ -1,132 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { flattenObject } from './flatten-object';
|
||||
|
||||
describe('flattenObject', () => {
|
||||
it('should flatten a nested object correctly', () => {
|
||||
const nestedObject = {
|
||||
language: 'en',
|
||||
notifications: {
|
||||
email: true,
|
||||
push: {
|
||||
sound: true,
|
||||
vibration: false,
|
||||
},
|
||||
},
|
||||
theme: 'light',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
language: 'en',
|
||||
notificationsEmail: true,
|
||||
notificationsPushSound: true,
|
||||
notificationsPushVibration: false,
|
||||
theme: 'light',
|
||||
};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle empty objects', () => {
|
||||
const nestedObject = {};
|
||||
const expected = {};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle objects with primitive values', () => {
|
||||
const nestedObject = {
|
||||
active: true,
|
||||
age: 30,
|
||||
name: 'Alice',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
active: true,
|
||||
age: 30,
|
||||
name: 'Alice',
|
||||
};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle objects with null values', () => {
|
||||
const nestedObject = {
|
||||
user: {
|
||||
age: null,
|
||||
name: null,
|
||||
},
|
||||
};
|
||||
|
||||
const expected = {
|
||||
userAge: null,
|
||||
userName: null,
|
||||
};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle nested empty objects', () => {
|
||||
const nestedObject = {
|
||||
a: {},
|
||||
b: { c: {} },
|
||||
};
|
||||
|
||||
const expected = {};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle arrays within objects', () => {
|
||||
const nestedObject = {
|
||||
hobbies: ['reading', 'gaming'],
|
||||
name: 'Alice',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
hobbies: ['reading', 'gaming'],
|
||||
name: 'Alice',
|
||||
};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
it('should flatten objects with nested arrays correctly', () => {
|
||||
const nestedObject = {
|
||||
person: {
|
||||
hobbies: ['reading', 'gaming'],
|
||||
name: 'Alice',
|
||||
},
|
||||
};
|
||||
|
||||
const expected = {
|
||||
personHobbies: ['reading', 'gaming'],
|
||||
personName: 'Alice',
|
||||
};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle objects with undefined values', () => {
|
||||
const nestedObject = {
|
||||
user: {
|
||||
age: undefined,
|
||||
name: 'Alice',
|
||||
},
|
||||
};
|
||||
|
||||
const expected = {
|
||||
userAge: undefined,
|
||||
userName: 'Alice',
|
||||
};
|
||||
|
||||
const result = flattenObject(nestedObject);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
@@ -1,82 +0,0 @@
|
||||
import type { Flatten } from '@vben-core/typings';
|
||||
|
||||
import { capitalizeFirstLetter } from '@vben-core/toolkit';
|
||||
|
||||
/**
|
||||
* 将嵌套对象扁平化
|
||||
* @param obj - 需要扁平化的对象
|
||||
* @param parentKey - 父键名,用于递归时拼接键名
|
||||
* @param result - 存储结果的对象
|
||||
* @returns 扁平化后的对象
|
||||
*
|
||||
* 示例:
|
||||
* const nestedObj = {
|
||||
* user: {
|
||||
* name: 'Alice',
|
||||
* address: {
|
||||
* city: 'Wonderland',
|
||||
* zip: '12345'
|
||||
* }
|
||||
* },
|
||||
* items: [
|
||||
* { id: 1, name: 'Item 1' },
|
||||
* { id: 2, name: 'Item 2' }
|
||||
* ],
|
||||
* active: true
|
||||
* };
|
||||
* const flatObj = flattenObject(nestedObj);
|
||||
* console.log(flatObj);
|
||||
* 输出:
|
||||
* {
|
||||
* userName: 'Alice',
|
||||
* userAddressCity: 'Wonderland',
|
||||
* userAddressZip: '12345',
|
||||
* items: [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' } ],
|
||||
* active: true
|
||||
* }
|
||||
*/
|
||||
function flattenObject<T extends Record<string, any>>(
|
||||
obj: T,
|
||||
parentKey: string = '',
|
||||
result: Record<string, any> = {},
|
||||
): Flatten<T> {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
const newKey = parentKey
|
||||
? `${parentKey}${capitalizeFirstLetter(key)}`
|
||||
: key;
|
||||
const value = obj[key];
|
||||
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
flattenObject(value, newKey, result);
|
||||
} else {
|
||||
result[newKey] = value;
|
||||
}
|
||||
});
|
||||
return result as Flatten<T>;
|
||||
}
|
||||
|
||||
export { flattenObject };
|
||||
|
||||
// 定义递归类型,用于推断扁平化后的对象类型
|
||||
// 限制递归深度的辅助类型
|
||||
// type FlattenDepth<
|
||||
// T,
|
||||
// Depth extends number,
|
||||
// CurrentDepth extends number[] = [],
|
||||
// > = {
|
||||
// [K in keyof T as CurrentDepth['length'] extends Depth
|
||||
// ? K
|
||||
// : T[K] extends object
|
||||
// ? `${CurrentDepth['length'] extends 0 ? UnCapitalize<K & string> : Capitalize<K & string>}${keyof FlattenDepth<T[K], Depth, [...CurrentDepth, 1]> extends string ? Capitalize<keyof FlattenDepth<T[K], Depth, [...CurrentDepth, 1]>> : ''}`
|
||||
// : `${CurrentDepth['length'] extends 0 ? UnCapitalize<K & string> : Capitalize<K & string>}`]: CurrentDepth['length'] extends Depth
|
||||
// ? T[K]
|
||||
// : T[K] extends object
|
||||
// ? FlattenDepth<T[K], Depth, [...CurrentDepth, 1]>[keyof FlattenDepth<
|
||||
// T[K],
|
||||
// Depth,
|
||||
// [...CurrentDepth, 1]
|
||||
// >]
|
||||
// : T[K];
|
||||
// };
|
||||
|
||||
// type Flatten<T, Depth extends number = 4> = FlattenDepth<T, Depth>;
|
@@ -1,4 +1,2 @@
|
||||
export * from './find-menu-by-path';
|
||||
export * from './flatten-object';
|
||||
export * from './merge-route-modules';
|
||||
export * from './nested-object';
|
||||
|
@@ -1,115 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { nestedObject } from './nested-object';
|
||||
|
||||
describe('nestedObject', () => {
|
||||
it('should convert flat object to nested object with level 1', () => {
|
||||
const flatObject = {
|
||||
anotherKeyExample: 2,
|
||||
commonAppName: 1,
|
||||
someOtherKey: 3,
|
||||
};
|
||||
|
||||
const expectedNestedObject = {
|
||||
anotherKeyExample: 2,
|
||||
commonAppName: 1,
|
||||
someOtherKey: 3,
|
||||
};
|
||||
|
||||
expect(nestedObject(flatObject, 1)).toEqual(expectedNestedObject);
|
||||
});
|
||||
|
||||
it('should convert flat object to nested object with level 2', () => {
|
||||
const flatObject = {
|
||||
appAnotherKeyExample: 2,
|
||||
appCommonName: 1,
|
||||
appSomeOtherKey: 3,
|
||||
};
|
||||
|
||||
const expectedNestedObject = {
|
||||
app: {
|
||||
anotherKeyExample: 2,
|
||||
commonName: 1,
|
||||
someOtherKey: 3,
|
||||
},
|
||||
};
|
||||
|
||||
expect(nestedObject(flatObject, 2)).toEqual(expectedNestedObject);
|
||||
});
|
||||
|
||||
it('should convert flat object to nested object with level 3', () => {
|
||||
const flatObject = {
|
||||
appAnotherKeyExampleValue: 2,
|
||||
appCommonNameKey: 1,
|
||||
appSomeOtherKeyItem: 3,
|
||||
};
|
||||
|
||||
const expectedNestedObject = {
|
||||
app: {
|
||||
another: {
|
||||
keyExampleValue: 2,
|
||||
},
|
||||
common: {
|
||||
nameKey: 1,
|
||||
},
|
||||
some: {
|
||||
otherKeyItem: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(nestedObject(flatObject, 3)).toEqual(expectedNestedObject);
|
||||
});
|
||||
|
||||
it('should handle empty object', () => {
|
||||
const flatObject = {};
|
||||
|
||||
const expectedNestedObject = {};
|
||||
|
||||
expect(nestedObject(flatObject, 1)).toEqual(expectedNestedObject);
|
||||
});
|
||||
|
||||
it('should handle single key object', () => {
|
||||
const flatObject = {
|
||||
singleKey: 1,
|
||||
};
|
||||
|
||||
const expectedNestedObject = {
|
||||
singleKey: 1,
|
||||
};
|
||||
|
||||
expect(nestedObject(flatObject, 1)).toEqual(expectedNestedObject);
|
||||
});
|
||||
|
||||
it('should handle complex keys', () => {
|
||||
const flatObject = {
|
||||
anotherComplexKeyWithParts: 2,
|
||||
complexKeyWithMultipleParts: 1,
|
||||
};
|
||||
|
||||
const expectedNestedObject = {
|
||||
anotherComplexKeyWithParts: 2,
|
||||
complexKeyWithMultipleParts: 1,
|
||||
};
|
||||
|
||||
expect(nestedObject(flatObject, 1)).toEqual(expectedNestedObject);
|
||||
});
|
||||
|
||||
it('should correctly nest an object based on the specified level', () => {
|
||||
const obj = {
|
||||
oneFiveSix: 'Value156',
|
||||
oneTwoFour: 'Value124',
|
||||
oneTwoThree: 'Value123',
|
||||
};
|
||||
|
||||
const nested = nestedObject(obj, 2);
|
||||
|
||||
expect(nested).toEqual({
|
||||
one: {
|
||||
fiveSix: 'Value156',
|
||||
twoFour: 'Value124',
|
||||
twoThree: 'Value123',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,70 +0,0 @@
|
||||
import { toLowerCaseFirstLetter } from '@vben-core/toolkit';
|
||||
|
||||
/**
|
||||
* 将扁平对象转换为嵌套对象。
|
||||
*
|
||||
* @template T - 输入对象值的类型
|
||||
* @param {Record<string, T>} obj - 要转换的扁平对象
|
||||
* @param {number} level - 嵌套的层级
|
||||
* @returns {T} 嵌套对象
|
||||
*
|
||||
* @example
|
||||
* 将扁平对象转换为嵌套对象,嵌套层级为 1
|
||||
* const flatObject = {
|
||||
* 'commonAppName': 1,
|
||||
* 'anotherKeyExample': 2,
|
||||
* 'someOtherKey': 3
|
||||
* };
|
||||
* const nestedObject = nestedObject(flatObject, 1);
|
||||
* console.log(nestedObject);
|
||||
* 输出:
|
||||
* {
|
||||
* commonAppName: 1,
|
||||
* anotherKeyExample: 2,
|
||||
* someOtherKey: 3
|
||||
* }
|
||||
*
|
||||
* @example
|
||||
* 将扁平对象转换为嵌套对象,嵌套层级为 2
|
||||
* const flatObject = {
|
||||
* 'appCommonName': 1,
|
||||
* 'appAnotherKeyExample': 2,
|
||||
* 'appSomeOtherKey': 3
|
||||
* };
|
||||
* const nestedObject = nestedObject(flatObject, 2);
|
||||
* console.log(nestedObject);
|
||||
* 输出:
|
||||
* {
|
||||
* app: {
|
||||
* commonName: 1,
|
||||
* anotherKeyExample: 2,
|
||||
* someOtherKey: 3
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
function nestedObject<T>(obj: Record<string, T>, level: number): T {
|
||||
const result: any = {};
|
||||
|
||||
for (const key in obj) {
|
||||
const keys = key.split(/(?=[A-Z])/);
|
||||
// 将驼峰式分割为数组;
|
||||
let current = result;
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const lowerKey = keys[i].toLowerCase();
|
||||
if (i === level - 1) {
|
||||
const remainingKeys = keys.slice(i).join(''); // 保留后续部分作为键的一部分
|
||||
current[toLowerCaseFirstLetter(remainingKeys)] = obj[key];
|
||||
break;
|
||||
} else {
|
||||
current[lowerKey] = current[lowerKey] || {};
|
||||
current = current[lowerKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
export { nestedObject };
|
@@ -17,7 +17,6 @@ const defaultPreferences: Preferences = {
|
||||
layout: 'sidebar-nav',
|
||||
locale: 'zh-CN',
|
||||
name: 'Vben Admin Pro',
|
||||
semiDarkMenu: true,
|
||||
},
|
||||
breadcrumb: {
|
||||
enable: true,
|
||||
@@ -82,6 +81,7 @@ const defaultPreferences: Preferences = {
|
||||
colorWarning: 'hsl(42 84% 61%)',
|
||||
mode: 'dark',
|
||||
radius: '0.5',
|
||||
semiDarkMenu: true,
|
||||
},
|
||||
transition: {
|
||||
enable: true,
|
||||
@@ -89,6 +89,15 @@ const defaultPreferences: Preferences = {
|
||||
name: 'fade-slide',
|
||||
progress: true,
|
||||
},
|
||||
widget: {
|
||||
aiAssistant: true,
|
||||
fullscreen: true,
|
||||
globalSearch: true,
|
||||
languageToggle: true,
|
||||
notification: true,
|
||||
sidebarToggle: true,
|
||||
themeToggle: true,
|
||||
},
|
||||
};
|
||||
|
||||
export { defaultPreferences };
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import type { DeepPartial } from '@vben-core/typings';
|
||||
|
||||
import type { Preferences } from './types';
|
||||
|
||||
import { preferencesManager } from './preferences';
|
||||
@@ -5,10 +7,6 @@ import { preferencesManager } from './preferences';
|
||||
// 偏好设置(带有层级关系)
|
||||
const preferences: Preferences = preferencesManager.getPreferences();
|
||||
|
||||
// 扁平化后的偏好设置
|
||||
// const flatPreferences: Flatten<Preferences> =
|
||||
// preferencesManager.getFlatPreferences();
|
||||
|
||||
// 更新偏好设置
|
||||
const updatePreferences =
|
||||
preferencesManager.updatePreferences.bind(preferencesManager);
|
||||
@@ -20,9 +18,13 @@ const resetPreferences =
|
||||
const clearPreferencesCache =
|
||||
preferencesManager.clearCache.bind(preferencesManager);
|
||||
|
||||
function defineOverridesPreferences(preferences: DeepPartial<Preferences>) {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
export {
|
||||
clearPreferencesCache,
|
||||
// flatPreferences,
|
||||
defineOverridesPreferences,
|
||||
preferences,
|
||||
preferencesManager,
|
||||
resetPreferences,
|
||||
|
@@ -46,8 +46,6 @@ interface AppPreferences {
|
||||
locale: SupportedLanguagesType;
|
||||
/** 应用名 */
|
||||
name: string;
|
||||
/** 是否开启半深色菜单(只在theme='light'时生效) */
|
||||
semiDarkMenu: boolean;
|
||||
}
|
||||
|
||||
interface BreadcrumbPreferences {
|
||||
@@ -164,6 +162,8 @@ interface ThemePreferences {
|
||||
mode: ThemeModeType;
|
||||
/** 圆角 */
|
||||
radius: string;
|
||||
/** 是否开启半深色菜单(只在theme='light'时生效) */
|
||||
semiDarkMenu: boolean;
|
||||
}
|
||||
|
||||
interface TransitionPreferences {
|
||||
@@ -177,6 +177,23 @@ interface TransitionPreferences {
|
||||
progress: boolean;
|
||||
}
|
||||
|
||||
interface WidgetPreferences {
|
||||
/** 是否开启vben助手部件 */
|
||||
aiAssistant: boolean;
|
||||
/** 是否启用全屏部件 */
|
||||
fullscreen: boolean;
|
||||
/** 是否启用全局搜索部件 */
|
||||
globalSearch: boolean;
|
||||
/** 是否启用语言切换部件 */
|
||||
languageToggle: boolean;
|
||||
/** 是否显示通知部件 */
|
||||
notification: boolean;
|
||||
/** 是否显示侧边栏显示/隐藏部件 */
|
||||
sidebarToggle: boolean;
|
||||
/** 是否显示主题切换部件 */
|
||||
themeToggle: boolean;
|
||||
}
|
||||
|
||||
interface Preferences {
|
||||
/** 全局配置 */
|
||||
app: AppPreferences;
|
||||
@@ -202,6 +219,8 @@ interface Preferences {
|
||||
theme: ThemePreferences;
|
||||
/** 动画配置 */
|
||||
transition: TransitionPreferences;
|
||||
/** 功能配置 */
|
||||
widget: WidgetPreferences;
|
||||
}
|
||||
|
||||
type PreferencesKeys = keyof Preferences;
|
||||
@@ -230,4 +249,5 @@ export type {
|
||||
ThemeModeType,
|
||||
ThemePreferences,
|
||||
TransitionPreferences,
|
||||
WidgetPreferences,
|
||||
};
|
||||
|
@@ -147,7 +147,6 @@
|
||||
"general": "General",
|
||||
"language": "Language",
|
||||
"dynamic-title": "Dynamic Title",
|
||||
"ai-assistant": "Ai Assistant",
|
||||
"sidebar": {
|
||||
"title": "Sidebar",
|
||||
"width": "Width",
|
||||
@@ -248,6 +247,16 @@
|
||||
"search": "Global Search",
|
||||
"logout": "Logout",
|
||||
"preferences": "Preferences"
|
||||
},
|
||||
"widget": {
|
||||
"title": "Widget",
|
||||
"global-search": "Enable Global Search",
|
||||
"fullscreen": "Enable Fullscreen",
|
||||
"theme-toggle": "Enable Theme Toggle",
|
||||
"language-toggle": "Enable Language Toggle",
|
||||
"notification": "Enable Notification",
|
||||
"sidebar-toggle": "Enable Sidebar Toggle",
|
||||
"ai-assistant": "Enable AI Assistant"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -146,7 +146,6 @@
|
||||
"general": "通用",
|
||||
"language": "语言",
|
||||
"dynamic-title": "动态标题",
|
||||
"ai-assistant": "Ai 助手",
|
||||
"sidebar": {
|
||||
"title": "侧边栏",
|
||||
"width": "宽度",
|
||||
@@ -247,6 +246,16 @@
|
||||
"search": "全局搜索",
|
||||
"logout": "退出登录",
|
||||
"preferences": "偏好设置"
|
||||
},
|
||||
"widget": {
|
||||
"title": "小部件",
|
||||
"global-search": "启用全局搜索",
|
||||
"fullscreen": "启用全屏",
|
||||
"theme-toggle": "启用主题切换",
|
||||
"language-toggle": "启用语言切换",
|
||||
"notification": "启用通知",
|
||||
"sidebar-toggle": "启用侧边栏切换",
|
||||
"ai-assistant": "启用 AI 助手"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
packages/@core/shared/typings/src/flatten.d.ts
vendored
40
packages/@core/shared/typings/src/flatten.d.ts
vendored
@@ -1,40 +0,0 @@
|
||||
// `Prev` 类型用于表示递归深度的递减。它是一个元组,其索引代表了递归的层数,通过索引访问可以得到减少后的层数。
|
||||
// 例如,Prev[3] 等于 2,表示递归深度从 3 减少到 2。
|
||||
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...0[]];
|
||||
|
||||
// `FlattenDepth` 类型用于将一个嵌套的对象类型“展平”,同时考虑到了递归的深度。
|
||||
// 它接受三个泛型参数:T(要处理的类型),Prefix(属性名前缀,默认为空字符串),Depth(递归深度,默认为3)。
|
||||
// 如果当前深度(Depth)为 0,则停止递归并返回 `never`。否则,如果属性值是对象类型,则递归调用 `FlattenDepth` 并递减深度。
|
||||
// 对于非对象类型的属性,将其直接映射到结果类型中,并根据前缀构造属性名。
|
||||
|
||||
type FlattenDepth<T, Prefix extends string = '', Depth extends number = 4> = {
|
||||
[K in keyof T]: T[K] extends object
|
||||
? Depth extends 0
|
||||
? never
|
||||
: FlattenDepth<
|
||||
T[K],
|
||||
`${Prefix}${K extends string ? (Prefix extends '' ? K : Capitalize<K>) : ''}`,
|
||||
Prev[Depth]
|
||||
>
|
||||
: {
|
||||
[P in `${Prefix}${K extends string ? (Prefix extends '' ? K : Capitalize<K>) : ''}`]: T[K];
|
||||
};
|
||||
}[keyof T] extends infer O
|
||||
? { [P in keyof O]: O[P] }
|
||||
: never;
|
||||
|
||||
// `UnionToIntersection` 类型用于将一个联合类型转换为交叉类型。
|
||||
// 这个类型通过条件类型和类型推断的方式来实现。它先尝试将输入类型(U)映射为一个函数类型,
|
||||
// 然后通过推断这个函数类型的返回类型(infer I),最终得到一个交叉类型。
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
||||
k: infer I,
|
||||
) => void
|
||||
? I
|
||||
: never;
|
||||
|
||||
type Flatten<T> = UnionToIntersection<FlattenDepth<T>>;
|
||||
|
||||
type FlattenObject<T> = FlattenDepth<T>;
|
||||
type FlattenObjectKeys<T> = keyof FlattenObject<T>;
|
||||
|
||||
export type { Flatten, FlattenObject, FlattenObjectKeys, UnionToIntersection };
|
@@ -1,5 +1,4 @@
|
||||
export type * from './app';
|
||||
export type * from './flatten';
|
||||
export type * from './helper';
|
||||
export type * from './menu-record';
|
||||
export type * from './tabs';
|
||||
|
@@ -87,6 +87,11 @@ interface VbenLayoutProps {
|
||||
* @default 'fixed'
|
||||
*/
|
||||
headerMode?: LayoutHeaderModeType;
|
||||
/**
|
||||
* 是否显示header切换侧边栏按钮
|
||||
* @default
|
||||
*/
|
||||
headerToggleSidebarButton?: boolean;
|
||||
/**
|
||||
* header是否显示
|
||||
* @default true
|
||||
@@ -152,21 +157,21 @@ interface VbenLayoutProps {
|
||||
* @default 210
|
||||
*/
|
||||
sidebarWidth?: number;
|
||||
/**
|
||||
* footer背景颜色
|
||||
* @default #fff
|
||||
*/
|
||||
tabbarBackgroundColor?: string;
|
||||
/**
|
||||
* tab是否可见
|
||||
* @default true
|
||||
*/
|
||||
tabbarEnable?: boolean;
|
||||
/**
|
||||
* footer背景颜色
|
||||
* @default #fff
|
||||
*/
|
||||
tabsBackgroundColor?: string;
|
||||
/**
|
||||
* tab高度
|
||||
* @default 30
|
||||
*/
|
||||
tabsHeight?: number;
|
||||
tabbarHeight?: number;
|
||||
/**
|
||||
* zIndex
|
||||
* @default 100
|
||||
|
@@ -32,8 +32,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
headerHeight: 50,
|
||||
headerHeightOffset: 10,
|
||||
headerHidden: false,
|
||||
|
||||
headerMode: 'fixed',
|
||||
headerToggleSidebarButton: true,
|
||||
headerVisible: true,
|
||||
isMobile: false,
|
||||
layout: 'sidebar-nav',
|
||||
@@ -45,7 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
sidebarTheme: 'dark',
|
||||
sidebarWidth: 180,
|
||||
tabbarEnable: true,
|
||||
tabsHeight: 36,
|
||||
tabbarHeight: 36,
|
||||
zIndex: 200,
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ const headerWrapperHeight = computed(() => {
|
||||
height += getHeaderHeight.value;
|
||||
}
|
||||
if (props.tabbarEnable) {
|
||||
height += props.tabsHeight;
|
||||
height += props.tabbarHeight;
|
||||
}
|
||||
return height;
|
||||
});
|
||||
@@ -364,6 +364,7 @@ const maskStyle = computed((): CSSProperties => {
|
||||
|
||||
const showHeaderToggleButton = computed(() => {
|
||||
return (
|
||||
props.headerToggleSidebarButton &&
|
||||
isSideMode.value &&
|
||||
!isSidebarMixedNav.value &&
|
||||
!isMixedNav.value &&
|
||||
@@ -528,7 +529,7 @@ function handleOpenMenu() {
|
||||
|
||||
<LayoutTabbar
|
||||
v-if="tabbarEnable"
|
||||
:height="tabsHeight"
|
||||
:height="tabbarHeight"
|
||||
:style="tabbarStyle"
|
||||
>
|
||||
<slot name="tabbar"></slot>
|
||||
|
@@ -13,7 +13,7 @@ interface Props {
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'CodeAuthority',
|
||||
name: 'CodeAccess',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
@@ -1,5 +1,5 @@
|
||||
export { default as CodeAuthority } from './code-authority.vue';
|
||||
export { default as CodeAccess } from './code-access.vue';
|
||||
export * from './generate-menu-and-routes';
|
||||
export { default as RoleAuthority } from './role-authority.vue';
|
||||
export { default as RoleAccess } from './role-access.vue';
|
||||
export type * from './types';
|
||||
export * from './use-access';
|
||||
|
@@ -13,7 +13,7 @@ interface Props {
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'RoleAuthority',
|
||||
name: 'RoleAccess',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { usePreferences } from '@vben-core/preferences';
|
||||
import { preferences, usePreferences } from '@vben-core/preferences';
|
||||
import { VbenFullScreen } from '@vben-core/shadcn-ui';
|
||||
import { useCoreAccessStore } from '@vben-core/stores';
|
||||
|
||||
@@ -33,14 +33,15 @@ const { globalSearchShortcutKey } = usePreferences();
|
||||
</div>
|
||||
<div class="flex h-full min-w-0 flex-shrink-0 items-center">
|
||||
<GlobalSearch
|
||||
v-if="preferences.widget.globalSearch"
|
||||
:enable-shortcut-key="globalSearchShortcutKey"
|
||||
:menus="accessStore.accessMenus"
|
||||
class="mr-4"
|
||||
/>
|
||||
<ThemeToggle class="mr-2" />
|
||||
<LanguageToggle class="mr-2" />
|
||||
<VbenFullScreen class="mr-2" />
|
||||
<slot name="notification"></slot>
|
||||
<ThemeToggle v-if="preferences.widget.themeToggle" class="mr-2" />
|
||||
<LanguageToggle v-if="preferences.widget.languageToggle" class="mr-2" />
|
||||
<VbenFullScreen v-if="preferences.widget.fullscreen" class="mr-2" />
|
||||
<slot v-if="preferences.widget.notification" name="notification"></slot>
|
||||
<slot name="user-dropdown"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -38,7 +38,7 @@ const headerMenuTheme = computed(() => {
|
||||
});
|
||||
|
||||
const theme = computed(() => {
|
||||
const dark = isDark.value || preferences.app.semiDarkMenu;
|
||||
const dark = isDark.value || preferences.theme.semiDarkMenu;
|
||||
return dark ? 'dark' : 'light';
|
||||
});
|
||||
|
||||
@@ -122,6 +122,7 @@ function clearPreferencesAndLogout() {
|
||||
:footer-fixed="preferences.footer.fixed"
|
||||
:header-hidden="preferences.header.hidden"
|
||||
:header-mode="preferences.header.mode"
|
||||
:header-toggle-sidebar-button="preferences.widget.sidebarToggle"
|
||||
:header-visible="preferences.header.enable"
|
||||
:is-mobile="preferences.app.isMobile"
|
||||
:layout="layout"
|
||||
@@ -131,7 +132,7 @@ function clearPreferencesAndLogout() {
|
||||
:sidebar-expand-on-hover="preferences.sidebar.expandOnHover"
|
||||
:sidebar-extra-collapse="preferences.sidebar.extraCollapse"
|
||||
:sidebar-hidden="preferences.sidebar.hidden"
|
||||
:sidebar-semi-dark="preferences.app.semiDarkMenu"
|
||||
:sidebar-semi-dark="preferences.theme.semiDarkMenu"
|
||||
:sidebar-theme="theme"
|
||||
:sidebar-width="preferences.sidebar.width"
|
||||
:tabbar-enable="preferences.tabbar.enable"
|
||||
@@ -158,7 +159,7 @@ function clearPreferencesAndLogout() {
|
||||
|
||||
<template #floating-groups>
|
||||
<CozeAssistant
|
||||
v-if="preferences.app.aiAssistant"
|
||||
v-if="preferences.widget.aiAssistant"
|
||||
:is-mobile="preferences.app.isMobile"
|
||||
/>
|
||||
<VbenBackTop />
|
||||
|
@@ -13,7 +13,6 @@ defineOptions({
|
||||
|
||||
const appLocale = defineModel<string>('appLocale');
|
||||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||
const appAiAssistant = defineModel<boolean>('appAiAssistant');
|
||||
|
||||
const localeItems: SelectListItem[] = SUPPORT_LANGUAGES.map((item) => ({
|
||||
label: item.text,
|
||||
@@ -28,7 +27,4 @@ const localeItems: SelectListItem[] = SUPPORT_LANGUAGES.map((item) => ({
|
||||
<SwitchItem v-model="appDynamicTitle">
|
||||
{{ $t('preferences.dynamic-title') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="appAiAssistant">
|
||||
{{ $t('preferences.ai-assistant') }}
|
||||
</SwitchItem>
|
||||
</template>
|
||||
|
@@ -10,6 +10,7 @@ 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 Tabbar } from './layout/tabbar.vue';
|
||||
export { default as Widget } from './layout/widget.vue';
|
||||
export { default as GlobalShortcutKeys } from './shortcut-keys/global.vue';
|
||||
export { default as SwitchItem } from './switch-item.vue';
|
||||
export { default as BuiltinTheme } from './theme/builtin.vue';
|
||||
|
@@ -1,21 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@vben-core/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('preferences.tabbar.enable') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="logoVisible">
|
||||
{{ $t('preferences.logo-visible') }}
|
||||
</SwitchItem>
|
||||
</template>
|
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@vben-core/locales';
|
||||
|
||||
import SwitchItem from '../switch-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceInterfaceControl',
|
||||
});
|
||||
|
||||
const widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');
|
||||
const widgetFullscreen = defineModel<boolean>('widgetFullscreen');
|
||||
const widgetLanguageToggle = defineModel<boolean>('widgetLanguageToggle');
|
||||
const widgetNotification = defineModel<boolean>('widgetNotification');
|
||||
const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
|
||||
const widgetAiAssistant = defineModel<boolean>('widgetAiAssistant');
|
||||
const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SwitchItem v-model="widgetGlobalSearch">
|
||||
{{ $t('preferences.widget.global-search') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="widgetThemeToggle">
|
||||
{{ $t('preferences.widget.theme-toggle') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="widgetLanguageToggle">
|
||||
{{ $t('preferences.widget.language-toggle') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="widgetFullscreen">
|
||||
{{ $t('preferences.widget.fullscreen') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="widgetNotification">
|
||||
{{ $t('preferences.widget.notification') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="widgetAiAssistant">
|
||||
{{ $t('preferences.widget.ai-assistant') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="widgetSidebarToggle">
|
||||
{{ $t('preferences.widget.sidebar-toggle') }}
|
||||
</SwitchItem>
|
||||
</template>
|
@@ -51,6 +51,7 @@ import {
|
||||
Sidebar,
|
||||
Tabbar,
|
||||
Theme,
|
||||
Widget,
|
||||
} from './blocks';
|
||||
import IconSetting from './icons/setting.vue';
|
||||
import { useOpenPreferences } from './use-open-preferences';
|
||||
@@ -59,7 +60,6 @@ const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
||||
const { toast } = useToast();
|
||||
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
|
||||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||
const appAiAssistant = defineModel<boolean>('appAiAssistant');
|
||||
const appLayout = defineModel<LayoutType>('appLayout');
|
||||
const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
|
||||
const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
|
||||
@@ -129,6 +129,14 @@ const shortcutKeysGlobalPreferences = defineModel<boolean>(
|
||||
'shortcutKeysGlobalPreferences',
|
||||
);
|
||||
|
||||
const widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');
|
||||
const widgetFullscreen = defineModel<boolean>('widgetFullscreen');
|
||||
const widgetLanguageToggle = defineModel<boolean>('widgetLanguageToggle');
|
||||
const widgetNotification = defineModel<boolean>('widgetNotification');
|
||||
const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
|
||||
const widgetAiAssistant = defineModel<boolean>('widgetAiAssistant');
|
||||
const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
|
||||
|
||||
const {
|
||||
diffPreference,
|
||||
isDark,
|
||||
@@ -245,7 +253,6 @@ async function handleReset() {
|
||||
<template #general>
|
||||
<Block :title="$t('preferences.general')">
|
||||
<General
|
||||
v-model:app-ai-assistant="appAiAssistant"
|
||||
v-model:app-dynamic-title="appDynamicTitle"
|
||||
v-model:app-locale="appLocale"
|
||||
/>
|
||||
@@ -346,6 +353,17 @@ async function handleReset() {
|
||||
v-model:tabbar-show-icon="tabbarShowIcon"
|
||||
/>
|
||||
</Block>
|
||||
<Block :title="$t('preferences.widget.title')">
|
||||
<Widget
|
||||
v-model:widget-ai-assistant="widgetAiAssistant"
|
||||
v-model:widget-fullscreen="widgetFullscreen"
|
||||
v-model:widget-global-search="widgetGlobalSearch"
|
||||
v-model:widget-language-toggle="widgetLanguageToggle"
|
||||
v-model:widget-notification="widgetNotification"
|
||||
v-model:widget-sidebar-toggle="widgetSidebarToggle"
|
||||
v-model:widget-theme-toggle="widgetThemeToggle"
|
||||
/>
|
||||
</Block>
|
||||
<Block :title="$t('preferences.footer.title')">
|
||||
<Footer
|
||||
v-model:footer-enable="footerEnable"
|
||||
|
Reference in New Issue
Block a user