feat: Feature/pro docs (#70)

* chore: merge main

* feat: update docs

* feat: remove coze-assistant

* feat: add watermark plugin

* feat: update preferences

* feat: update docs

---------

Co-authored-by: vince <vince292007@gmail.com>
This commit is contained in:
Vben
2024-07-28 14:29:05 +08:00
committed by GitHub
parent 14538f7ed5
commit 376fd17a61
225 changed files with 7731 additions and 1784 deletions

View File

@@ -40,7 +40,7 @@
"@vueuse/core": "^10.11.0",
"radix-vue": "^1.9.2",
"sortablejs": "^1.15.2",
"vue": "^3.4.33"
"vue": "^3.4.34"
},
"devDependencies": {
"@types/sortablejs": "^1.15.8"

View File

@@ -31,6 +31,6 @@
"@vben-core/toolkit": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.0",
"vue": "^3.4.33"
"vue": "^3.4.34"
}
}

View File

@@ -9,14 +9,15 @@ const defaultPreferences: Preferences = {
compact: false,
contentCompact: 'wide',
defaultAvatar:
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/avatar-v1.webp',
'https://unpkg.com/@vbenjs/static-source@0.1.5/source/avatar-v1.webp',
dynamicTitle: true,
enablePreferences: true,
isMobile: false,
layout: 'sidebar-nav',
locale: 'zh-CN',
loginExpiredMode: 'page',
loginExpiredMode: 'modal',
name: 'Vben Admin',
watermark: false,
},
breadcrumb: {
enable: true,
@@ -44,8 +45,7 @@ const defaultPreferences: Preferences = {
},
logo: {
enable: true,
source:
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/logo-v1.webp',
source: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp',
},
navigation: {
accordion: true,
@@ -75,6 +75,9 @@ const defaultPreferences: Preferences = {
keepAlive: true,
persist: true,
showIcon: true,
showMaximize: true,
showMore: true,
showRefresh: true,
styleType: 'chrome',
},
theme: {
@@ -94,7 +97,6 @@ const defaultPreferences: Preferences = {
progress: true,
},
widget: {
aiAssistant: true,
fullscreen: true,
globalSearch: true,
languageToggle: true,

View File

@@ -1,12 +1,4 @@
import type {
BuiltinThemeType,
SupportedLanguagesType,
} from '@vben-core/typings';
interface Language {
key: SupportedLanguagesType;
text: string;
}
import type { BuiltinThemeType } from '@vben-core/typings';
interface BuiltinThemePreset {
color: string;
@@ -15,25 +7,11 @@ interface BuiltinThemePreset {
type: BuiltinThemeType;
}
/**
* Supported languages
*/
const SUPPORT_LANGUAGES: Language[] = [
{
key: 'zh-CN',
text: '简体中文',
},
{
key: 'en-US',
text: 'English',
},
];
const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
{
color: 'hsl(231 98% 65%)',
type: 'default',
},
// {
// color: 'hsl(231 98% 65%)',
// type: 'default',
// },
{
color: 'hsl(245 82% 67%)',
type: 'violet',
@@ -102,6 +80,6 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
export const COLOR_PRESETS = [...BUILT_IN_THEME_PRESETS].slice(0, 7);
export { BUILT_IN_THEME_PRESETS, SUPPORT_LANGUAGES };
export { BUILT_IN_THEME_PRESETS };
export type { BuiltinThemePreset };

View File

@@ -24,17 +24,6 @@ describe('preferences', () => {
preferenceManager = new PreferenceManager();
});
it('initPreferences should initialize preferences with overrides and namespace', async () => {
const overrides = { theme: { colorPrimary: 'hsl(231 98% 65%)' } };
const namespace = 'testNamespace';
await preferenceManager.initPreferences({ namespace, overrides });
expect(preferenceManager.getPreferences().theme.colorPrimary).toBe(
overrides.theme.colorPrimary,
);
});
it('loads default preferences if no saved preferences found', () => {
const preferences = preferenceManager.getPreferences();
expect(preferences).toEqual(defaultPreferences);

View File

@@ -41,7 +41,7 @@ class PreferenceManager {
this.savePreferences = useDebounceFn(
(preference: Preferences) => this._savePreferences(preference),
100,
150,
);
}

View File

@@ -10,11 +10,12 @@ import type {
LoginExpiredModeType,
NavigationStyleType,
PageTransitionType,
SupportedLanguagesType,
TabsStyleType,
ThemeModeType,
} from '@vben-core/typings';
type SupportedLanguagesType = 'en-US' | 'zh-CN';
interface AppPreferences {
/** 权限模式 */
accessMode: AccessModeType;
@@ -44,6 +45,10 @@ interface AppPreferences {
loginExpiredMode: LoginExpiredModeType;
/** 应用名 */
name: string;
/**
* @zh_CN 是否开启水印
*/
watermark: boolean;
}
interface BreadcrumbPreferences {
@@ -149,6 +154,12 @@ interface TabbarPreferences {
persist: boolean;
/** 是否开启多标签页图标 */
showIcon: boolean;
/** 显示最大化按钮 */
showMaximize: boolean;
/** 显示更多按钮 */
showMore: boolean;
/** 显示刷新按钮 */
showRefresh: boolean;
/** 标签页风格 */
styleType: TabsStyleType;
}
@@ -184,8 +195,6 @@ interface TransitionPreferences {
}
interface WidgetPreferences {
/** 是否开启vben助手部件 */
aiAssistant: boolean;
/** 是否启用全屏部件 */
fullscreen: boolean;
/** 是否启用全局搜索部件 */
@@ -249,6 +258,7 @@ export type {
PreferencesKeys,
ShortcutKeyPreferences,
SidebarPreferences,
SupportedLanguagesType,
TabbarPreferences,
ThemePreferences,
TransitionPreferences,

View File

@@ -5,7 +5,7 @@ import {
generatorColorVariables,
} from '@vben-core/toolkit';
import { BUILT_IN_THEME_PRESETS } from './constants';
import { BUILT_IN_THEME_PRESETS, type BuiltinThemePreset } from './constants';
/**
* 更新主题的 CSS 变量以及其他 CSS 变量
@@ -37,9 +37,13 @@ function updateCSSVariables(preferences: Preferences) {
}
// 获取当前的内置主题
const currentBuiltType = BUILT_IN_THEME_PRESETS.find(
(item) => item.type === builtinType,
);
const currentBuiltType = [
{
color: preferences.theme.colorPrimary,
type: 'default',
} as BuiltinThemePreset,
...BUILT_IN_THEME_PRESETS,
].find((item) => item.type === builtinType);
let builtinTypeColorPrimary: string | undefined = '';

View File

@@ -12,7 +12,7 @@ const VBEN_DOC_URL = 'https://doc.vben.pro';
* @zh_CN Vben Logo
*/
const VBEN_LOGO_URL =
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/logo-v1.webp';
'https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp';
/**
* @zh_CN Vben Admin 首页地址

View File

@@ -85,17 +85,21 @@
/* 只有非mac下才进行调整mac下使用默认滚动条 */
html:not([data-platform='macOs']) {
*::-webkit-scrollbar {
::-webkit-scrollbar {
@apply h-[1px] w-[10px];
}
*::-webkit-scrollbar-thumb {
::-webkit-scrollbar-thumb {
@apply bg-border rounded-sm border-none;
}
*::-webkit-scrollbar-track {
::-webkit-scrollbar-track {
@apply rounded-sm border-none bg-transparent shadow-none;
}
::-webkit-scrollbar-button {
@apply hidden;
}
}
}

View File

@@ -35,7 +35,7 @@
},
"dependencies": {
"@iconify/vue": "^4.1.2",
"lucide-vue-next": "^0.414.0",
"vue": "^3.4.33"
"lucide-vue-next": "^0.416.0",
"vue": "^3.4.34"
}
}

View File

@@ -36,7 +36,7 @@
},
"dependencies": {
"@ctrl/tinycolor": "^4.1.0",
"@vue/shared": "^3.4.33",
"@vue/shared": "^3.4.34",
"clsx": "^2.1.1",
"defu": "^6.1.4",
"lodash.clonedeep": "^4.5.0",

View File

@@ -38,7 +38,7 @@
}
},
"dependencies": {
"vue": "^3.4.33",
"vue": "^3.4.34",
"vue-router": "^4.4.0"
}
}

View File

@@ -1,5 +1,3 @@
type SupportedLanguagesType = 'en-US' | 'zh-CN';
type LayoutType =
| 'full-content'
| 'header-nav'
@@ -26,7 +24,8 @@ type BuiltinThemeType =
| 'stone'
| 'violet'
| 'yellow'
| 'zinc';
| 'zinc'
| (Record<never, never> & string);
type ContentCompactType = 'compact' | 'wide';
@@ -34,20 +33,52 @@ type LayoutHeaderModeType = 'auto' | 'auto-scroll' | 'fixed' | 'static';
/**
* 登录过期模式
* 'modal' 弹窗模式 | 'page' 页面模式
* modal 弹窗模式
* page 页面模式
*/
type LoginExpiredModeType = 'modal' | 'page';
/**
* 面包屑样式
* background 背景
* normal 默认
*/
type BreadcrumbStyleType = 'background' | 'normal';
type AccessModeType = 'allow-all' | 'backend' | 'frontend';
/**
* 权限模式
* backend 后端权限模式
* frontend 前端权限模式
*/
type AccessModeType = 'backend' | 'frontend';
/**
* 导航风格
* plain 朴素
* rounded 圆润
*/
type NavigationStyleType = 'plain' | 'rounded';
/**
* 标签栏风格
* brisk 轻快
* card 卡片
* chrome 谷歌
* plain 朴素
*/
type TabsStyleType = 'brisk' | 'card' | 'chrome' | 'plain';
/**
* 页面切换动画
*/
type PageTransitionType = 'fade' | 'fade-down' | 'fade-slide' | 'fade-up';
/**
* 页面切换动画
* panel-center 居中布局
* panel-left 居左布局
* panel-right 居右布局
*/
type AuthPageLayoutType = 'panel-center' | 'panel-left' | 'panel-right';
export type {
@@ -61,7 +92,6 @@ export type {
LoginExpiredModeType,
NavigationStyleType,
PageTransitionType,
SupportedLanguagesType,
TabsStyleType,
ThemeModeType,
};

View File

@@ -28,6 +28,10 @@ interface MenuRecordBadgeRaw {
* 菜单原始对象
*/
interface MenuRecordRaw extends MenuRecordBadgeRaw {
/**
* 激活时的图标名
*/
activeIcon?: string;
/**
* 子菜单
*/

View File

@@ -3,6 +3,10 @@ import type { Router, RouteRecordRaw } from 'vue-router';
import type { Component } from 'vue';
interface RouteMeta {
/**
* 激活图标(菜单/tab
*/
activeIcon?: string;
/**
* 当前激活的菜单,有时候不想激活现有菜单,需要激活父级菜单时使用
* @default false
@@ -13,6 +17,11 @@ interface RouteMeta {
* @default false
*/
affixTab?: boolean;
/**
* 固定标签页的顺序
* @default 0
*/
affixTabOrder?: number;
/**
* 需要特定的角色标识才可以访问
* @default []
@@ -56,10 +65,6 @@ interface RouteMeta {
* @default false
*/
hideInTab?: boolean;
/**
* 路由跳转地址
*/
href?: string;
/**
* 图标(菜单/tab
*/
@@ -87,7 +92,7 @@ interface RouteMeta {
loaded?: boolean;
/**
* 标签页最大打开数量
* @default false
* @default -1
*/
maxNumOfOpenTab?: number;
/**
@@ -126,5 +131,6 @@ export type {
ComponentRecordType,
GenerateMenuAndRoutesOptions,
RouteMeta,
RouteRecordRaw,
RouteRecordStringComponent,
};

View File

@@ -42,6 +42,6 @@
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.0",
"vue": "^3.4.33"
"vue": "^3.4.34"
}
}

View File

@@ -43,6 +43,6 @@
"@vben-core/toolkit": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.0",
"vue": "^3.4.33"
"vue": "^3.4.34"
}
}

View File

@@ -26,6 +26,10 @@ const subMenu = useSubMenuContext();
const { parentMenu, parentPaths } = useMenu();
const active = computed(() => props.path === rootMenu?.activePath);
const menuIcon = computed(() =>
active.value ? props.activeIcon || props.icon : props.icon,
);
const isTopLevelMenuItem = computed(
() => parentMenu.value?.type.name === 'Menu',
);
@@ -94,7 +98,7 @@ onBeforeUnmount(() => {
>
<template #trigger>
<div :class="[nsMenu.be('tooltip', 'trigger')]">
<VbenIcon :class="nsMenu.e('icon')" :icon="icon" fallback />
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
<slot></slot>
<span v-if="collapseShowTitle" :class="nsMenu.e('name')">
<slot name="title"></slot>
@@ -109,7 +113,7 @@ onBeforeUnmount(() => {
class="right-2"
v-bind="props"
/>
<VbenIcon :class="nsMenu.e('icon')" :icon="icon" fallback />
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
<slot></slot>
<slot name="title"></slot>
</div>

View File

@@ -12,7 +12,7 @@ defineOptions({
name: 'NormalMenu',
});
withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<Props>(), {
activePath: '',
collapse: false,
menus: () => [],
@@ -25,6 +25,12 @@ const emit = defineEmits<{
}>();
const { b, e, is } = useNamespace('normal-menu');
function menuIcon(menu: MenuRecordRaw) {
return props.activePath === menu.path
? menu.activeIcon || menu.icon
: menu.icon;
}
</script>
<template>
@@ -44,7 +50,8 @@ const { b, e, is } = useNamespace('normal-menu');
@click="() => emit('select', menu)"
@mouseenter="() => emit('enter', menu)"
>
<VbenIcon :class="e('icon')" :icon="menu.icon" fallback />
<VbenIcon :class="e('icon')" :icon="menuIcon(menu)" fallback />
<span :class="e('name')" class="truncate"> {{ menu.name }}</span>
</li>
</template>

View File

@@ -172,6 +172,10 @@ function handleMouseleave(deepDispatch = false) {
}
}
const menuIcon = computed(() =>
active.value ? props.activeIcon || props.icon : props.icon,
);
const item = reactive({
active,
parentPaths,
@@ -215,7 +219,7 @@ onBeforeUnmount(() => {
<template #trigger>
<SubMenuContent
:class="is('active', active)"
:icon="icon"
:icon="menuIcon"
:is-menu-more="isSubMenuMore"
:is-top-level-menu-submenu="isTopLevelMenuSubmenu"
:level="currentLevel"
@@ -246,7 +250,7 @@ onBeforeUnmount(() => {
<template v-else>
<SubMenuContent
:class="is('active', active)"
:icon="icon"
:icon="menuIcon"
:is-menu-more="isSubMenuMore"
:is-top-level-menu-submenu="isTopLevelMenuSubmenu"
:level="currentLevel"

View File

@@ -50,6 +50,10 @@ interface MenuProps {
}
interface SubMenuProps extends MenuRecordBadgeRaw {
/**
* @zh_CN 激活图标
*/
activeIcon?: string;
/**
* @zh_CN 是否禁用
*/
@@ -65,6 +69,10 @@ interface SubMenuProps extends MenuRecordBadgeRaw {
}
interface MenuItemProps extends MenuRecordBadgeRaw {
/**
* @zh_CN 图标
*/
activeIcon?: string;
/**
* @zh_CN 是否禁用
*/

View File

@@ -31,12 +31,19 @@ const hasChildren = computed(() => {
Reflect.has(menu, 'children') && !!menu.children && menu.children.length > 0
);
});
// function menuIcon(menu: MenuRecordRaw) {
// return props.activePath === menu.path
// ? menu.activeIcon || menu.icon
// : menu.icon;
// }
</script>
<template>
<MenuItem
v-if="!hasChildren"
:key="menu.path"
:active-icon="menu.activeIcon"
:badge="menu.badge"
:badge-type="menu.badgeType"
:badge-variants="menu.badgeVariants"
@@ -48,6 +55,7 @@ const hasChildren = computed(() => {
<SubMenuComp
v-else
:key="`${menu.path}_sub`"
:active-icon="menu.activeIcon"
:icon="menu.icon"
:path="menu.path"
>

View File

@@ -48,8 +48,8 @@
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.0",
"class-variance-authority": "^0.7.0",
"lucide-vue-next": "^0.414.0",
"lucide-vue-next": "^0.416.0",
"radix-vue": "^1.9.2",
"vue": "^3.4.33"
"vue": "^3.4.34"
}
}

View File

@@ -32,14 +32,14 @@ function handleItemClick(menu: IDropdownMenuItem) {
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuGroup>
<template v-for="menu in menus" :key="menu.key">
<template v-for="menu in menus" :key="menu.value">
<DropdownMenuItem
:disabled="menu.disabled"
class="data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground text-foreground/80 mb-1 cursor-pointer"
@click="handleItemClick(menu)"
>
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
{{ menu.text }}
{{ menu.label }}
</DropdownMenuItem>
<DropdownMenuSeparator v-if="menu.separator" class="bg-border" />
</template>

View File

@@ -30,18 +30,20 @@ function handleItemClick(value: string) {
<template v-for="menu in menus" :key="menu.key">
<DropdownMenuItem
:class="
menu.key === modelValue ? 'bg-accent text-accent-foreground' : ''
menu.value === modelValue
? 'bg-accent text-accent-foreground'
: ''
"
class="data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground text-foreground/80 mb-1 cursor-pointer"
@click="handleItemClick(menu.key)"
@click="handleItemClick(menu.value)"
>
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
<span
v-if="!menu.icon"
:class="menu.key === modelValue ? 'bg-foreground' : ''"
:class="menu.value === modelValue ? 'bg-foreground' : ''"
class="mr-2 size-1.5 rounded-full"
></span>
{{ menu.text }}
{{ menu.label }}
</DropdownMenuItem>
</template>
</DropdownMenuGroup>

View File

@@ -12,17 +12,17 @@ interface VbenDropdownMenuItem {
*/
icon?: Component;
/**
* @zh_CN 唯一标识
* @zh_CN 标题
*/
key: string;
label: string;
/**
* @zh_CN 是否是分割线
*/
separator?: boolean;
/**
* @zh_CN 标题
* @zh_CN 唯一标识
*/
text: string;
value: string;
}
interface DropdownMenuProps {

View File

@@ -41,6 +41,6 @@
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/typings": "workspace:*",
"vue": "^3.4.33"
"vue": "^3.4.34"
}
}

View File

@@ -1,2 +1,3 @@
export { default as TabsToolMore } from './tool-more.vue';
export { default as TabsToolRefresh } from './tool-refresh.vue';
export { default as TabsToolScreen } from './tool-screen.vue';

View File

@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { RotateCw } from '@vben-core/icons';
const emit = defineEmits<{ refresh: [] }>();
const loading = ref(false);
function handleClick() {
loading.value = true;
setTimeout(() => {
loading.value = false;
}, 1000);
emit('refresh');
}
</script>
<template>
<div
class="flex-center hover:bg-muted hover:text-foreground text-muted-foreground border-border h-full cursor-pointer border-l px-[9px] text-lg font-semibold"
@click="handleClick"
>
<RotateCw
:class="{
'animate-spin duration-1000': loading,
}"
class="size-4"
/>
</div>
</template>