feat: new interface pendant can be configured to display hidden
parent
db76325d68
commit
a765d3bbc0
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Description
|
||||
|
||||
Vben Admin Pro 数据mock服务
|
||||
Vben Admin Pro 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。由于 sqlite 安装需要在本地进行编译,所以这里接口是直接返回的。线上环境不再提供mock集成,可自行部署服务或者对接真实数据,同步 mock.js等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了 真是的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。
|
||||
|
||||
## Running the app
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<!-- 由 vite 注入 VITE_GLOB_APP_TITLE 变量,在 . env 内配置 -->
|
||||
<!-- 由 vite 注入 VITE_GLOB_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||
<title><%= VITE_GLOB_APP_TITLE %></title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</head>
|
||||
|
|
|
@ -10,7 +10,8 @@ import { getAllMenus } from '#/apis';
|
|||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const forbiddenPage = () => import('#/views/_essential/fallback/forbidden.vue');
|
||||
const forbiddenComponent = () =>
|
||||
import('#/views/_essential/fallback/forbidden.vue');
|
||||
|
||||
async function generateAccess(options: GeneratorMenuAndRoutesOptions) {
|
||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||
|
@ -30,7 +31,7 @@ async function generateAccess(options: GeneratorMenuAndRoutesOptions) {
|
|||
return await getAllMenus();
|
||||
},
|
||||
// 可以指定没有权限跳转403页面
|
||||
forbiddenComponent: forbiddenPage,
|
||||
forbiddenComponent,
|
||||
// 如果 route.meta.menuVisibleWithForbidden = true
|
||||
layoutMap,
|
||||
pageMap,
|
||||
|
|
|
@ -10,7 +10,7 @@ import dayjs from 'dayjs';
|
|||
|
||||
const antdLocale = ref<Locale>(defaultLocale);
|
||||
|
||||
const modules = import.meta.glob('./langs/*.y(a)?ml');
|
||||
const modules = import.meta.glob('./langs/*.json');
|
||||
|
||||
const localesMap = loadLocalesMap(modules);
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"page": {
|
||||
"demos": {
|
||||
"title": "Demos",
|
||||
"access": {
|
||||
"title": "Access Control",
|
||||
"frontend-control": "Front-end Control",
|
||||
"backend-control": "Backend Control",
|
||||
"page": "Page visit",
|
||||
"button": "Button control",
|
||||
"loading-menu": "In the loading menu",
|
||||
"access-test-1": "Super visit",
|
||||
"access-test-2": "Admin visit",
|
||||
"access-test-3": "User visit"
|
||||
},
|
||||
"nested": {
|
||||
"title": "Nested Menu",
|
||||
"menu1": "Menu 1",
|
||||
"menu2": "Menu 2",
|
||||
"menu21": "Menu 2-1",
|
||||
"menu3": "Menu 3",
|
||||
"menu31": "Menu 3-1",
|
||||
"menu32": "Menu 3-2",
|
||||
"menu321": "Menu 3-2-1"
|
||||
},
|
||||
"outside": {
|
||||
"title": "External Page",
|
||||
"embedded": "embedded Page",
|
||||
"external-link": "External Link"
|
||||
},
|
||||
"fallback": { "title": "Fallback Page" }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
page:
|
||||
demos:
|
||||
title: Demos
|
||||
access:
|
||||
title: Access Control
|
||||
frontend-control: Front-end Control
|
||||
backend-control: Backend Control
|
||||
page: Page visit
|
||||
button: Button control
|
||||
loading-menu: In the loading menu
|
||||
access-test-1: Super visit
|
||||
access-test-2: Admin visit
|
||||
access-test-3: User visit
|
||||
nested:
|
||||
title: Nested Menu
|
||||
menu1: Menu 1
|
||||
menu2: Menu 2
|
||||
menu21: Menu 2-1
|
||||
menu3: Menu 3
|
||||
menu31: Menu 3-1
|
||||
menu32: Menu 3-2
|
||||
menu321: Menu 3-2-1
|
||||
outside:
|
||||
title: External Page
|
||||
embedded: embedded Page
|
||||
external-link: External Link
|
||||
fallback:
|
||||
title: Fallback Page
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"page": {
|
||||
"demos": {
|
||||
"title": "演示",
|
||||
"access": {
|
||||
"title": "访问控制",
|
||||
"frontend-control": "前端控制",
|
||||
"backend-control": "后端控制",
|
||||
"page": "页面访问",
|
||||
"button": "按钮控制",
|
||||
"access-test-1": "Super 可见",
|
||||
"access-test-2": "Admin 可见",
|
||||
"access-test-3": "User 可见"
|
||||
},
|
||||
"nested": {
|
||||
"title": "嵌套菜单",
|
||||
"menu1": "菜单 1",
|
||||
"menu2": "菜单 2",
|
||||
"menu21": "菜单 2-1",
|
||||
"menu3": "菜单 3",
|
||||
"menu31": "菜单 3-1",
|
||||
"menu32": "菜单 3-2",
|
||||
"menu321": "菜单 3-2-1"
|
||||
},
|
||||
"outside": {
|
||||
"title": "外部页面",
|
||||
"embedded": "内嵌",
|
||||
"external-link": "外链"
|
||||
},
|
||||
"fallback": {
|
||||
"title": "缺省页"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
page:
|
||||
demos:
|
||||
title: 演示
|
||||
access:
|
||||
title: 访问控制
|
||||
frontend-control: 前端控制
|
||||
backend-control: 后端控制
|
||||
page: 页面访问
|
||||
button: 按钮控制
|
||||
access-test-1: Super 可见
|
||||
access-test-2: Admin 可见
|
||||
access-test-3: User 可见
|
||||
nested:
|
||||
title: 嵌套菜单
|
||||
menu1: 菜单 1
|
||||
menu2: 菜单 2
|
||||
menu21: 菜单 2-1
|
||||
menu3: 菜单 3
|
||||
menu31: 菜单 3-1
|
||||
menu32: 菜单 3-2
|
||||
menu321: 菜单 3-2-1
|
||||
outside:
|
||||
title: 外部页面
|
||||
embedded: 内嵌
|
||||
external-link: 外链
|
||||
fallback:
|
||||
title: 缺省页
|
|
@ -1,8 +1,9 @@
|
|||
import type { DeepPartial } from '@vben/types';
|
||||
import type { Preferences } from '@vben-core/preferences';
|
||||
import { defineOverridesPreferences } from '@vben-core/preferences';
|
||||
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
*/
|
||||
export const overridesPreferences: DeepPartial<Preferences> = {};
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { LoginAndRegisterParams } from '@vben/universal-ui';
|
|||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { CodeAuthority, useAccess } from '@vben/access';
|
||||
import { CodeAccess, useAccess } from '@vben/access';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
|
@ -82,20 +82,20 @@ async function changeAccount(role: string) {
|
|||
|
||||
<div class="card-box mt-5 p-5 font-semibold">
|
||||
<div class="mb-3 text-lg">组件形式控制</div>
|
||||
<CodeAuthority :value="['AC_100100']">
|
||||
<CodeAccess :value="['AC_100100']">
|
||||
<Button class="mr-4"> Super 账号可见 ["AC_1000001"] </Button>
|
||||
</CodeAuthority>
|
||||
<CodeAuthority :value="['AC_100030']">
|
||||
</CodeAccess>
|
||||
<CodeAccess :value="['AC_100030']">
|
||||
<Button class="mr-4"> Admin 账号可见 ["AC_100010"] </Button>
|
||||
</CodeAuthority>
|
||||
<CodeAuthority :value="['AC_1000001']">
|
||||
</CodeAccess>
|
||||
<CodeAccess :value="['AC_1000001']">
|
||||
<Button class="mr-4"> User 账号可见 ["AC_1000001"] </Button>
|
||||
</CodeAuthority>
|
||||
<CodeAuthority :value="['AC_100100', 'AC_100010']">
|
||||
</CodeAccess>
|
||||
<CodeAccess :value="['AC_100100', 'AC_100010']">
|
||||
<Button class="mr-4">
|
||||
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</CodeAuthority>
|
||||
</CodeAccess>
|
||||
</div>
|
||||
|
||||
<div class="card-box mt-5 p-5 font-semibold">
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { LoginAndRegisterParams } from '@vben/universal-ui';
|
|||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { CodeAuthority, RoleAuthority, useAccess } from '@vben/access';
|
||||
import { CodeAccess, RoleAccess, useAccess } from '@vben/access';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
|
@ -81,18 +81,18 @@ async function changeAccount(role: string) {
|
|||
</div>
|
||||
<div class="card-box mt-5 p-5 font-semibold">
|
||||
<div class="mb-3 text-lg">角色 - 组件形式控制</div>
|
||||
<RoleAuthority :value="['super']">
|
||||
<RoleAccess :value="['super']">
|
||||
<Button class="mr-4"> Super 角色可见 </Button>
|
||||
</RoleAuthority>
|
||||
<RoleAuthority :value="['admin']">
|
||||
</RoleAccess>
|
||||
<RoleAccess :value="['admin']">
|
||||
<Button class="mr-4"> Admin 角色可见 </Button>
|
||||
</RoleAuthority>
|
||||
<RoleAuthority :value="['user']">
|
||||
</RoleAccess>
|
||||
<RoleAccess :value="['user']">
|
||||
<Button class="mr-4"> User 角色可见 </Button>
|
||||
</RoleAuthority>
|
||||
<RoleAuthority :value="['super', 'admin']">
|
||||
</RoleAccess>
|
||||
<RoleAccess :value="['super', 'admin']">
|
||||
<Button class="mr-4"> Super & Admin 角色都可见 </Button>
|
||||
</RoleAuthority>
|
||||
</RoleAccess>
|
||||
</div>
|
||||
|
||||
<div class="card-box mt-5 p-5 font-semibold">
|
||||
|
@ -113,20 +113,20 @@ async function changeAccount(role: string) {
|
|||
|
||||
<div class="card-box mt-5 p-5 font-semibold">
|
||||
<div class="mb-3 text-lg">权限码 - 组件形式控制</div>
|
||||
<CodeAuthority :value="['AC_100100']">
|
||||
<CodeAccess :value="['AC_100100']">
|
||||
<Button class="mr-4"> Super 账号可见 ["AC_1000001"] </Button>
|
||||
</CodeAuthority>
|
||||
<CodeAuthority :value="['AC_100030']">
|
||||
</CodeAccess>
|
||||
<CodeAccess :value="['AC_100030']">
|
||||
<Button class="mr-4"> Admin 账号可见 ["AC_100010"] </Button>
|
||||
</CodeAuthority>
|
||||
<CodeAuthority :value="['AC_1000001']">
|
||||
</CodeAccess>
|
||||
<CodeAccess :value="['AC_1000001']">
|
||||
<Button class="mr-4"> User 账号可见 ["AC_1000001"] </Button>
|
||||
</CodeAuthority>
|
||||
<CodeAuthority :value="['AC_100100', 'AC_100010']">
|
||||
</CodeAccess>
|
||||
<CodeAccess :value="['AC_100100', 'AC_100010']">
|
||||
<Button class="mr-4">
|
||||
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</CodeAuthority>
|
||||
</CodeAccess>
|
||||
</div>
|
||||
|
||||
<div class="card-box mt-5 p-5 font-semibold">
|
||||
|
|
|
@ -7,10 +7,6 @@ import type {
|
|||
LibraryPluginOptions,
|
||||
} from '../typing';
|
||||
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { getPackages } from '@vben/node-utils';
|
||||
|
||||
import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||
import viteVue from '@vitejs/plugin-vue';
|
||||
import viteVueJsx from '@vitejs/plugin-vue-jsx';
|
||||
|
@ -117,28 +113,10 @@ async function loadApplicationPlugins(
|
|||
{
|
||||
condition: i18n,
|
||||
plugins: async () => {
|
||||
const { packages } = await getPackages();
|
||||
|
||||
const include: string[] = [];
|
||||
|
||||
// 加载所有应用的国际化文件
|
||||
for (const { dir, relativeDir } of packages) {
|
||||
if (
|
||||
// 排除非应用目录
|
||||
!relativeDir.startsWith('apps') ||
|
||||
// 排除mock目录
|
||||
relativeDir.includes('backend-mock')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
include.push(`${join(dir, 'src', 'locales', 'langs')}/*.yaml`);
|
||||
}
|
||||
|
||||
return [
|
||||
viteVueI18nPlugin({
|
||||
compositionOnly: true,
|
||||
fullInstall: true,
|
||||
include,
|
||||
runtimeOnly: true,
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -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 助手"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue