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

@@ -42,6 +42,7 @@
"@vben-core/menu-ui": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/tabs-ui": "workspace:*",
"@vben/constants": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/locales": "workspace:*",
@@ -50,7 +51,7 @@
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.0",
"vue": "^3.4.33",
"vue": "^3.4.34",
"vue-router": "^4.4.0"
}
}

View File

@@ -1,5 +1,11 @@
<script lang="ts" setup>
import type { RouteLocationNormalizedLoaded } from 'vue-router';
import type {
RouteLocationNormalizedLoaded,
RouteLocationNormalizedLoadedGeneric,
} from 'vue-router';
import { type VNode } from 'vue';
import { RouterView } from 'vue-router';
import { useContentHeight } from '@vben/hooks';
import { preferences, usePreferences } from '@vben/preferences';
@@ -43,6 +49,39 @@ function getTransitionName(_route: RouteLocationNormalizedLoaded) {
// return inTabs && route.meta.loaded ? undefined : transitionName;
return transitionName;
}
/**
* 转换组件,自动添加 name
* @param component
*/
function transformComponent(
component: VNode,
route: RouteLocationNormalizedLoadedGeneric,
) {
const routeName = route.name as string;
// 如果组件没有 name则直接返回
if (!routeName) {
return component;
}
const componentName = (component.type as any).name;
// 已经设置过 name则直接返回
if (componentName) {
return component;
}
// componentName 与 routeName 一致,则直接返回
if (componentName === routeName) {
return component;
}
// 设置 name
component.type ||= {};
(component.type as any).name = routeName;
return component;
}
</script>
<template>
@@ -61,7 +100,7 @@ function getTransitionName(_route: RouteLocationNormalizedLoaded) {
:include="getCachedTabs"
>
<component
:is="Component"
:is="transformComponent(Component, route)"
v-if="renderRouteView"
v-show="!route.meta.iframeSrc"
:key="route.fullPath"

View File

@@ -1,19 +1,20 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, watch } from 'vue';
import { useWatermark } from '@vben/hooks';
import { $t } from '@vben/locales';
import {
preferences,
updatePreferences,
usePreferences,
} from '@vben/preferences';
import { useCoreLockStore } from '@vben/stores';
import { useCoreAccessStore, useCoreLockStore } from '@vben/stores';
import { MenuRecordRaw } from '@vben/types';
import { mapTree } from '@vben/utils';
import { VbenAdminLayout } from '@vben-core/layout-ui';
import { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
import { Breadcrumb, CozeAssistant, Preferences } from '../widgets';
import { Breadcrumb, Preferences } from '../widgets';
import { LayoutContent } from './content';
import { Copyright } from './copyright';
import { LayoutFooter } from './footer';
@@ -40,6 +41,8 @@ const {
layout,
sidebarCollapsed,
} = usePreferences();
const coreAccessStore = useCoreAccessStore();
const { updateWatermark } = useWatermark();
const coreLockStore = useCoreLockStore();
const headerMenuTheme = computed(() => {
@@ -127,6 +130,23 @@ function toggleSidebar() {
function clearPreferencesAndLogout() {
emit('clearPreferencesAndLogout');
}
watch(
() => preferences.app.watermark,
async (val) => {
if (val) {
// await nextTick();
updateWatermark({
content: `${preferences.app.name} 用户名: ${coreAccessStore.userInfo?.username}`,
// parent: contentRef.value,
});
}
},
{
immediate: true,
},
);
</script>
<template>
@@ -174,10 +194,6 @@ function clearPreferencesAndLogout() {
</template>
<template #floating-groups>
<CozeAssistant
v-if="preferences.widget.aiAssistant"
:is-mobile="preferences.app.isMobile"
/>
<VbenBackTop />
</template>

View File

@@ -57,7 +57,7 @@ function useMixedMenu() {
* 侧边菜单激活路径
*/
const sidebarActive = computed(() => {
return route?.meta?.activePath ?? route.path;
return (route?.meta?.activePath as string) ?? route.path;
});
/**

View File

@@ -5,7 +5,12 @@ import { useRoute } from 'vue-router';
import { useContentMaximize, useTabs } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import { useCoreTabbarStore } from '@vben/stores';
import { TabsToolMore, TabsToolScreen, TabsView } from '@vben-core/tabs-ui';
import {
TabsToolMore,
TabsToolRefresh,
TabsToolScreen,
TabsView,
} from '@vben-core/tabs-ui';
import { useTabbar } from './use-tabbar';
@@ -18,7 +23,7 @@ defineProps<{ showIcon?: boolean; theme?: string }>();
const route = useRoute();
const coreTabbarStore = useCoreTabbarStore();
const { toggleMaximize } = useContentMaximize();
const { unpinTab } = useTabs();
const { refreshTab, unpinTab } = useTabs();
const {
createContextMenus,
@@ -29,7 +34,14 @@ const {
} = useTabbar();
const menus = computed(() => {
return createContextMenus(route);
const menus = createContextMenus(route);
return menus.map((item) => {
return {
...item,
label: item.text,
value: item.key,
};
});
});
// 刷新后如果不保持tab状态关闭其他tab
@@ -53,8 +65,13 @@ if (!preferences.tabbar.persist) {
@update:active="handleClick"
/>
<div class="flex-center h-full">
<TabsToolMore :menus="menus" />
<TabsToolRefresh
v-if="preferences.tabbar.showRefresh"
@refresh="refreshTab"
/>
<TabsToolMore v-if="preferences.tabbar.showMore" :menus="menus" />
<TabsToolScreen
v-if="preferences.tabbar.showMaximize"
:screen="preferences.sidebar.hidden"
@change="toggleMaximize"
@update:screen="toggleMaximize"

View File

@@ -42,9 +42,9 @@ const breadcrumbs = computed((): IBreadcrumb[] => {
}
resultBreadcrumb.push({
icon: icon as string,
icon,
path: path || route.path,
title: $t((title || name) as string),
title: title ? $t((title || name) as string) : '',
// items: children.map((child) => {
// return {
// icon: child?.meta?.icon as string,

View File

@@ -1,70 +0,0 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { useScriptTag } from '@vueuse/core';
interface AssistantProps {
botIcon?: string;
botId?: string;
botTitle?: string;
isMobile?: boolean;
}
const props = withDefaults(defineProps<AssistantProps>(), {
botIcon:
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/avatar-v1-transparent-bg.webp',
botId: '7374674983739621392',
botTitle: 'Vben Admin Assistant',
isMobile: false,
});
let client: any;
const wrapperEl = ref();
const { load, unload } = useScriptTag(
'https://sf-cdn.coze.com/obj/unpkg-va/flow-platform/chat-app-sdk/0.1.0-beta.4/libs/oversea/index.js',
() => {
client = new (window as any).CozeWebSDK.WebChatClient({
componentProps: {
icon: props.botIcon,
layout: props.isMobile ? 'mobile' : 'pc',
// lang: 'zh-CN',
title: props.botTitle,
},
config: {
bot_id: props.botId,
},
el: wrapperEl.value,
});
},
{
manual: true,
},
);
onMounted(() => {
load();
});
onUnmounted(() => {
unload();
client?.destroy();
});
</script>
<template>
<div ref="wrapperEl" class="coze-vben-admin-assistant"></div>
</template>
<style>
.coze-vben-admin-assistant {
position: fixed;
right: 30px;
bottom: 60px;
z-index: 1000;
img {
width: 50px !important;
height: 50px !important;
border-radius: 50%;
}
}
</style>

View File

@@ -1,6 +1,5 @@
export { default as Breadcrumb } from './breadcrumb.vue';
export { default as AuthenticationColorToggle } from './color-toggle.vue';
export { default as CozeAssistant } from './coze-assistant.vue';
export * from './global-search';
export { default as LanguageToggle } from './language-toggle.vue';
export { default as AuthenticationLayoutToggle } from './layout-toggle.vue';

View File

@@ -1,21 +1,16 @@
<script setup lang="ts">
import type { SupportedLanguagesType } from '@vben/types';
import type { SupportedLanguagesType } from '@vben/locales';
import { SUPPORT_LANGUAGES } from '@vben/constants';
import { Languages } from '@vben/icons';
import { loadLocaleMessages } from '@vben/locales';
import {
preferences,
SUPPORT_LANGUAGES,
updatePreferences,
} from '@vben/preferences';
import { preferences, updatePreferences } from '@vben/preferences';
import { VbenDropdownRadioMenu, VbenIconButton } from '@vben-core/shadcn-ui';
defineOptions({
name: 'LanguageToggle',
});
const menus = SUPPORT_LANGUAGES;
async function handleUpdate(value: string) {
const locale = value as SupportedLanguagesType;
updatePreferences({
@@ -23,7 +18,6 @@ async function handleUpdate(value: string) {
locale,
},
});
// 更改预览
await loadLocaleMessages(locale);
}
</script>
@@ -31,7 +25,7 @@ async function handleUpdate(value: string) {
<template>
<div>
<VbenDropdownRadioMenu
:menus="menus"
:menus="SUPPORT_LANGUAGES"
:model-value="preferences.app.locale"
@update:model-value="handleUpdate"
>

View File

@@ -20,18 +20,18 @@ defineOptions({
const menus = computed((): VbenDropdownMenuItem[] => [
{
icon: PanelLeft,
key: 'panel-left',
text: $t('authentication.layout.alignLeft'),
label: $t('authentication.layout.alignLeft'),
value: 'panel-left',
},
{
icon: InspectionPanel,
key: 'panel-center',
text: $t('authentication.layout.center'),
label: $t('authentication.layout.center'),
value: 'panel-center',
},
{
icon: PanelRight,
key: 'panel-right',
text: $t('authentication.layout.alignRight'),
label: $t('authentication.layout.alignRight'),
value: 'panel-right',
},
]);

View File

@@ -1,8 +1,6 @@
<script setup lang="ts">
import type { SelectOption } from '@vben/types';
import { SUPPORT_LANGUAGES } from '@vben/constants';
import { $t } from '@vben/locales';
import { SUPPORT_LANGUAGES } from '@vben/preferences';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue';
@@ -13,18 +11,17 @@ defineOptions({
const appLocale = defineModel<string>('appLocale');
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
const localeItems: SelectOption[] = SUPPORT_LANGUAGES.map((item) => ({
label: item.text,
value: item.key,
}));
const appWatermark = defineModel<boolean>('appWatermark');
</script>
<template>
<SelectItem v-model="appLocale" :items="localeItems">
<SelectItem v-model="appLocale" :items="SUPPORT_LANGUAGES">
{{ $t('preferences.language') }}
</SelectItem>
<SwitchItem v-model="appDynamicTitle">
{{ $t('preferences.dynamicTitle') }}
</SwitchItem>
<SwitchItem v-model="appWatermark">
{{ $t('preferences.watermark') }}
</SwitchItem>
</template>

View File

@@ -18,6 +18,9 @@ const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
const tabbarPersist = defineModel<boolean>('tabbarPersist');
const tabbarDragable = defineModel<boolean>('tabbarDragable');
const tabbarStyleType = defineModel<string>('tabbarStyleType');
const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
const tabbarShowRefresh = defineModel<boolean>('tabbarShowRefresh');
const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
const styleItems = computed((): SelectOption[] => [
{
@@ -44,9 +47,6 @@ const styleItems = computed((): SelectOption[] => [
<SwitchItem v-model="tabbarEnable" :disabled="disabled">
{{ $t('preferences.tabbar.enable') }}
</SwitchItem>
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.icon') }}
</SwitchItem>
<SwitchItem v-model="tabbarPersist" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.persist') }}
</SwitchItem>
@@ -56,4 +56,16 @@ const styleItems = computed((): SelectOption[] => [
<SelectItem v-model="tabbarStyleType" :items="styleItems">
{{ $t('preferences.tabbar.styleType.title') }}
</SelectItem>
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.icon') }}
</SwitchItem>
<SwitchItem v-model="tabbarShowRefresh" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.showMore') }}
</SwitchItem>
<SwitchItem v-model="tabbarShowMore" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.showRefresh') }}
</SwitchItem>
<SwitchItem v-model="tabbarShowMaximize" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.showMaximize') }}
</SwitchItem>
</template>

View File

@@ -12,7 +12,6 @@ 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 widgetLockScreen = defineModel<boolean>('widgetLockScreen');
</script>
@@ -33,9 +32,6 @@ const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
<SwitchItem v-model="widgetNotification">
{{ $t('preferences.widget.notification') }}
</SwitchItem>
<SwitchItem v-model="widgetAiAssistant">
{{ $t('preferences.widget.aiAssistant') }}
</SwitchItem>
<SwitchItem v-model="widgetLockScreen">
{{ $t('preferences.widget.lockScreen') }}
</SwitchItem>

View File

@@ -8,6 +8,7 @@ import { $t } from '@vben/locales';
import {
BUILT_IN_THEME_PRESETS,
type BuiltinThemePreset,
preferences,
} from '@vben/preferences';
import { convertToHsl, TinyColor } from '@vben/utils';
@@ -25,6 +26,16 @@ const inputValue = computed(() => {
return new TinyColor(themeColorPrimary.value).toHexString();
});
const builtinThemePresets = computed(() => {
return [
{
color: preferences.theme.colorPrimary,
type: 'default',
},
...BUILT_IN_THEME_PRESETS,
];
});
function typeView(name: BuiltinThemeType) {
switch (name) {
case 'default': {
@@ -97,7 +108,7 @@ function selectColor() {
<template>
<div class="flex w-full flex-wrap justify-between">
<template v-for="theme in BUILT_IN_THEME_PRESETS" :key="theme.type">
<template v-for="theme in builtinThemePresets" :key="theme.type">
<div class="flex cursor-pointer flex-col" @click="handleSelect(theme)">
<div
:class="{

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import type { SupportedLanguagesType } from '@vben/locales';
import type {
BreadcrumbStyleType,
BuiltinThemeType,
@@ -6,7 +7,6 @@ import type {
LayoutHeaderModeType,
LayoutType,
NavigationStyleType,
SupportedLanguagesType,
ThemeModeType,
} from '@vben/types';
import type { SegmentedItem } from '@vben-core/shadcn-ui';
@@ -61,6 +61,7 @@ const appLayout = defineModel<LayoutType>('appLayout');
const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
const appWatermark = defineModel<boolean>('appWatermark');
const transitionProgress = defineModel<boolean>('transitionProgress');
const transitionName = defineModel<string>('transitionName');
@@ -93,6 +94,9 @@ const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
const tabbarEnable = defineModel<boolean>('tabbarEnable');
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
const tabbarShowRefresh = defineModel<boolean>('tabbarShowRefresh');
const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
const tabbarPersist = defineModel<boolean>('tabbarPersist');
const tabbarDragable = defineModel<boolean>('tabbarDragable');
const tabbarStyleType = defineModel<string>('tabbarStyleType');
@@ -136,7 +140,6 @@ 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 widgetLockScreen = defineModel<boolean>('widgetLockScreen');
@@ -252,6 +255,7 @@ async function handleReset() {
<General
v-model:app-dynamic-title="appDynamicTitle"
v-model:app-locale="appLocale"
v-model:app-watermark="appWatermark"
/>
</Block>
@@ -342,19 +346,20 @@ async function handleReset() {
"
/>
</Block>
<Block :title="$t('preferences.tabbar.title')">
<Tabbar
v-model:tabbar-dragable="tabbarDragable"
v-model:tabbar-enable="tabbarEnable"
v-model:tabbar-persist="tabbarPersist"
v-model:tabbar-show-icon="tabbarShowIcon"
v-model:tabbar-show-maximize="tabbarShowMaximize"
v-model:tabbar-show-more="tabbarShowMore"
v-model:tabbar-show-refresh="tabbarShowRefresh"
v-model:tabbar-style-type="tabbarStyleType"
/>
</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"

View File

@@ -9,7 +9,7 @@ import Preferences from './preferences-sheet.vue';
/**
* preferences 转成 vue props
* preferences.widget.aiAssistant=>widgetAiAssistant
* preferences.widget.fullscreen=>widgetFullscreen
*/
const attrs = computed(() => {
const result: Record<string, any> = {};
@@ -23,7 +23,7 @@ const attrs = computed(() => {
/**
* preferences 转成 vue listener
* preferences.widget.aiAssistant=>@update:widgetAiAssistant
* preferences.widget.fullscreen=>@update:widgetFullscreen
*/
const listen = computed(() => {
const result: Record<string, any> = {};

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/library.json",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}