feat: 添加整体风格自适应操作系统浅色、深色、自动主题功能,让平台充满现代气息 (#869)

* feat: 添加整体风格自适应操作系统浅色、深色、自动主题功能,让平台更加现代化

* chore: update
This commit is contained in:
xiaoming 2024-01-14 23:21:43 +08:00 committed by GitHub
parent 61c6ec230a
commit dd78313622
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 100 additions and 30 deletions

View File

@ -47,7 +47,7 @@
<img alt="PureAdmin" src="https://xiaoxian521.github.io/hyperlink/img/vue-pure-admin/2.jpg">
</p>
黑模式
色风格
<p align="center">
<img alt="PureAdmin" src="https://xiaoxian521.github.io/hyperlink/img/vue-pure-admin/3.jpg">

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.11 323.11 0 0 1-107.769-242.852z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.1 323.1 0 0 1-107.769-242.852z"/></svg>

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 706 B

View File

@ -9,6 +9,7 @@
"Layout": "vertical",
"Theme": "light",
"DarkMode": false,
"OverallStyle": "light",
"Grey": false,
"Weak": false,
"HideTabs": false,

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.11 323.11 0 0 1-107.769-242.852z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.1 323.1 0 0 1-107.769-242.852z"/></svg>

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 706 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="icon" viewBox="0 0 1024 1024"><path d="M554 849.574c0 23.365-18.635 42.307-42 42.307s-42-18.941-42-42.307V662.719c0-23.365 18.635-42.307 42-42.307v-7.051c23.365 0 42 25.993 42 49.358z"/><path d="M893 888.5c0 17.397-14.103 31.5-31.5 31.5h-700c-17.397 0-31.5-14.103-31.5-31.5s14.103-31.5 31.5-31.5h700c17.397 0 31.5 14.103 31.5 31.5m33-714.074C926 135.484 894.686 105 855.744 105H168.256C129.314 105 98 135.484 98 174.426V533h828zM98 630.988C98 669.931 129.314 702 168.256 702h687.488C894.686 702 926 669.931 926 630.988V596H98z"/></svg>

After

Width:  |  Height:  |  Size: 605 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -115,7 +115,13 @@ export default defineComponent({
onClick={event => handleChange({ option, index }, event)}
>
<input type="radio" name="segmented" />
<div class="pure-segmented-item-label">
<div
class="pure-segmented-item-label"
v-tippy={{
content: option?.tip,
zIndex: 41000
}}
>
{option.icon && !isFunction(option.label) ? (
<span
class="pure-segmented-item-icon"

View File

@ -15,4 +15,6 @@ export interface OptionsType {
value?: string | number;
/** 是否禁用 */
disabled?: boolean;
/** `tooltip` 提示 */
tip?: string;
}

View File

@ -6,6 +6,7 @@ import {
reactive,
computed,
nextTick,
onUnmounted,
onBeforeMount
} from "vue";
import panel from "../panel/index.vue";
@ -21,6 +22,7 @@ import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import Check from "@iconify-icons/ep/check";
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();
@ -32,6 +34,7 @@ const horizontalRef = ref();
const {
dataTheme,
overallStyle,
layoutTheme,
themeColors,
toggleClass,
@ -70,7 +73,7 @@ const getThemeColorStyle = computed(() => {
};
});
/** 当网页为暗黑模式时不显示亮白色切换选项 */
/** 当网页整体为暗色风格时不显示亮白色主题配色切换选项 */
const showThemeColors = computed(() => {
return themeColor => {
return themeColor === "light" && isDark.value ? false : true;
@ -162,13 +165,24 @@ const getThemeColor = computed(() => {
const themeOptions = computed<Array<OptionsType>>(() => {
return [
{
label: "色",
label: "色",
icon: dayIcon,
theme: "light",
tip: "清新启航,点亮舒适的工作界面",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
},
{
label: "色",
label: "色",
icon: darkIcon,
theme: "dark",
tip: "月光序曲,沉醉于夜的静谧雅致",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
},
{
label: "自动",
icon: systemIcon,
theme: "system",
tip: "同步时光,界面随晨昏自然呼应",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}
];
@ -177,10 +191,12 @@ const themeOptions = computed<Array<OptionsType>>(() => {
const markOptions: Array<OptionsType> = [
{
label: "灵动",
tip: "灵动标签,添趣生辉",
value: "smart"
},
{
label: "卡片",
tip: "卡片标签,高效浏览",
value: "card"
}
];
@ -195,7 +211,8 @@ function setLayoutModel(layout: string) {
darkMode: $storage.layout?.darkMode,
sidebarStatus: $storage.layout?.sidebarStatus,
epThemeColor: $storage.layout?.epThemeColor,
themeColor: layoutTheme.value.theme
themeColor: $storage.layout?.themeColor,
overallStyle: $storage.layout?.overallStyle
};
useAppStoreHook().setLayout(layout);
}
@ -220,9 +237,34 @@ watch($storage, ({ layout }) => {
}
});
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")?.setAttribute("class", "html-grey");
settings.weakVal &&
@ -231,6 +273,8 @@ onBeforeMount(() => {
settings.hideFooter && hideFooterChange();
});
});
onUnmounted(() => removeMatchMedia);
</script>
<template>
@ -238,12 +282,17 @@ onBeforeMount(() => {
<div class="p-6">
<p class="mb-3 font-medium text-sm dark:text-white">整体风格</p>
<Segmented
:modelValue="dataTheme ? 1 : 0"
class="select-none"
:modelValue="overallStyle === 'system' ? 2 : dataTheme ? 1 : 0"
:options="themeOptions"
@change="
{
dataTheme = !dataTheme;
dataThemeChange();
theme => {
theme.index === 1 && theme.index !== 2
? (dataTheme = true)
: (dataTheme = false);
overallStyle = theme.option.theme;
dataThemeChange(theme.option.theme);
theme.index === 2 && watchSystemThemeChange();
}
"
/>
@ -272,7 +321,7 @@ onBeforeMount(() => {
<li
ref="verticalRef"
v-tippy="{
content: '左侧菜单',
content: '左侧菜单,亲切熟悉',
zIndex: 41000
}"
:class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
@ -285,7 +334,7 @@ onBeforeMount(() => {
v-if="device !== 'mobile'"
ref="horizontalRef"
v-tippy="{
content: '顶部菜单',
content: '顶部菜单,简洁概览',
zIndex: 41000
}"
:class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
@ -298,7 +347,7 @@ onBeforeMount(() => {
v-if="device !== 'mobile'"
ref="mixRef"
v-tippy="{
content: '混合菜单',
content: '混合菜单,灵活多变',
zIndex: 41000
}"
:class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
@ -311,6 +360,7 @@ onBeforeMount(() => {
<p class="mt-5 mb-3 font-medium text-base dark:text-white">页签风格</p>
<Segmented
class="select-none"
:modelValue="markValue === 'smart' ? 0 : 1"
:options="markOptions"
@change="onChange"

View File

@ -38,6 +38,7 @@ export function useDataThemeChange() {
const { $storage } = useGlobal<GlobalPropertiesApi>();
const dataTheme = ref<boolean>($storage?.layout?.darkMode);
const overallStyle = ref<string>($storage?.layout?.overallStyle);
const body = document.documentElement as HTMLElement;
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
@ -64,7 +65,8 @@ export function useDataThemeChange() {
darkMode: dataTheme.value,
sidebarStatus: $storage.layout?.sidebarStatus,
epThemeColor: $storage.layout?.epThemeColor,
themeColor: isClick ? theme : storageThemeColor
themeColor: isClick ? theme : storageThemeColor,
overallStyle: overallStyle.value
};
if (theme === "default" || theme === "light") {
@ -94,8 +96,9 @@ export function useDataThemeChange() {
}
};
/** 亮色、暗色整体风格切换 */
function dataThemeChange() {
/** 浅色、深色整体风格切换 */
function dataThemeChange(overall?: string) {
overallStyle.value = overall;
if (useEpThemeStoreHook().epTheme === "light" && dataTheme.value) {
setLayoutThemeColor("default", false);
} else {
@ -130,6 +133,7 @@ export function useDataThemeChange() {
return {
body,
dataTheme,
overallStyle,
layoutTheme,
themeColors,
onReset,

View File

@ -28,7 +28,8 @@ export function useLayout() {
darkMode: $config?.DarkMode ?? false,
sidebarStatus: $config?.SidebarStatus ?? true,
epThemeColor: $config?.EpThemeColor ?? "#409EFF",
themeColor: $config?.Theme ?? "light"
themeColor: $config?.Theme ?? "light",
overallStyle: $config?.OverallStyle ?? "light"
};
}
/** 灰色模式、色弱模式、隐藏标签页 */

View File

@ -73,7 +73,8 @@ function setTheme(layoutModel: string) {
darkMode: $storage.layout?.darkMode,
sidebarStatus: $storage.layout?.sidebarStatus,
epThemeColor: $storage.layout?.epThemeColor,
themeColor: $storage.layout?.themeColor
themeColor: $storage.layout?.themeColor,
overallStyle: $storage.layout?.overallStyle
};
}
@ -119,7 +120,7 @@ onMounted(() => {
});
onBeforeMount(() => {
useDataThemeChange().dataThemeChange();
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
});
const layoutHeader = defineComponent({

View File

@ -1,6 +1,6 @@
@use "element-plus/theme-chalk/src/dark/css-vars.scss" as *;
/* 暗黑模式适配 */
/* 整体暗色风格适配 */
html.dark {
$border-style: #303030;
$color-white: #fff;
@ -126,7 +126,7 @@ html.dark {
}
}
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,非暗黑模式在 src/style/element-plus.scss 文件进行了适配 */
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,整体浅色风格在 src/style/element-plus.scss 文件进行了适配 */
.pure-message {
background-color: rgb(36 37 37) !important;
background-image: initial !important;

View File

@ -116,7 +116,7 @@
}
}
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,暗黑模式在 src/style/dark.scss 文件进行了适配 */
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,整体暗色风格在 src/style/dark.scss 文件进行了适配 */
.pure-message {
padding: 10px 13px !important;
background: #fff !important;
@ -189,7 +189,7 @@
}
}
/* 仿 el-scrollbar 滚动条样式支持大多数浏览器如Chrome、Edge、Firefox、Safari等。暗黑模式在 src/style/dark.scss 文件进行了适配 */
/* 仿 el-scrollbar 滚动条样式支持大多数浏览器如Chrome、Edge、Firefox、Safari等。整体暗色风格在 src/style/dark.scss 文件进行了适配 */
.pure-scrollbar {
/* Firefox */
scrollbar-width: thin; /* 可选值为 'auto', 'thin', 'none' */

View File

@ -19,7 +19,8 @@ export const injectResponsiveStorage = (app: App, config: PlatformConfigs) => {
darkMode: config.DarkMode ?? false,
sidebarStatus: config.SidebarStatus ?? true,
epThemeColor: config.EpThemeColor ?? "#409EFF",
themeColor: config.Theme ?? "light" // 主题色对应项目配置中的主题色与theme不同的是它不会受到亮色、暗色整体风格切换的影响只会在手动点击主题色时改变
themeColor: config.Theme ?? "light", // 主题色对应项目配置中的主题色与theme不同的是它不会受到浅色、深色整体风格切换的影响只会在手动点击主题色时改变
overallStyle: config.OverallStyle ?? "light" // 整体风格浅色light、深色dark、自动system
},
// 项目配置-界面显示
configure: Storage.getData("configure", nameSpace) ?? {

View File

@ -117,7 +117,7 @@ defineOptions({
<h4 class="mb-4">
类似 Ant Design 风格的消息提示点击弹出提示信息基于 ElMessage
样式改版不会影响 ElMessage
原本样式使用和打包大小成本极低并适配暗黑模式
原本样式使用和打包大小成本极低并适配整体暗色风格
</h4>
<el-space wrap>

View File

@ -56,8 +56,8 @@ const currentPage = computed(() => {
const { t } = useI18n();
const { initStorage } = useLayout();
initStorage();
const { dataTheme, dataThemeChange } = useDataThemeChange();
dataThemeChange();
const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
dataThemeChange(overallStyle.value);
const { title, getDropdownItemStyle, getDropdownItemClass } = useNav();
const { locale, translationCh, translationEn } = useTranslationLang();

3
types/global.d.ts vendored
View File

@ -83,6 +83,7 @@ declare global {
Layout?: string;
Theme?: string;
DarkMode?: boolean;
OverallStyle?: string;
Grey?: boolean;
Weak?: boolean;
HideTabs?: boolean;
@ -127,6 +128,7 @@ declare global {
sidebarStatus?: boolean;
epThemeColor?: string;
themeColor?: string;
overallStyle?: string;
showLogo?: boolean;
showModel?: string;
mapConfigure?: {
@ -154,6 +156,7 @@ declare global {
sidebarStatus?: boolean;
epThemeColor?: string;
themeColor?: string;
overallStyle?: string;
};
configure: {
grey?: boolean;