第一次提交

This commit is contained in:
2026-04-03 09:56:14 +08:00
commit 60e2c8debd
3598 changed files with 746659 additions and 0 deletions

View File

@@ -0,0 +1,367 @@
import { defineComponent, computed, unref } from 'vue';
import { BasicDrawer } from '/@/components/Drawer/index';
import { Divider } from 'ant-design-vue';
import { TypePicker, ThemeColorPicker, SettingFooter, SwitchItem, SelectItem, InputNumberItem } from './components';
import { AppDarkModeToggle } from '/@/components/Application';
import { MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
import { useI18n } from '/@/hooks/web/useI18n';
import { layoutHandler } from './handler';
import {
HandlerEnum,
contentModeOptions,
topMenuAlignOptions,
getMenuTriggerOptions,
routerTransitionOptions,
menuTypeList,
mixSidebarTriggerOptions,
tabsThemeOptions,
} from './enum';
import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST, APP_PRESET_COLOR_LIST } from '/@/settings/designSetting';
const { t } = useI18n();
export default defineComponent({
name: 'SettingDrawer',
setup(_, { attrs }) {
const {
getContentMode,
getShowFooter,
getShowBreadCrumb,
getShowBreadCrumbIcon,
getShowLogo,
getFullContent,
getColorWeak,
getGrayMode,
getLockTime,
getShowDarkModeToggle,
getThemeColor,
getAiIconShow,
} = useRootSetting();
const { getOpenPageLoading, getBasicTransition, getEnableTransition, getOpenNProgress } = useTransitionSetting();
const {
getIsHorizontal,
getShowMenu,
getMenuType,
getTrigger,
getCollapsedShowTitle,
getMenuFixed,
getCollapsed,
getCanDrag,
getTopMenuAlign,
getAccordion,
getMenuWidth,
getMenuBgColor,
getIsTopMenu,
getSplit,
getIsMixSidebar,
getCloseMixSidebarOnChange,
getMixSideTrigger,
getMixSideFixed,
} = useMenuSetting();
const { getShowHeader, getFixed: getHeaderFixed, getHeaderBgColor, getShowSearch } = useHeaderSetting();
const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold, getTabsTheme } = useMultipleTabSetting();
const getShowMenuRef = computed(() => {
return unref(getShowMenu) && !unref(getIsHorizontal);
});
const isDev= import.meta.env.DEV
function renderSidebar() {
return (
<>
<TypePicker
menuTypeList={menuTypeList}
handler={(item: typeof menuTypeList[0]) => {
layoutHandler(HandlerEnum.CHANGE_LAYOUT, {
mode: item.mode,
type: item.type,
split: unref(getIsHorizontal) ? false : undefined,
});
}}
def={unref(getMenuType)}
/>
</>
);
}
function renderHeaderTheme() {
return <ThemeColorPicker colorList={HEADER_PRESET_BG_COLOR_LIST} def={unref(getHeaderBgColor)} event={HandlerEnum.HEADER_THEME} />;
}
function renderSiderTheme() {
return <ThemeColorPicker colorList={SIDE_BAR_BG_COLOR_LIST} def={unref(getMenuBgColor)} event={HandlerEnum.MENU_THEME} />;
}
function renderMainTheme() {
return <ThemeColorPicker colorList={APP_PRESET_COLOR_LIST} def={unref(getThemeColor)} event={HandlerEnum.CHANGE_THEME_COLOR} />;
}
/**
* @description:
*/
function renderFeatures() {
let triggerDef = unref(getTrigger);
const triggerOptions = getMenuTriggerOptions(unref(getSplit));
const some = triggerOptions.some((item) => item.value === triggerDef);
if (!some) {
triggerDef = TriggerEnum.FOOTER;
}
return (
<>
<SwitchItem
title={t('layout.setting.splitMenu')}
event={HandlerEnum.MENU_SPLIT}
def={unref(getSplit)}
disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX}
/>
{/*<SwitchItem*/}
{/* title={t('layout.setting.mixSidebarFixed')}*/}
{/* event={HandlerEnum.MENU_FIXED_MIX_SIDEBAR}*/}
{/* def={unref(getMixSideFixed)}*/}
{/* disabled={!unref(getIsMixSidebar)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.closeMixSidebarOnChange')}*/}
{/* event={HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE}*/}
{/* def={unref(getCloseMixSidebarOnChange)}*/}
{/* disabled={!unref(getIsMixSidebar)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.menuCollapse')}*/}
{/* event={HandlerEnum.MENU_COLLAPSED}*/}
{/* def={unref(getCollapsed)}*/}
{/* disabled={!unref(getShowMenuRef)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.menuSearch')}*/}
{/* event={HandlerEnum.HEADER_SEARCH}*/}
{/* def={unref(getShowSearch)}*/}
{/* disabled={!unref(getShowHeader)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.menuAccordion')}*/}
{/* event={HandlerEnum.MENU_ACCORDION}*/}
{/* def={unref(getAccordion)}*/}
{/* disabled={!unref(getShowMenuRef)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.fixedHeader')}*/}
{/* event={HandlerEnum.HEADER_FIXED}*/}
{/* def={unref(getHeaderFixed)}*/}
{/* disabled={!unref(getShowHeader)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.fixedSideBar')}*/}
{/* event={HandlerEnum.MENU_FIXED}*/}
{/* def={unref(getMenuFixed)}*/}
{/* disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}*/}
{/*/>*/}
{/*<SelectItem*/}
{/* title={t('layout.setting.mixSidebarTrigger')}*/}
{/* event={HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR}*/}
{/* def={unref(getMixSideTrigger)}*/}
{/* options={mixSidebarTriggerOptions}*/}
{/* disabled={!unref(getIsMixSidebar)}*/}
{/*/>*/}
<SelectItem title={t('layout.setting.tabsTheme')} event={HandlerEnum.TABS_THEME} def={unref(getTabsTheme)} options={tabsThemeOptions} />
<SelectItem
title={t('layout.setting.topMenuLayout')}
event={HandlerEnum.MENU_TOP_ALIGN}
def={unref(getTopMenuAlign)}
options={topMenuAlignOptions}
disabled={!unref(getShowHeader) || unref(getSplit) || (!unref(getIsTopMenu) && !unref(getSplit)) || unref(getIsMixSidebar)}
/>
<SelectItem
title={t('layout.setting.menuCollapseButton')}
event={HandlerEnum.MENU_TRIGGER}
def={triggerDef}
options={triggerOptions}
disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}
/>
{
isDev && <SelectItem
title={t('layout.setting.contentMode')}
event={HandlerEnum.CONTENT_MODE}
def={unref(getContentMode)}
options={contentModeOptions}
/>
}
{
isDev && <InputNumberItem
title={t('layout.setting.autoScreenLock')}
min={0}
event={HandlerEnum.LOCK_TIME}
defaultValue={unref(getLockTime)}
formatter={(value: string) => {
return parseInt(value) === 0 ? `0(${t('layout.setting.notAutoScreenLock')})` : `${value}${t('layout.setting.minute')}`;
}}
/>
}
{
isDev && <InputNumberItem
title={t('layout.setting.expandedMenuWidth')}
max={600}
min={100}
step={10}
event={HandlerEnum.MENU_WIDTH}
disabled={!unref(getShowMenuRef)}
defaultValue={unref(getMenuWidth)}
formatter={(value: string) => `${parseInt(value)}px`}
/>
}
</>
);
}
function renderContent() {
return (
<>
{
isDev && <SwitchItem
title={t('layout.setting.menuDrag')}
event={HandlerEnum.MENU_HAS_DRAG}
def={unref(getCanDrag)}
disabled={!unref(getShowMenuRef)}
/>
}
{
isDev && <SwitchItem
title={t('layout.setting.collapseMenuDisplayName')}
event={HandlerEnum.MENU_COLLAPSED_SHOW_TITLE}
def={unref(getCollapsedShowTitle)}
disabled={!unref(getShowMenuRef) || !unref(getCollapsed) || unref(getIsMixSidebar)}
/>
}
<SwitchItem title={t('layout.setting.tabs')} event={HandlerEnum.TABS_SHOW} def={unref(getShowMultipleTab)} />
<SwitchItem
title={t('layout.setting.breadcrumb')}
event={HandlerEnum.SHOW_BREADCRUMB}
def={unref(getShowBreadCrumb)}
disabled={!unref(getShowHeader)}
/>
{/*<SwitchItem*/}
{/* title={t('layout.setting.breadcrumbIcon')}*/}
{/* event={HandlerEnum.SHOW_BREADCRUMB_ICON}*/}
{/* def={unref(getShowBreadCrumbIcon)}*/}
{/* disabled={!unref(getShowHeader)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.tabsRedoBtn')}*/}
{/* event={HandlerEnum.TABS_SHOW_REDO}*/}
{/* def={unref(getShowRedo)}*/}
{/* disabled={!unref(getShowMultipleTab)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.tabsQuickBtn')}*/}
{/* event={HandlerEnum.TABS_SHOW_QUICK}*/}
{/* def={unref(getShowQuick)}*/}
{/* disabled={!unref(getShowMultipleTab)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.tabsFoldBtn')}*/}
{/* event={HandlerEnum.TABS_SHOW_FOLD}*/}
{/* def={unref(getShowFold)}*/}
{/* disabled={!unref(getShowMultipleTab)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.sidebar')}*/}
{/* event={HandlerEnum.MENU_SHOW_SIDEBAR}*/}
{/* def={unref(getShowMenu)}*/}
{/* disabled={unref(getIsHorizontal)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title={t('layout.setting.header')}*/}
{/* event={HandlerEnum.HEADER_SHOW}*/}
{/* def={unref(getShowHeader)}*/}
{/*/>*/}
{/*<SwitchItem*/}
{/* title="Logo"*/}
{/* event={HandlerEnum.SHOW_LOGO}*/}
{/* def={unref(getShowLogo)}*/}
{/* disabled={unref(getIsMixSidebar)}*/}
{/*/>*/}
<SwitchItem title={t('layout.setting.footer')} event={HandlerEnum.SHOW_FOOTER} def={unref(getShowFooter)} />
{/*<SwitchItem*/}
{/* title={t('layout.setting.fullContent')}*/}
{/* event={HandlerEnum.FULL_CONTENT}*/}
{/* def={unref(getFullContent)}*/}
{/*/>*/}
<SwitchItem title={t('layout.setting.grayMode')} event={HandlerEnum.GRAY_MODE} def={unref(getGrayMode)} />
<SwitchItem title={t('layout.setting.colorWeak')} event={HandlerEnum.COLOR_WEAK} def={unref(getColorWeak)} />
<SwitchItem title={t('layout.setting.aiIconSHow')} event={HandlerEnum.AI_ICON_SHOW} def={unref(getAiIconShow)} />
</>
);
}
function renderTransition() {
return (
<>
<SwitchItem title={t('layout.setting.progress')} event={HandlerEnum.OPEN_PROGRESS} def={unref(getOpenNProgress)} />
<SwitchItem title={t('layout.setting.switchLoading')} event={HandlerEnum.OPEN_PAGE_LOADING} def={unref(getOpenPageLoading)} />
<SwitchItem title={t('layout.setting.switchAnimation')} event={HandlerEnum.OPEN_ROUTE_TRANSITION} def={unref(getEnableTransition)} />
<SelectItem
title={t('layout.setting.animationType')}
event={HandlerEnum.ROUTER_TRANSITION}
def={unref(getBasicTransition)}
options={routerTransitionOptions}
disabled={!unref(getEnableTransition)}
/>
</>
);
}
return () => (
<BasicDrawer {...attrs} title={t('layout.setting.drawerTitle')} width={330} class="setting-drawer">
{unref(getShowDarkModeToggle) && <Divider>{() => t('layout.setting.darkMode')}</Divider>}
{unref(getShowDarkModeToggle) && <AppDarkModeToggle class="mx-auto" />}
<Divider>{() => t('layout.setting.navMode')}</Divider>
{renderSidebar()}
<Divider>{() => t('layout.setting.sysTheme')}</Divider>
{renderMainTheme()}
<Divider>{() => t('layout.setting.headerTheme')}</Divider>
{renderHeaderTheme()}
<Divider>{() => t('layout.setting.sidebarTheme')}</Divider>
{renderSiderTheme()}
<Divider>{() => t('layout.setting.interfaceFunction')}</Divider>
{renderFeatures()}
{/*<Divider>{() => t('layout.setting.interfaceDisplay')}</Divider>*/}
{renderContent()}
{/*<Divider>{() => t('layout.setting.animation')}</Divider>*/}
{/*{renderTransition()}*/}
<Divider />
<SettingFooter />
</BasicDrawer>
);
},
});

View File

@@ -0,0 +1,56 @@
<template>
<div :class="prefixCls">
<span> {{ title }}</span>
<InputNumber :max="7200" v-bind="$attrs" size="small" :class="`${prefixCls}-input-number`" @change="handleChange" />
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { InputNumber } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { baseHandler } from '../handler';
import { HandlerEnum } from '../enum';
export default defineComponent({
name: 'InputNumberItem',
components: { InputNumber },
props: {
event: {
type: Number as PropType<HandlerEnum>,
},
title: {
type: String,
},
},
setup(props) {
const { prefixCls } = useDesign('setting-input-number-item');
function handleChange(e) {
props.event && baseHandler(props.event, e);
}
return {
prefixCls,
handleChange,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-input-number-item';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
margin: 16px 0;
&-input-number {
width: 126px;
}
}
html[data-theme='dark'] {
.@{prefix-cls} {
color: rgba(255, 255, 255, 0.8);
}
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<div :class="prefixCls">
<span> {{ title }}</span>
<Select v-bind="getBindValue" :class="`${prefixCls}-select`" @change="handleChange" :disabled="disabled" size="small" :options="options" />
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import { Select } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { baseHandler } from '../handler';
import { HandlerEnum } from '../enum';
export default defineComponent({
name: 'SelectItem',
components: { Select },
props: {
event: {
type: Number as PropType<HandlerEnum>,
},
disabled: {
type: Boolean,
},
title: {
type: String,
},
def: {
type: [String, Number] as PropType<string | number>,
},
initValue: {
type: [String, Number] as PropType<string | number>,
},
options: {
type: Array as PropType<LabelValueOptions>,
default: () => [],
},
},
setup(props) {
const { prefixCls } = useDesign('setting-select-item');
const getBindValue = computed(() => {
return props.def ? { value: props.def, defaultValue: props.initValue || props.def } : {};
});
function handleChange(e: ChangeEvent) {
props.event && baseHandler(props.event, e);
}
return {
prefixCls,
handleChange,
getBindValue,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-select-item';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
margin: 16px 0;
&-select {
width: 126px;
}
}
html[data-theme='dark'] {
.@{prefix-cls} {
color: rgba(255, 255, 255, 0.8);
}
}
</style>

View File

@@ -0,0 +1,99 @@
<template>
<div :class="prefixCls">
<a-button v-if="isDev" type="primary" block @click="handleCopy">
<CopyOutlined class="mr-2" />
{{ t('layout.setting.copyBtn') }}
</a-button>
<a-button color="warning" block @click="handleResetSetting" class="my-3">
<RedoOutlined class="mr-2" />
{{ t('common.resetText') }}
</a-button>
<a-button color="error" block @click="handleClearAndRedo">
<RedoOutlined class="mr-2" />
{{ t('layout.setting.clearBtn') }}
</a-button>
</div>
</template>
<script lang="ts">
import { defineComponent, unref } from 'vue';
import { CopyOutlined, RedoOutlined } from '@ant-design/icons-vue';
import { useAppStore } from '/@/store/modules/app';
import { usePermissionStore } from '/@/store/modules/permission';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
import { useUserStore } from '/@/store/modules/user';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { useMessage } from '/@/hooks/web/useMessage';
import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
import { updateColorWeak } from '/@/logics/theme/updateColorWeak';
import { updateGrayMode } from '/@/logics/theme/updateGrayMode';
import defaultSetting from '/@/settings/projectSetting';
export default defineComponent({
name: 'SettingFooter',
components: { CopyOutlined, RedoOutlined },
setup() {
const permissionStore = usePermissionStore();
const { prefixCls } = useDesign('setting-footer');
const { t } = useI18n();
const { createSuccessModal, createMessage } = useMessage();
const tabStore = useMultipleTabStore();
const userStore = useUserStore();
const appStore = useAppStore();
function handleCopy() {
const { isSuccessRef } = useCopyToClipboard(JSON.stringify(unref(appStore.getProjectConfig), null, 2));
unref(isSuccessRef) &&
createSuccessModal({
title: t('layout.setting.operatingTitle'),
content: t('layout.setting.operatingContent'),
});
}
function handleResetSetting() {
try {
appStore.setProjectConfig(defaultSetting);
const { colorWeak, grayMode } = defaultSetting;
// updateTheme(themeColor);
updateColorWeak(colorWeak);
updateGrayMode(grayMode);
createMessage.success(t('layout.setting.resetSuccess'));
} catch (error) {
createMessage.error(error);
}
}
function handleClearAndRedo() {
localStorage.clear();
appStore.resetAllState();
permissionStore.resetState();
tabStore.resetState();
userStore.resetState();
location.reload();
}
const isDev = import.meta.env.DEV;
return {
prefixCls,
t,
handleCopy,
handleResetSetting,
handleClearAndRedo,
isDev,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-footer';
.@{prefix-cls} {
display: flex;
flex-direction: column;
align-items: center;
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<div :class="prefixCls">
<span> {{ title }}</span>
<Switch
v-bind="getBindValue"
@change="handleChange"
:disabled="disabled"
:checkedChildren="t('layout.setting.on')"
:unCheckedChildren="t('layout.setting.off')"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import { Switch } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { baseHandler } from '../handler';
import { HandlerEnum } from '../enum';
export default defineComponent({
name: 'SwitchItem',
components: { Switch },
props: {
event: {
type: Number as PropType<HandlerEnum>,
},
disabled: {
type: Boolean,
},
title: {
type: String,
},
def: {
type: Boolean,
},
},
setup(props) {
const { prefixCls } = useDesign('setting-switch-item');
const { t } = useI18n();
const getBindValue = computed(() => {
return props.def ? { checked: props.def } : {};
});
function handleChange(e: ChangeEvent) {
props.event && baseHandler(props.event, e);
}
return {
prefixCls,
t,
handleChange,
getBindValue,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-switch-item';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
margin: 16px 0;
}
html[data-theme='dark'] {
.@{prefix-cls} {
color: rgba(255, 255, 255, 0.8);
}
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div :class="prefixCls">
<template v-for="color in colorList || []" :key="color">
<span
@click=" !isDisabledColor && handleClick(color)"
:class="[
`${prefixCls}__item`,
{
[`${prefixCls}__item--active`]: def === color,
[`${prefixCls}__item--black`]: color == '#ffffff',
disabledColor: isDisabledColor,
},
]"
:style="{ background: color }"
>
<CheckOutlined />
</span>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, watch, ref } from 'vue';
import { CheckOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { baseHandler } from '../handler';
import { HandlerEnum } from '../enum';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { ThemeEnum } from '/@/enums/appEnum';
export default defineComponent({
name: 'ThemeColorPicker',
components: { CheckOutlined },
props: {
colorList: {
type: Array as PropType<string[]>,
defualt: [],
},
event: {
type: Number as PropType<HandlerEnum>,
},
def: {
type: String,
},
},
setup(props) {
const { prefixCls } = useDesign('setting-theme-picker');
const { getDarkMode } = useRootSetting();
const isDisabledColor = ref(false);
function handleClick(color: string) {
props.event && baseHandler(props.event, color);
}
// 代码逻辑说明: 【QQYUN-8927】暗黑主题下不允许切换顶栏主题和菜单主题
watch(
() => getDarkMode.value,
(newValue) => {
isDisabledColor.value = props.event === 1 ? false : newValue === ThemeEnum.DARK;
},
{ immediate: true }
);
return {
prefixCls,
handleClick,
isDisabledColor
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-setting-theme-picker';
.@{prefix-cls} {
display: flex;
flex-wrap: wrap;
margin: 16px 0;
justify-content: space-around;
// 代码逻辑说明: 【QQYUN-7677】antd4兼容改造勾选垂直居中
line-height: 1.3;
&__item {
width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid #ddd;
border-radius: 2px;
&.disabledColor {
cursor: not-allowed;
opacity: 0.5;
}
svg {
display: none;
}
&--active {
svg {
display: inline-block;
margin: 0 0 3px 3px;
font-size: 12px;
fill: @white !important;
}
}
&--black {
svg {
fill: #000 !important;
}
}
}
}
</style>

View File

@@ -0,0 +1,178 @@
<template>
<div :class="prefixCls">
<template v-for="item in menuTypeList || []" :key="item.title">
<Tooltip :title="item.title" placement="bottom">
<div
@click="handler(item)"
:class="[
`${prefixCls}__item`,
`${prefixCls}__item--${item.type}`,
{
[`${prefixCls}__item--active`]: def === item.type,
},
]"
>
<div class="mix-sidebar"></div>
</div>
</Tooltip>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { Tooltip } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { menuTypeList } from '../enum';
export default defineComponent({
name: 'MenuTypePicker',
components: { Tooltip },
props: {
menuTypeList: {
type: Array as PropType<typeof menuTypeList>,
defualt: () => [],
},
handler: {
type: Function as PropType<Fn>,
default: () => ({}),
},
def: {
type: String,
default: '',
},
},
setup() {
const { prefixCls } = useDesign('setting-menu-type-picker');
return {
prefixCls,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-menu-type-picker';
.@{prefix-cls} {
display: flex;
&__item {
position: relative;
width: 56px;
height: 48px;
margin-right: 16px;
overflow: hidden;
cursor: pointer;
background-color: #f0f2f5;
border-radius: 4px;
box-shadow: 0 1px 2.5px 0 rgba(0, 0, 0, 0.18);
&::before,
&::after {
position: absolute;
content: '';
}
&--sidebar,
&--light {
&::before {
top: 0;
left: 0;
z-index: 1;
width: 33%;
height: 100%;
background-color: #273352;
border-radius: 4px 0 0 4px;
}
&::after {
top: 0;
left: 0;
width: 100%;
height: 25%;
background-color: #fff;
}
}
&--mix {
&::before {
top: 0;
left: 0;
width: 33%;
height: 100%;
background-color: #fff;
border-radius: 4px 0 0 4px;
}
&::after {
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 25%;
background-color: #273352;
}
}
&--top-menu {
&::after {
top: 0;
left: 0;
width: 100%;
height: 25%;
background-color: #273352;
}
}
&--dark {
background-color: #273352;
}
&--mix-sidebar {
&::before {
top: 0;
left: 0;
z-index: 1;
width: 25%;
height: 100%;
background-color: #273352;
border-radius: 4px 0 0 4px;
}
&::after {
top: 0;
left: 0;
width: 100%;
height: 25%;
background-color: #fff;
}
.mix-sidebar {
position: absolute;
left: 25%;
width: 15%;
height: 100%;
background-color: #fff;
}
}
&:hover,
&--active {
padding: 12px;
border: 2px solid @primary-color;
&::before,
&::after {
border-radius: 0;
}
}
}
img {
width: 100%;
height: 100%;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,8 @@
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export const TypePicker = createAsyncComponent(() => import('./TypePicker.vue'));
export const ThemeColorPicker = createAsyncComponent(() => import('./ThemeColorPicker.vue'));
export const SettingFooter = createAsyncComponent(() => import('./SettingFooter.vue'));
export const SwitchItem = createAsyncComponent(() => import('./SwitchItem.vue'));
export const SelectItem = createAsyncComponent(() => import('./SelectItem.vue'));
export const InputNumberItem = createAsyncComponent(() => import('./InputNumberItem.vue'));

View File

@@ -0,0 +1,168 @@
import { TabsThemeEnum, ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum';
import { MenuModeEnum, MenuTypeEnum, TopMenuAlignEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export enum HandlerEnum {
CHANGE_LAYOUT,
CHANGE_THEME_COLOR,
CHANGE_THEME,
// menu
MENU_HAS_DRAG,
MENU_ACCORDION,
MENU_TRIGGER,
MENU_TOP_ALIGN,
MENU_COLLAPSED,
MENU_COLLAPSED_SHOW_TITLE,
MENU_WIDTH,
MENU_SHOW_SIDEBAR,
MENU_THEME,
MENU_SPLIT,
MENU_FIXED,
MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE,
MENU_TRIGGER_MIX_SIDEBAR,
MENU_FIXED_MIX_SIDEBAR,
// header
HEADER_SHOW,
HEADER_THEME,
HEADER_FIXED,
HEADER_SEARCH,
TABS_SHOW_QUICK,
TABS_SHOW_REDO,
TABS_SHOW,
TABS_SHOW_FOLD,
TABS_THEME,
LOCK_TIME,
FULL_CONTENT,
CONTENT_MODE,
SHOW_BREADCRUMB,
SHOW_BREADCRUMB_ICON,
GRAY_MODE,
COLOR_WEAK,
SHOW_LOGO,
SHOW_FOOTER,
ROUTER_TRANSITION,
OPEN_PROGRESS,
OPEN_PAGE_LOADING,
OPEN_ROUTE_TRANSITION,
AI_ICON_SHOW,
}
// 标签页样式
export const tabsThemeOptions = [
{
value: TabsThemeEnum.SMOOTH,
label: t('layout.setting.tabsThemeSmooth'),
},
{
value: TabsThemeEnum.CARD,
label: t('layout.setting.tabsThemeCard'),
},
{
value: TabsThemeEnum.SIMPLE,
label: t('layout.setting.tabsThemeSimple'),
},
];
export const contentModeOptions = [
{
value: ContentEnum.FULL,
label: t('layout.setting.contentModeFull'),
},
{
value: ContentEnum.FIXED,
label: t('layout.setting.contentModeFixed'),
},
];
export const topMenuAlignOptions = [
{
value: TopMenuAlignEnum.CENTER,
label: t('layout.setting.topMenuAlignRight'),
},
{
value: TopMenuAlignEnum.START,
label: t('layout.setting.topMenuAlignLeft'),
},
{
value: TopMenuAlignEnum.END,
label: t('layout.setting.topMenuAlignCenter'),
},
];
export const getMenuTriggerOptions = (hideTop: boolean) => {
return [
{
value: TriggerEnum.NONE,
label: t('layout.setting.menuTriggerNone'),
},
{
value: TriggerEnum.FOOTER,
label: t('layout.setting.menuTriggerBottom'),
},
...(hideTop
? []
: [
{
value: TriggerEnum.HEADER,
label: t('layout.setting.menuTriggerTop'),
},
]),
];
};
export const routerTransitionOptions = [
RouterTransitionEnum.ZOOM_FADE,
RouterTransitionEnum.FADE,
RouterTransitionEnum.ZOOM_OUT,
RouterTransitionEnum.FADE_SIDE,
RouterTransitionEnum.FADE_BOTTOM,
RouterTransitionEnum.FADE_SCALE,
].map((item) => {
return {
label: item,
value: item,
};
});
export const menuTypeList = [
{
title: t('layout.setting.menuTypeSidebar'),
mode: MenuModeEnum.INLINE,
type: MenuTypeEnum.SIDEBAR,
},
{
title: t('layout.setting.menuTypeMix'),
mode: MenuModeEnum.INLINE,
type: MenuTypeEnum.MIX,
},
{
title: t('layout.setting.menuTypeTopMenu'),
mode: MenuModeEnum.HORIZONTAL,
type: MenuTypeEnum.TOP_MENU,
},
{
title: t('layout.setting.menuTypeMixSidebar'),
mode: MenuModeEnum.INLINE,
type: MenuTypeEnum.MIX_SIDEBAR,
},
];
export const mixSidebarTriggerOptions = [
{
value: MixSidebarTriggerEnum.HOVER,
label: t('layout.setting.triggerHover'),
},
{
value: MixSidebarTriggerEnum.CLICK,
label: t('layout.setting.triggerClick'),
},
];

View File

@@ -0,0 +1,248 @@
import { HandlerEnum, tabsThemeOptions} from './enum';
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
import { updateColorWeak } from '/@/logics/theme/updateColorWeak';
import { updateGrayMode } from '/@/logics/theme/updateGrayMode';
import { useAppStore } from '/@/store/modules/app';
import { ProjectConfig } from '/#/config';
import { changeTheme } from '/@/logics/theme';
import { updateDarkTheme } from '/@/logics/theme/dark';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { getConfigByMenuType } from '../../../utils/getConfigByMenuType';
import { isObject } from '/@/utils/is';
import { ThemeEnum } from '/@/enums/appEnum';
import { APP__THEME__COLOR } from '/@/enums/cacheEnum';
/**
* 2024-04-07
* liaozhiyang
* 切换导航栏模式都走这个方法,每个模式都会有固定的顶部和菜单颜色搭配。暗黑模式则不走固定搭配
* */
export function layoutHandler(event: HandlerEnum, value: any) {
const isHTopMenu = isObject(value) && value.type == MenuTypeEnum.TOP_MENU && value.mode == MenuModeEnum.HORIZONTAL;
const isMixMenu = isObject(value) && value.type == MenuTypeEnum.MIX && value.mode == MenuModeEnum.INLINE;
const isMixSidebarMenu = isObject(value) && value.type == MenuTypeEnum.MIX_SIDEBAR && value.mode == MenuModeEnum.INLINE;
const appStore = useAppStore();
const darkMode = appStore.getDarkMode === ThemeEnum.DARK;
// 根据菜单类型动态获取主题色
const {themeColor: dynamicThemeColor, headerBgColor, sideBgColor } = getConfigByMenuType(value.type);
if (isHTopMenu) {
baseHandler(event, value);
baseHandler(HandlerEnum.HEADER_THEME, headerBgColor);
baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor);
if (darkMode) {
updateHeaderBgColor();
updateSidebarBgColor();
}
baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value);
} else if (isMixMenu) {
baseHandler(event, value);
baseHandler(HandlerEnum.HEADER_THEME, headerBgColor);
baseHandler(HandlerEnum.MENU_THEME, sideBgColor);
if (darkMode) {
updateHeaderBgColor();
updateSidebarBgColor();
}
// 顶部混合导航模式使用动态主题色
baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor);
baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value);
} else if (isMixSidebarMenu) {
baseHandler(event, value);
baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor);
baseHandler(HandlerEnum.HEADER_THEME, headerBgColor);
baseHandler(HandlerEnum.MENU_THEME, sideBgColor);
if (darkMode) {
updateHeaderBgColor();
updateSidebarBgColor();
}
baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value);
} else {
baseHandler(event, value);
baseHandler(HandlerEnum.HEADER_THEME, headerBgColor);
baseHandler(HandlerEnum.MENU_THEME, sideBgColor);
if (darkMode) {
updateHeaderBgColor();
updateSidebarBgColor();
}
baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor);
baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value);
}
// 代码逻辑说明: 【QQYUN-13600】默认顶部混合导航模式且启用顶部左侧导航切换到其他模式时导航刷新后菜单样式混乱
if (isMixMenu) {
baseHandler(HandlerEnum.MENU_SPLIT, true);
} else {
baseHandler(HandlerEnum.MENU_SPLIT, false);
}
}
export function baseHandler(event: HandlerEnum, value: any) {
const appStore = useAppStore();
const config = handler(event, value);
appStore.setProjectConfig(config);
if (event === HandlerEnum.CHANGE_THEME) {
updateHeaderBgColor();
updateSidebarBgColor();
}
}
export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConfig> {
const appStore = useAppStore();
const { getThemeColor, getDarkMode } = useRootSetting();
switch (event) {
case HandlerEnum.CHANGE_LAYOUT:
const { mode, type, split } = value;
const splitOpt = split === undefined ? { split } : {};
return {
menuSetting: {
mode,
type,
collapsed: false,
show: true,
hidden: false,
...splitOpt,
},
};
case HandlerEnum.CHANGE_THEME_COLOR:
if (getThemeColor.value === value) {
return {};
}
// 代码逻辑说明: 【QQYUN-8925】系统主题颜色供页面加载使用
localStorage.setItem(APP__THEME__COLOR, value);
changeTheme(value);
return { themeColor: value };
case HandlerEnum.CHANGE_THEME:
if (getDarkMode.value === value) {
return {};
}
updateDarkTheme(value);
return {};
case HandlerEnum.MENU_HAS_DRAG:
return { menuSetting: { canDrag: value } };
case HandlerEnum.MENU_ACCORDION:
return { menuSetting: { accordion: value } };
case HandlerEnum.MENU_TRIGGER:
return { menuSetting: { trigger: value } };
case HandlerEnum.MENU_TOP_ALIGN:
return { menuSetting: { topMenuAlign: value } };
case HandlerEnum.MENU_COLLAPSED:
return { menuSetting: { collapsed: value } };
case HandlerEnum.MENU_WIDTH:
return { menuSetting: { menuWidth: value } };
case HandlerEnum.MENU_SHOW_SIDEBAR:
return { menuSetting: { show: value } };
case HandlerEnum.MENU_COLLAPSED_SHOW_TITLE:
return { menuSetting: { collapsedShowTitle: value } };
case HandlerEnum.MENU_THEME:
updateSidebarBgColor(value);
return { menuSetting: { bgColor: value } };
case HandlerEnum.MENU_SPLIT:
return { menuSetting: { split: value } };
case HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE:
return { menuSetting: { closeMixSidebarOnChange: value } };
case HandlerEnum.MENU_FIXED:
return { menuSetting: { fixed: value } };
case HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR:
return { menuSetting: { mixSideTrigger: value } };
case HandlerEnum.MENU_FIXED_MIX_SIDEBAR:
return { menuSetting: { mixSideFixed: value } };
// ============transition==================
case HandlerEnum.OPEN_PAGE_LOADING:
appStore.setPageLoading(false);
return { transitionSetting: { openPageLoading: value } };
case HandlerEnum.ROUTER_TRANSITION:
return { transitionSetting: { basicTransition: value } };
case HandlerEnum.OPEN_ROUTE_TRANSITION:
return { transitionSetting: { enable: value } };
case HandlerEnum.OPEN_PROGRESS:
return { transitionSetting: { openNProgress: value } };
// ============root==================
case HandlerEnum.LOCK_TIME:
return { lockTime: value };
case HandlerEnum.FULL_CONTENT:
return { fullContent: value };
case HandlerEnum.CONTENT_MODE:
return { contentMode: value };
case HandlerEnum.SHOW_BREADCRUMB:
return { showBreadCrumb: value };
case HandlerEnum.SHOW_BREADCRUMB_ICON:
return { showBreadCrumbIcon: value };
case HandlerEnum.GRAY_MODE:
updateGrayMode(value);
return { grayMode: value };
case HandlerEnum.SHOW_FOOTER:
return { showFooter: value };
case HandlerEnum.COLOR_WEAK:
updateColorWeak(value);
return { colorWeak: value };
// 代码逻辑说明: 【QQYUN-10952】AI助手支持通过设置来配置是否显示
case HandlerEnum.AI_ICON_SHOW:
return { aiIconShow: value };
case HandlerEnum.SHOW_LOGO:
return { showLogo: value };
// ============tabs==================
case HandlerEnum.TABS_SHOW_QUICK:
return { multiTabsSetting: { showQuick: value } };
case HandlerEnum.TABS_SHOW:
return { multiTabsSetting: { show: value } };
case HandlerEnum.TABS_SHOW_REDO:
return { multiTabsSetting: { showRedo: value } };
case HandlerEnum.TABS_SHOW_FOLD:
return { multiTabsSetting: { showFold: value } };
case HandlerEnum.TABS_THEME:
return { multiTabsSetting: { theme: value } };
// ============header==================
case HandlerEnum.HEADER_THEME:
updateHeaderBgColor(value);
return { headerSetting: { bgColor: value } };
case HandlerEnum.HEADER_SEARCH:
return { headerSetting: { showSearch: value } };
case HandlerEnum.HEADER_FIXED:
return { headerSetting: { fixed: value } };
case HandlerEnum.HEADER_SHOW:
return { headerSetting: { show: value } };
default:
return {};
}
}

View File

@@ -0,0 +1,26 @@
<template>
<div @click="openDrawer(true)">
<Icon icon="ion:settings-outline" />
<SettingDrawer @register="register" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import SettingDrawer from './SettingDrawer';
import Icon from '/@/components/Icon';
import { useDrawer } from '/@/components/Drawer';
export default defineComponent({
name: 'SettingButton',
components: { SettingDrawer, Icon },
setup() {
const [register, { openDrawer }] = useDrawer();
return {
register,
openDrawer,
};
},
});
</script>