added dark mode

This commit is contained in:
2026-06-14 21:04:26 +03:30
parent 6d0601f464
commit c0633e5d5a
52 changed files with 1386 additions and 314 deletions
+13 -1
View File
@@ -10,6 +10,14 @@
</template>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between gap-3">
<span class="text-gray-400 text-sm">حالت نمایش</span>
<ARadioGroup v-model:value="colorSchema" size="small">
<ARadioButton value="light">روشن</ARadioButton>
<ARadioButton value="dark">تاریک</ARadioButton>
<ARadioButton value="auto">خودکار</ARadioButton>
</ARadioGroup>
</div>
<div class="flex justify-between">
<span class="text-gray-400 text-sm">نوار کناری (Sidebar)</span>
<ASwitch :checked="true" size="small" />
@@ -59,10 +67,14 @@
import { usePrefix } from '@core/composable/usePrefix'
import { useSettingsStore } from '@core/stores/settings'
import { useLayoutStore } from '@core/stores/layout'
import { useDarkmode } from '@core/stores/darkmode'
import { storeToRefs } from 'pinia'
//const visible = ref<boolean>(false)
const { prefixCls } = usePrefix('base-settings')
const settingsStore = useSettingsStore()
const layoutStore = useLayoutStore()
const darkmode = useDarkmode()
const { colorSchema } = storeToRefs(darkmode)
</script>
<style lang="less">
@@ -71,7 +83,7 @@ const layoutStore = useLayoutStore()
.layout-item{
cursor: pointer;
border:2px solid #eee;
border:2px solid var(--admin-border);
padding:5px;
&.active{
border-color: @primary-color;
+5 -3
View File
@@ -2,7 +2,7 @@
<!-- :class="[, isMobile ? null : 'shadow']"-->
<!-- :theme="sideTheme"-->
<ALayoutSider
theme="light"
:theme="menuTheme"
:class="[prefixCls]"
:width="layoutStore.sideMenu.width"
:collapsible="layoutStore.sideMenu.collapsible"
@@ -96,10 +96,12 @@
import { useLayoutStore } from '@core/stores/layout'
import { usePrefix } from '@core/composable/usePrefix'
import BasicMenu from '@core/layouts/composnts/BasicMenu.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
//import { coreVersion, version } from '../utils'
//import { isDate } from 'lodash'
const layoutStore = useLayoutStore()
const { prefixCls } = usePrefix('basic-side-menu')
const { menuTheme } = useAdminTheme()
</script>
<style lang="less">
@@ -124,13 +126,13 @@ const { prefixCls } = usePrefix('basic-side-menu')
}
.logo{
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--admin-border);
overflow: hidden;
transition: all 0.3s;
}
.side-version{
border-top: 1px solid #eee;
border-top: 1px solid var(--admin-border);
@apply p-2;
max-height: 50px
}
+40 -13
View File
@@ -1,8 +1,8 @@
<template>
<ALayoutHeader
theme="light"
:theme="menuTheme"
:class="[prefixCls]"
:style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"
:style="`height:${layoutStore.header.height}`"
>
<!-- :style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"-->
<div class="flex w-full min-w-0 items-center overflow-hidden">
@@ -26,8 +26,8 @@
<ADropdown placement="bottomLeft" class="ml-5">
<a class="ant-dropdown-link" @click.prevent>
<!-- Hover me-->
<div class="flex items-center gap-1 text-gray-400">
<AAvatar :src="placeholderM" />
<div class="flex items-center gap-1 text-[var(--admin-text-secondary)]">
<AAvatar :src="placeholderM" class="header-avatar" />
<Icon icon="vuesax-linear:arrow-down" size="15" />
</div>
</a>
@@ -40,6 +40,7 @@
</div>
</AMenuItem>
<AMenuDivider />
<ThemeMenuSection />
<AMenuItem @click="myInfoVisible = true">
<div class="flex items-center gap-1">
<Icon icon="vuesax-linear:profile" size="18" />
@@ -164,11 +165,14 @@ import type { FormInstance } from 'ant-design-vue'
import { cloneDeep } from 'lodash'
import { useWindowSize } from '@vueuse/core'
import {getConfigApp} from "@/utils/config";
import ThemeMenuSection from '@core/components/ThemeMenuSection.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const { prefixCls } = usePrefix('basic-header')
const layoutStore = useLayoutStore()
const store = useStaticApiListsStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const { menuTheme } = useAdminTheme()
const { width } = useWindowSize()
@@ -223,7 +227,11 @@ function handleShowSettings() {
}
function clearLocalStorage() {
const colorSchema = localStorage.getItem('color-schema')
localStorage.clear()
if (colorSchema) {
localStorage.setItem('color-schema', colorSchema)
}
location.reload()
}
</script>
@@ -232,16 +240,35 @@ function clearLocalStorage() {
@prefix: ~'@{prefixCls}-basic-header';
.@{prefix} {
padding: unset;
padding: 0 12px !important;
line-height: normal !important;
overflow: hidden;
max-width: 100%;
//height: unset;
//@apply flex flex-col justify-center ;
//height: 64px;
//line-height: unset;
border-bottom: 2px solid #eee;
// height: @header-height;
background-color: #fff;
//@apply backdrop-blur bg-red-200;
display: flex;
align-items: center;
border-bottom: 1px solid var(--admin-border);
background-color: var(--admin-bg-surface);
> .flex {
height: 100%;
align-items: center;
}
.header-avatar {
border: 2px solid var(--admin-border-strong);
}
.hr-tamin-base-button.ant-btn-text {
color: var(--admin-text-secondary);
background: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
&:hover,
&:focus {
color: var(--admin-text);
background: var(--admin-bg-hover) !important;
}
}
}
</style>
+101 -73
View File
@@ -14,6 +14,7 @@
>
<AMenu
:mode="layoutStore.sideMenu.mode"
:theme="menuTheme"
v-model:selectedKeys="selectedKeys"
:disabled-overflow="isTopMenu"
>
@@ -64,6 +65,7 @@ import { useLayoutStore } from '@core/stores/layout'
import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import { useRouterStore } from '@core/stores/router'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const SCROLL_STEP = 180
const EDGE_THRESHOLD = 2
@@ -73,6 +75,7 @@ const layoutStore = useLayoutStore()
const currentRoute = useRoute()
const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
const routerStore = useRouterStore()
const { menuTheme } = useAdminTheme()
const menuScrollRef = ref<HTMLElement | null>(null)
const showLeftArrow = ref(false)
const showRightArrow = ref(false)
@@ -224,6 +227,77 @@ useResizeObserver(menuScrollRef, () => {
position: relative;
min-width: 0;
flex: 1;
height: 100%;
display: flex;
align-items: center;
.top-menu-scroll .ant-menu.ant-menu-horizontal {
display: flex;
flex-wrap: nowrap;
align-items: center;
width: max-content;
height: 40px;
line-height: 40px;
border-bottom: none !important;
padding: 0 4px !important;
background: transparent !important;
}
.top-menu-scroll .ant-menu-horizontal > .ant-menu-item,
.top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu,
.top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title {
top: 0 !important;
margin-top: 0 !important;
height: 36px !important;
line-height: 36px !important;
padding: 0 10px !important;
margin-inline: 2px !important;
border-radius: 6px;
display: inline-flex;
align-items: center;
transition: background-color 0.15s ease, color 0.15s ease !important;
}
.top-menu-scroll .ant-menu-horizontal > .ant-menu-item::after,
.top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu::after {
display: none !important;
}
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected,
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected {
background-color: var(--admin-menu-selected);
color: var(--admin-menu-selected-text);
}
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item-selected,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected > .ant-menu-submenu-title {
background-color: var(--admin-menu-selected) !important;
color: var(--admin-menu-selected-text) !important;
}
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item-selected .ant-menu-item-icon,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected .ant-menu-item-icon,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected .ant-menu-submenu-arrow {
color: var(--admin-menu-selected-text) !important;
}
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item:not(.ant-menu-item-selected):hover,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu:not(.ant-menu-submenu-selected):hover > .ant-menu-submenu-title {
background-color: var(--admin-bg-hover) !important;
color: var(--admin-gold-light) !important;
box-shadow: inset 0 0 0 1px var(--admin-border-strong) !important;
}
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected::after,
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected::after {
display: none !important;
}
.top-menu-scroll .ant-menu-horizontal .ant-menu-item-icon + .ant-menu-title-content,
.top-menu-scroll .ant-menu-horizontal .ant-menu-item-icon + span {
margin-inline-start: 6px !important;
margin-inline-end: 0 !important;
}
}
.top-menu-nav__btn {
@@ -233,31 +307,31 @@ useResizeObserver(menuScrollRef, () => {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 36px;
width: 28px;
height: 32px;
border: none;
border-radius: 6px;
background: #fff;
color: @primary-color;
background: var(--admin-bg-elevated);
color: var(--admin-gold-light);
cursor: pointer;
transform: translateY(-50%);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
box-shadow: 0 0 0 1px var(--admin-border);
transition: background-color 0.2s ease;
&:hover {
background: lighten(@primary-color, 53%);
background: var(--admin-bg-hover);
}
&--left {
left: 0;
padding-right: 4px;
background: linear-gradient(to right, #fff 55%, rgba(255, 255, 255, 0.85));
padding-right: 2px;
background: linear-gradient(to right, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade));
}
&--right {
right: 0;
padding-left: 4px;
background: linear-gradient(to left, #fff 55%, rgba(255, 255, 255, 0.85));
padding-left: 2px;
background: linear-gradient(to left, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade));
}
}
@@ -267,48 +341,14 @@ useResizeObserver(menuScrollRef, () => {
overflow-y: hidden;
flex: 1;
min-width: 0;
height: 100%;
display: flex;
align-items: center;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.ant-menu {
display: flex;
flex-wrap: nowrap;
width: max-content;
border-bottom: none;
padding-top: 4px;
padding-bottom: 4px;
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item,
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu {
padding: 0 5px;
}
.ant-menu-horizontal > .ant-menu-item::after,
.ant-menu-horizontal > .ant-menu-submenu::after {
right: 0 !important;
left: 0 !important;
}
.ant-menu-horizontal > .ant-menu-item-selected::after,
.ant-menu-horizontal > .ant-menu-submenu-selected::after {
position: absolute;
bottom: 0;
border-bottom: 2px solid @primary-color;
transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: '';
}
.ant-menu-item-selected {
background-color: lighten(@primary-color, 53%);
}
.ant-menu-item {
margin-left: 12px;
}
}
.ant-menu-item,
@@ -321,40 +361,34 @@ useResizeObserver(menuScrollRef, () => {
}
}
/** start style for horizontal menu */
.ant-menu-horizontal {
padding: 0 !important;
li {
padding: 5px;
}
}
/** end style for horizontal menu */
&-side-menu {
.ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover,
.ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover {
.ant-menu:not(.ant-menu-dark) .ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover,
.ant-menu:not(.ant-menu-dark) .ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover {
transition: background-color 0.9ms linear;
border-radius: var(--border-radius-base);
color: unset !important;
background-color: #f3f3f3 !important;
background-color: var(--admin-bg-hover) !important;
}
}
.ant-menu {
.ant-menu:not(.ant-menu-horizontal) {
padding: 10px;
user-select: none;
.ant-menu-item,
.ant-menu-submenu-title {
@apply px-2 !important;
}
}
.ant-menu-title-content {
font-size: 15px;
font-size: 14px;
}
.ant-menu-item,
.ant-menu-submenu-title {
@apply px-2 !important;
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item.ant-menu-item-selected {
&::after {
content: unset;
}
}
.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span,
@@ -380,12 +414,6 @@ useResizeObserver(menuScrollRef, () => {
height: 36px;
}
.ant-menu-item.ant-menu-item-selected {
&::after {
content: unset;
}
}
.ant-menu.ant-menu-sub.ant-menu-inline {
background-color: unset !important;
}
+6 -4
View File
@@ -64,11 +64,13 @@ const menu = computed(() => {
@prefix:~ '@{prefixCls}-basic-sub-menu';
.@{prefix}{
&.ant-menu-submenu-selected{
&.ant-menu-submenu-selected {
border-radius: var(--border-radius-base);
background-color: var(--ant-primary-1);
.ant-menu-submenu-arrow{
color: var(--ant-primary)
background-color: var(--admin-menu-selected);
color: var(--admin-menu-selected-text);
.ant-menu-submenu-arrow {
color: var(--admin-menu-selected-text);
}
}