You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

624 lines
16 KiB

<script setup lang="ts">
import {
ref,
unref,
watch,
reactive,
computed,
nextTick,
onUnmounted,
onBeforeMount
} from "vue";
import panel from "../panel/index.vue";
import { emitter } from "@/utils/mitt";
import { useNav } from "@/layout/hooks/useNav";
import { useAppStoreHook } from "@/store/modules/app";
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import Segmented, { type OptionsType } from "@/components/ReSegmented";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import { useDark, useGlobal, debounce, isNumber } from "@pureadmin/utils";
import Check from "@iconify-icons/ep/check";
import LeftArrow from "@iconify-icons/ri/arrow-left-s-line";
import RightArrow from "@iconify-icons/ri/arrow-right-s-line";
import dayIcon from "@/assets/svg/day.svg?component";
import darkIcon from "@/assets/svg/dark.svg?component";
import systemIcon from "@/assets/svg/system.svg?component";
const { device } = useNav();
const { isDark } = useDark();
const { $storage } = useGlobal<GlobalPropertiesApi>();
const mixRef = ref();
const verticalRef = ref();
const horizontalRef = ref();
const {
dataTheme,
overallStyle,
layoutTheme,
themeColors,
toggleClass,
dataThemeChange,
setLayoutThemeColor
} = useDataThemeChange();
/* body添加layout属性,作用于src/style/sidebar.scss */
if (unref(layoutTheme)) {
const layout = unref(layoutTheme).layout;
const theme = unref(layoutTheme).theme;
toggleTheme({
scopeName: `layout-theme-${theme}`
});
setLayoutModel(layout);
}
/** 默认灵动模式 */
const markValue = ref($storage.configure?.showModel ?? "smart");
const logoVal = ref($storage.configure?.showLogo ?? true);
const settings = reactive({
greyVal: $storage.configure.grey,
weakVal: $storage.configure.weak,
tabsVal: $storage.configure.hideTabs,
showLogo: $storage.configure.showLogo,
showModel: $storage.configure.showModel,
hideFooter: $storage.configure.hideFooter,
multiTagsCache: $storage.configure.multiTagsCache,
stretch: $storage.configure.stretch
});
const getThemeColorStyle = computed(() => {
return color => {
return { background: color };
};
});
/** 当网页整体为暗色风格时不显示亮白色主题配色切换选项 */
const showThemeColors = computed(() => {
return themeColor => {
return themeColor === "light" && isDark.value ? false : true;
};
});
function storageConfigureChange<T>(key: string, val: T): void {
const storageConfigure = $storage.configure;
storageConfigure[key] = val;
$storage.configure = storageConfigure;
}
/** 灰色模式设置 */
const greyChange = (value): void => {
const htmlEl = document.querySelector("html");
toggleClass(settings.greyVal, "html-grey", htmlEl);
storageConfigureChange("grey", value);
};
/** 色弱模式设置 */
const weekChange = (value): void => {
const htmlEl = document.querySelector("html");
toggleClass(settings.weakVal, "html-weakness", htmlEl);
storageConfigureChange("weak", value);
};
/** 隐藏标签页设置 */
const tagsChange = () => {
const showVal = settings.tabsVal;
storageConfigureChange("hideTabs", showVal);
emitter.emit("tagViewsChange", showVal as unknown as string);
};
/** 隐藏页脚设置 */
const hideFooterChange = () => {
const hideFooter = settings.hideFooter;
storageConfigureChange("hideFooter", hideFooter);
};
/** 标签页持久化设置 */
const multiTagsCacheChange = () => {
const multiTagsCache = settings.multiTagsCache;
storageConfigureChange("multiTagsCache", multiTagsCache);
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
};
function onChange({ option }) {
const { value } = option;
markValue.value = value;
storageConfigureChange("showModel", value);
emitter.emit("tagViewsShowModel", value);
}
/** 侧边栏Logo */
function logoChange() {
unref(logoVal)
? storageConfigureChange("showLogo", true)
: storageConfigureChange("showLogo", false);
emitter.emit("logoChange", unref(logoVal));
}
function setFalse(Doms): any {
Doms.forEach(v => {
toggleClass(false, "is-select", unref(v));
});
}
/** 页宽 */
const stretchTypeOptions: Array<OptionsType> = [
{
label: "固定",
tip: "紧凑页面,轻松找到所需信息",
value: "fixed"
},
{
label: "自定义",
tip: "最小1280、最大1600",
value: "custom"
}
];
const setStretch = value => {
settings.stretch = value;
storageConfigureChange("stretch", value);
};
const stretchTypeChange = ({ option }) => {
const { value } = option;
value === "custom" ? setStretch(1440) : setStretch(false);
};
/** 主题色 激活选择项 */
const getThemeColor = computed(() => {
return current => {
if (
current === layoutTheme.value.theme &&
layoutTheme.value.theme !== "light"
) {
return "#fff";
} else if (
current === layoutTheme.value.theme &&
layoutTheme.value.theme === "light"
) {
return "#1d2b45";
} else {
return "transparent";
}
};
});
const pClass = computed(() => {
return ["mb-[12px]", "font-medium", "text-sm", "dark:text-white"];
});
const themeOptions = computed<Array<OptionsType>>(() => {
return [
{
label: "浅色",
icon: dayIcon,
theme: "light",
tip: "清新启航,点亮舒适的工作界面",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
},
{
label: "深色",
icon: darkIcon,
theme: "dark",
tip: "月光序曲,沉醉于夜的静谧雅致",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
},
{
label: "自动",
icon: systemIcon,
theme: "system",
tip: "同步时光,界面随晨昏自然呼应",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}
];
});
const markOptions: Array<OptionsType> = [
{
label: "灵动",
tip: "灵动标签,添趣生辉",
value: "smart"
},
{
label: "卡片",
tip: "卡片标签,高效浏览",
value: "card"
}
];
/** 设置导航模式 */
function setLayoutModel(layout: string) {
layoutTheme.value.layout = layout;
window.document.body.setAttribute("layout", layout);
$storage.layout = {
layout,
theme: layoutTheme.value.theme,
darkMode: $storage.layout?.darkMode,
sidebarStatus: $storage.layout?.sidebarStatus,
epThemeColor: $storage.layout?.epThemeColor,
themeColor: $storage.layout?.themeColor,
overallStyle: $storage.layout?.overallStyle
};
useAppStoreHook().setLayout(layout);
}
watch($storage, ({ layout }) => {
switch (layout["layout"]) {
case "vertical":
toggleClass(true, "is-select", unref(verticalRef));
debounce(setFalse([horizontalRef]), 50);
debounce(setFalse([mixRef]), 50);
break;
case "horizontal":
toggleClass(true, "is-select", unref(horizontalRef));
debounce(setFalse([verticalRef]), 50);
debounce(setFalse([mixRef]), 50);
break;
case "mix":
toggleClass(true, "is-select", unref(mixRef));
debounce(setFalse([verticalRef]), 50);
debounce(setFalse([horizontalRef]), 50);
break;
}
});
const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
/** 根据操作系统主题设置平台整体风格 */
function updateTheme() {
if (overallStyle.value !== "system") return;
if (mediaQueryList.matches) {
dataTheme.value = true;
} else {
dataTheme.value = false;
}
dataThemeChange(overallStyle.value);
}
function removeMatchMedia() {
mediaQueryList.removeEventListener("change", updateTheme);
}
/** 监听操作系统主题改变 */
function watchSystemThemeChange() {
updateTheme();
removeMatchMedia();
mediaQueryList.addEventListener("change", updateTheme);
}
onBeforeMount(() => {
/* 初始化项目配置 */
nextTick(() => {
watchSystemThemeChange();
settings.greyVal &&
document.querySelector("html")?.classList.add("html-grey");
settings.weakVal &&
document.querySelector("html")?.classList.add("html-weakness");
settings.tabsVal && tagsChange();
settings.hideFooter && hideFooterChange();
});
});
onUnmounted(() => removeMatchMedia);
</script>
<template>
<panel>
<div class="p-5">
<p :class="pClass">整体风格</p>
<Segmented
class="select-none"
:modelValue="overallStyle === 'system' ? 2 : dataTheme ? 1 : 0"
:options="themeOptions"
@change="
theme => {
theme.index === 1 && theme.index !== 2
? (dataTheme = true)
: (dataTheme = false);
overallStyle = theme.option.theme;
dataThemeChange(theme.option.theme);
theme.index === 2 && watchSystemThemeChange();
}
"
/>
<p :class="['mt-5', pClass]">主题色</p>
<ul class="theme-color">
<li
v-for="(item, index) in themeColors"
v-show="showThemeColors(item.themeColor)"
:key="index"
:style="getThemeColorStyle(item.color)"
@click="setLayoutThemeColor(item.themeColor)"
>
<el-icon
style="margin: 0.1em 0.1em 0 0"
:size="17"
:color="getThemeColor(item.themeColor)"
>
<IconifyIconOffline :icon="Check" />
</el-icon>
</li>
</ul>
<p :class="['mt-5', pClass]">导航模式</p>
<ul class="pure-theme">
<li
ref="verticalRef"
v-tippy="{
content: '左侧菜单,亲切熟悉',
zIndex: 41000
}"
:class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
@click="setLayoutModel('vertical')"
>
<div />
<div />
</li>
<li
v-if="device !== 'mobile'"
ref="horizontalRef"
v-tippy="{
content: '顶部菜单,简洁概览',
zIndex: 41000
}"
:class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
@click="setLayoutModel('horizontal')"
>
<div />
<div />
</li>
<li
v-if="device !== 'mobile'"
ref="mixRef"
v-tippy="{
content: '混合菜单,灵活多变',
zIndex: 41000
}"
:class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
@click="setLayoutModel('mix')"
>
<div />
<div />
</li>
</ul>
<span v-if="useAppStoreHook().getViewportWidth > 1280">
<p :class="['mt-5', pClass]">页宽</p>
<Segmented
class="mb-2 select-none"
:modelValue="isNumber(settings.stretch) ? 1 : 0"
:options="stretchTypeOptions"
@change="stretchTypeChange"
/>
<el-input-number
v-if="isNumber(settings.stretch)"
v-model="settings.stretch as number"
:min="1280"
:max="1600"
controls-position="right"
@change="value => setStretch(value)"
/>
<button
v-else
v-ripple="{ class: 'text-gray-300' }"
class="bg-transparent flex-c w-full h-20 rounded-md border border-[var(--pure-border-color)]"
@click="setStretch(!settings.stretch)"
>
<div
class="flex-bc transition-all duration-300"
:class="[settings.stretch ? 'w-[24%]' : 'w-[50%]']"
style="color: var(--el-color-primary)"
>
<IconifyIconOffline
:icon="settings.stretch ? RightArrow : LeftArrow"
height="20"
/>
<div
class="flex-grow border-b border-dashed"
style="border-color: var(--el-color-primary)"
/>
<IconifyIconOffline
:icon="settings.stretch ? LeftArrow : RightArrow"
height="20"
/>
</div>
</button>
</span>
<p :class="['mt-4', pClass]">页签风格</p>
<Segmented
class="select-none"
:modelValue="markValue === 'smart' ? 0 : 1"
:options="markOptions"
@change="onChange"
/>
<p class="mt-5 font-medium text-sm dark:text-white">界面显示</p>
<ul class="setting">
<li>
<span class="dark:text-white">灰色模式</span>
<el-switch
v-model="settings.greyVal"
inline-prompt
active-text="开"
inactive-text="关"
@change="greyChange"
/>
</li>
<li>
<span class="dark:text-white">色弱模式</span>
<el-switch
v-model="settings.weakVal"
inline-prompt
active-text="开"
inactive-text="关"
@change="weekChange"
/>
</li>
<li>
<span class="dark:text-white">隐藏标签页</span>
<el-switch
v-model="settings.tabsVal"
inline-prompt
active-text="开"
inactive-text="关"
@change="tagsChange"
/>
</li>
<li>
<span class="dark:text-white">隐藏页脚</span>
<el-switch
v-model="settings.hideFooter"
inline-prompt
active-text="开"
inactive-text="关"
@change="hideFooterChange"
/>
</li>
<li>
<span class="dark:text-white">Logo</span>
<el-switch
v-model="logoVal"
inline-prompt
:active-value="true"
:inactive-value="false"
active-text="开"
inactive-text="关"
@change="logoChange"
/>
</li>
<li>
<span class="dark:text-white">页签持久化</span>
<el-switch
v-model="settings.multiTagsCache"
inline-prompt
active-text=""
inactive-text=""
@change="multiTagsCacheChange"
/>
</li>
</ul>
</div>
</panel>
</template>
<style lang="scss" scoped>
:deep(.el-divider__text) {
font-size: 16px;
font-weight: 700;
}
:deep(.el-switch__core) {
--el-switch-off-color: var(--pure-switch-off-color);
min-width: 36px;
height: 18px;
}
:deep(.el-switch__core .el-switch__action) {
height: 14px;
}
.theme-color {
height: 20px;
li {
float: left;
height: 20px;
margin-right: 8px;
cursor: pointer;
border-radius: 4px;
&:nth-child(1) {
border: 1px solid #ddd;
}
}
}
.pure-theme {
display: flex;
gap: 12px;
li {
position: relative;
width: 46px;
height: 36px;
overflow: hidden;
cursor: pointer;
background: #f0f2f5;
border-radius: 4px;
box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
&:nth-child(1) {
div {
&:nth-child(1) {
width: 30%;
height: 100%;
background: #1b2a47;
}
&:nth-child(2) {
position: absolute;
top: 0;
right: 0;
width: 70%;
height: 30%;
background: #fff;
box-shadow: 0 0 1px #888;
}
}
}
&:nth-child(2) {
div {
&:nth-child(1) {
width: 100%;
height: 30%;
background: #1b2a47;
box-shadow: 0 0 1px #888;
}
}
}
&:nth-child(3) {
div {
&:nth-child(1) {
width: 100%;
height: 30%;
background: #1b2a47;
box-shadow: 0 0 1px #888;
}
&:nth-child(2) {
position: absolute;
bottom: 0;
left: 0;
width: 30%;
height: 70%;
background: #fff;
box-shadow: 0 0 1px #888;
}
}
}
}
}
.is-select {
border: 2px solid var(--el-color-primary);
}
.setting {
li {
display: flex;
align-items: center;
justify-content: space-between;
padding: 3px 0;
font-size: 14px;
}
}
</style>