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
+4
View File
@@ -35,9 +35,11 @@ declare module '@vue/runtime-core' {
AMenu: typeof import('ant-design-vue/es')['Menu'] AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination'] APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
APopover: typeof import('ant-design-vue/es')['Popover']
ARadio: typeof import('ant-design-vue/es')['Radio'] ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
@@ -81,6 +83,8 @@ declare module '@vue/runtime-core' {
CustomTag: typeof import('./src/components/CustomTag.vue')['default'] CustomTag: typeof import('./src/components/CustomTag.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
ThemeMenuSection: typeof import('./src/core/components/ThemeMenuSection.vue')['default']
ThemeToggle: typeof import('./src/core/components/ThemeToggle.vue')['default']
UploadPic: typeof import('./src/components/UploadPic.vue')['default'] UploadPic: typeof import('./src/components/UploadPic.vue')['default']
} }
} }
+11
View File
@@ -6,6 +6,17 @@
<meta content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no" name="viewport">
<!-- todo --> <!-- todo -->
<title>admin </title> <title>admin </title>
<script>
(function () {
try {
var raw = localStorage.getItem('color-schema')
var schema = raw ? JSON.parse(raw) : 'auto'
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
var isDark = schema === 'dark' || (schema !== 'light' && prefersDark)
if (isDark) document.documentElement.classList.add('is-dark')
} catch (e) {}
})()
</script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+10 -2
View File
@@ -11,7 +11,10 @@ import {
export default defineConfig({ export default defineConfig({
shortcuts: [ shortcuts: [
// ... ['surface', 'bg-white dark:bg-[#1f1f1f]'],
['surface-muted', 'bg-gray-50 dark:bg-[#262626]'],
['border-base', 'border-gray-200 dark:border-[color:var(--admin-border)]'],
['text-muted', 'text-gray-600 dark:text-gray-400'],
], ],
theme: { theme: {
colors: { colors: {
@@ -20,7 +23,12 @@ export default defineConfig({
} }
}, },
presets: [ presets: [
presetUno(), presetUno({
dark: {
dark: 'html.is-dark',
light: 'html:not(.is-dark)',
},
}),
presetAttributify(), presetAttributify(),
presetIcons(), presetIcons(),
presetTypography(), presetTypography(),
+9 -5
View File
@@ -6,17 +6,21 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted} from 'vue' import { onMounted } from 'vue'
import {useConfigStore} from "@/store/configStore"; import { useConfigStore } from '@/store/configStore'
import { initDarkmode } from '@core/stores/darkmode'
const router = useRouter() const router = useRouter()
const config=useConfigStore() const config = useConfigStore()
initDarkmode()
function test() { function test() {
console.log('roussss', router) console.log('roussss', router)
router.push({ name: 'users' }) router.push({ name: 'users' })
} }
onMounted(async ()=>{ onMounted(async () => {
await config.getConfigData() await config.getConfigData()
}) })
/*const router = useRouter() /*const router = useRouter()
+1 -1
View File
@@ -22,7 +22,7 @@ export const changeProductActivation = async (id: number) =>
export const changeCustomerOrderActivation = async (id: number) => export const changeCustomerOrderActivation = async (id: number) =>
await useAxios.get(`/admin/product/trade/${id}`) await useAxios.get(`/admin/product/trade/${id}`)
export const changeCustomerArchive = async (id: number) => export const changeProductArchive = async (id: number) =>
await useAxios.get(`/admin/product/archive/${id}`) await useAxios.get(`/admin/product/archive/${id}`)
export const confirmUser = async (id: number) => export const confirmUser = async (id: number) =>
+3 -4
View File
@@ -81,13 +81,12 @@
margin: 0 8px; margin: 0 8px;
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
border: 1px solid #d1d5db; border: 1px solid var(--admin-border-strong);
border-radius: 4px; border-radius: 4px;
background-color: white; background-color: var(--admin-bg-surface);
//@apply bg-gray-50;
&:focus-visible { &:focus-visible {
background-color: #fff; background-color: var(--admin-bg-surface);
//display: none; //display: none;
outline: none; outline: none;
border: 1px solid @primary-color !important; border: 1px solid @primary-color !important;
+2 -2
View File
@@ -29,7 +29,7 @@ const props = withDefaults(defineProps<Props>(), {})
.open { .open {
width: 100%; width: 100%;
height: 1px; height: 1px;
background-color: #E5E7EB; background-color: var(--admin-border-strong);
} }
.rotate { .rotate {
@@ -40,7 +40,7 @@ const props = withDefaults(defineProps<Props>(), {})
.ant-collapse > .ant-collapse-item > .ant-collapse-header { .ant-collapse > .ant-collapse-item > .ant-collapse-header {
direction: rtl !important; direction: rtl !important;
justify-content: space-between !important; justify-content: space-between !important;
background-color: #fff !important; background-color: var(--admin-bg-surface) !important;
font-weight: bold; font-weight: bold;
} }
+2 -2
View File
@@ -206,8 +206,8 @@ function handleCancel() {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
z-index: 2; z-index: 2;
background: #fff; background: var(--admin-bg-elevated);
padding: 8px 16px calc(16px + env(safe-area-inset-bottom)); padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
border-top: 1px solid #f0f0f0; border-top: 1px solid var(--admin-border);
} }
</style> </style>
+5 -5
View File
@@ -29,7 +29,7 @@
footer footer
> >
<div class="overflow-y-auto max-h-[70vh] mt-10"> <div class="overflow-y-auto max-h-[70vh] mt-10">
<div :class="[prefixCls]" class="bg-blue-50 rounded flex justify-between p-3"> <div :class="[prefixCls]" class="admin-stat-muted rounded flex justify-between p-3">
<div> <div>
<div class="font-bold">{{ props.name }}</div> <div class="font-bold">{{ props.name }}</div>
<div class="text-primary-type mt-1">{{ props.type }}</div> <div class="text-primary-type mt-1">{{ props.type }}</div>
@@ -40,15 +40,15 @@
</div> </div>
</div> </div>
</div> </div>
<div class="bg-[#CACED8] w-full h-[1px] my-8" /> <div class="my-8 h-px w-full bg-[var(--admin-border)]" />
<div class="text-[#949DB0]">{{ props.itemsTitle }}</div> <div class="text-[var(--admin-text-secondary)]">{{ props.itemsTitle }}</div>
<div class="grid md:grid-cols-4 mt-5 gap-5 p-5"> <div class="grid md:grid-cols-4 mt-5 gap-5 p-5">
<div v-for="val in props.items"> <div v-for="val in props.items">
<div class="text-[#949DB0]">{{ val.key }}</div> <div class="text-[var(--admin-text-secondary)]">{{ val.key }}</div>
<div class="font-bold">{{ val.value }}</div> <div class="font-bold">{{ val.value }}</div>
</div> </div>
<div class="md:col-span-5" v-if="props.description"> <div class="md:col-span-5" v-if="props.description">
<div class="text-[#949DB0]">توضیحات</div> <div class="text-[var(--admin-text-secondary)]">توضیحات</div>
<div class="font-bold">{{ props.description ?? '-' }}</div> <div class="font-bold">{{ props.description ?? '-' }}</div>
</div> </div>
</div> </div>
+7 -2
View File
@@ -4,7 +4,7 @@
v-model:selectedKeys="selectedKeys" v-model:selectedKeys="selectedKeys"
:style="{ width: props.width }" :style="{ width: props.width }"
:mode="props.mode" :mode="props.mode"
:theme="props.theme" :theme="resolvedTheme"
> >
<BasicMenuItem :menu="props.menu" /> <BasicMenuItem :menu="props.menu" />
</AMenu> </AMenu>
@@ -17,6 +17,9 @@ import { defineProps } from 'vue'
//import Icon from '@core/components/BaseIcon/src/Icon.vue' //import Icon from '@core/components/BaseIcon/src/Icon.vue'
import BasicMenuItem from '@/components/BasicMenu/components/BasicMenuItem.vue' import BasicMenuItem from '@/components/BasicMenu/components/BasicMenuItem.vue'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const { menuTheme } = useAdminTheme()
const selectedKeys = ref<string[]>([]) const selectedKeys = ref<string[]>([])
const router = useRouter() const router = useRouter()
@@ -39,11 +42,13 @@ interface Prop {
theme?: MenuTheme theme?: MenuTheme
width?: string width?: string
} }
const props = withDefaults(defineProps<Prop>(), { const props = withDefaults(defineProps<Prop>(), {
mode: 'inline', mode: 'inline',
theme: 'light',
width: '20rem' width: '20rem'
}) })
const resolvedTheme = computed(() => props.theme ?? menuTheme.value)
onMounted(() => { onMounted(() => {
selectedKeys.value = [router.currentRoute.value.name as string] selectedKeys.value = [router.currentRoute.value.name as string]
//emits('onChange', '1') //emits('onChange', '1')
+9 -9
View File
@@ -104,9 +104,9 @@ function navigateTo(item: RouteRecordRaw) {
justify-content: space-around; justify-content: space-around;
gap: 4px; gap: 4px;
padding: 6px 8px calc(6px + env(safe-area-inset-bottom)); padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
background: #fff; background: var(--admin-bg-surface);
border-top: 1px solid #f0f0f0; border-top: 1px solid var(--admin-border);
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06); box-shadow: 0 -2px 12px var(--admin-shadow);
} }
.mobile-bottom-nav__item { .mobile-bottom-nav__item {
@@ -122,14 +122,14 @@ function navigateTo(item: RouteRecordRaw) {
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
color: #595959; color: var(--admin-text-secondary);
&--active { &--active {
color: #1677ff; color: var(--admin-menu-selected-text);
.mobile-bottom-nav__icon-wrap { .mobile-bottom-nav__icon-wrap {
background: #e6f4ff; background: var(--admin-menu-selected);
color: #1677ff; color: var(--admin-menu-selected-text);
} }
&::after { &::after {
@@ -141,7 +141,7 @@ function navigateTo(item: RouteRecordRaw) {
width: 28px; width: 28px;
height: 3px; height: 3px;
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
background: #1677ff; background: var(--admin-menu-selected-text);
} }
} }
@@ -157,7 +157,7 @@ function navigateTo(item: RouteRecordRaw) {
width: 40px; width: 40px;
height: 32px; height: 32px;
border-radius: 10px; border-radius: 10px;
color: #292d32; color: var(--admin-text-muted);
:deep(svg path) { :deep(svg path) {
stroke: currentColor; stroke: currentColor;
+29 -9
View File
@@ -22,6 +22,7 @@
</div> </div>
</AMenuItem> </AMenuItem>
<AMenuDivider /> <AMenuDivider />
<ThemeMenuSection />
<AMenuItem @click="myInfoVisible = true"> <AMenuItem @click="myInfoVisible = true">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<Icon icon="vuesax-linear:profile" size="18" /> <Icon icon="vuesax-linear:profile" size="18" />
@@ -80,6 +81,7 @@ import { message } from 'ant-design-vue/es'
import { onMounted, ref, computed } from 'vue' import { onMounted, ref, computed } from 'vue'
import type { FormInstance } from 'ant-design-vue' import type { FormInstance } from 'ant-design-vue'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import ThemeMenuSection from '@core/components/ThemeMenuSection.vue'
const { prefixCls } = usePrefix('basic-header') const { prefixCls } = usePrefix('basic-header')
const userStore = useUserStore() const userStore = useUserStore()
@@ -129,7 +131,11 @@ async function ok() {
} }
function clearLocalStorage() { function clearLocalStorage() {
const colorSchema = localStorage.getItem('color-schema')
localStorage.clear() localStorage.clear()
if (colorSchema) {
localStorage.setItem('color-schema', colorSchema)
}
location.reload() location.reload()
} }
@@ -142,9 +148,9 @@ function handleRefresh() {
@prefix: ~'@{prefixCls}-basic-header'; @prefix: ~'@{prefixCls}-basic-header';
.@{prefix} { .@{prefix} {
padding: 12px 16px; padding: 0;
background-color: #f5f5f5; background-color: var(--admin-bg-surface);
border-bottom: none; border-bottom: 1px solid var(--admin-border);
} }
.mobile-header-bar { .mobile-header-bar {
@@ -153,17 +159,18 @@ function handleRefresh() {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
padding: 12px 16px; width: 100%;
background: #fff; padding: 10px 12px;
border: 1px solid #e8e8e8; background: var(--admin-bg-surface);
border-radius: 16px; border: none;
border-radius: 0;
} }
.mobile-header-bar__name { .mobile-header-bar__name {
max-width: 34%; max-width: 34%;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: #1f1f1f; color: var(--admin-text);
line-height: 1.4; line-height: 1.4;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -195,7 +202,7 @@ function handleRefresh() {
padding: 0; padding: 0;
border: none; border: none;
background: transparent; background: transparent;
color: #292d32; color: var(--admin-text);
cursor: pointer; cursor: pointer;
line-height: 0; line-height: 0;
@@ -203,4 +210,17 @@ function handleRefresh() {
opacity: 0.7; opacity: 0.7;
} }
} }
:deep(.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> </style>
+4 -4
View File
@@ -92,11 +92,11 @@ function handleSelect(item: RouteRecordRaw) {
} }
.mobile-overflow-menu__trigger--active { .mobile-overflow-menu__trigger--active {
color: #1677ff; color: var(--admin-menu-selected-text);
.mobile-overflow-menu__icon-wrap { .mobile-overflow-menu__icon-wrap {
background: #e6f4ff; background: var(--admin-menu-selected);
color: #1677ff; color: var(--admin-menu-selected-text);
} }
&::after { &::after {
@@ -108,7 +108,7 @@ function handleSelect(item: RouteRecordRaw) {
width: 28px; width: 28px;
height: 3px; height: 3px;
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
background: #1677ff; background: var(--admin-menu-selected-text);
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="relative flex h-full min-h-0 min-w-0 w-full max-w-full flex-col overflow-hidden py-2 pr-3"> <div class="relative flex h-full min-h-0 min-w-0 w-full max-w-full flex-col overflow-hidden py-2 pr-3">
<div class="flex shrink-0 min-w-0 items-center justify-between gap-2"> <div class="flex shrink-0 min-w-0 items-center justify-between gap-2">
<div class="truncate text-black font-bold">{{ props.title }}</div> <div class="truncate font-bold text-[var(--admin-text)]">{{ props.title }}</div>
<slot name="extra" /> <slot name="extra" />
</div> </div>
<div class="mt-2 min-h-0 flex-1 overflow-x-hidden overflow-y-auto p-1"> <div class="mt-2 min-h-0 flex-1 overflow-x-hidden overflow-y-auto p-1">
+1 -1
View File
@@ -1,6 +1,6 @@
<template> <template>
<article <article
class="rounded-2xl border border-gray-200 bg-white shadow-sm" class="rounded-2xl border-base surface shadow-sm"
:class="[paddingClass, clickable ? 'cursor-pointer' : '']" :class="[paddingClass, clickable ? 'cursor-pointer' : '']"
@click="clickable ? $emit('click') : undefined" @click="clickable ? $emit('click') : undefined"
> >
+1
View File
@@ -4,6 +4,7 @@
@import (less) './scroll.less'; @import (less) './scroll.less';
@import (less) "./fonts.less"; @import (less) "./fonts.less";
@import (less) './var.less'; @import (less) './var.less';
@import (less) './theme.less';
@import (less) './ant-override.less'; @import (less) './ant-override.less';
+431
View File
@@ -0,0 +1,431 @@
@import (less) './var.less';
html {
color-scheme: light;
--admin-primary: @gold-color-light;
--admin-gold: @gold-color;
--admin-gold-light: @gold-color-light;
--admin-gold-hover: @gold-color-hover;
--admin-bg-body: #f0f2f5;
--admin-bg-surface: #ffffff;
--admin-bg-elevated: #ffffff;
--admin-bg-muted: #fafafa;
--admin-bg-subtle: #f6f6f4;
--admin-bg-hover: #f5f5f5;
--admin-border: #f0f0f0;
--admin-border-strong: #d9d9d9;
--admin-text: rgba(0, 0, 0, 0.88);
--admin-text-secondary: rgba(0, 0, 0, 0.45);
--admin-text-muted: rgba(0, 0, 0, 0.65);
--admin-shadow: rgba(0, 0, 0, 0.06);
--admin-menu-selected: @gold-color-muted;
--admin-menu-selected-text: @gold-color-dark;
--admin-gradient-surface: #ffffff;
--admin-gradient-surface-fade: rgba(255, 255, 255, 0.85);
}
html.is-dark {
color-scheme: dark;
--admin-primary: @gold-color-light;
--admin-gold: @gold-color-light;
--admin-gold-light: @gold-color-hover;
--admin-gold-hover: #edd054;
--admin-bg-body: #141414;
--admin-bg-surface: #141414;
--admin-bg-elevated: #1f1f1f;
--admin-bg-muted: #262626;
--admin-bg-subtle: #1a1a1a;
--admin-bg-hover: rgba(201, 162, 39, 0.1);
--admin-border: rgba(201, 162, 39, 0.18);
--admin-border-strong: rgba(201, 162, 39, 0.32);
--admin-text: rgba(255, 255, 255, 0.85);
--admin-text-secondary: rgba(255, 255, 255, 0.45);
--admin-text-muted: rgba(255, 255, 255, 0.65);
--admin-shadow: rgba(0, 0, 0, 0.45);
--admin-menu-selected: @gold-color-light;
--admin-menu-selected-text: #141414;
--admin-gradient-surface: #141414;
--admin-gradient-surface-fade: rgba(20, 20, 20, 0.85);
--ant-primary-color: @gold-color-light;
--ant-primary-color-hover: @gold-color-hover;
--ant-primary-color-active: @gold-color;
}
body {
background-color: var(--admin-bg-body);
color: var(--admin-text);
}
.ant-layout-header.@{prefixCls}-basic-header {
padding: 0 12px !important;
line-height: normal !important;
}
.@{prefixCls}-base-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
height: auto !important;
line-height: 1 !important;
}
.ant-btn-primary {
background: var(--admin-gold-light) !important;
border-color: var(--admin-gold-light) !important;
}
.ant-btn-primary:not([disabled]):hover,
.ant-btn-primary:not([disabled]):focus {
background: var(--admin-gold-hover) !important;
border-color: var(--admin-gold-hover) !important;
}
.ant-btn-primary:not([disabled]):active {
background: var(--admin-gold) !important;
border-color: var(--admin-gold) !important;
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected,
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected > .ant-menu-submenu-title {
color: var(--admin-menu-selected-text) !important;
background-color: var(--admin-menu-selected) !important;
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected::after,
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected::after {
display: none !important;
}
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
color: var(--admin-gold-light) !important;
}
.ant-tabs-ink-bar {
background: var(--admin-gold-light) !important;
}
.admin-bg-surface {
background-color: var(--admin-bg-surface);
}
.admin-bg-elevated {
background-color: var(--admin-bg-elevated);
}
.admin-stat-muted {
background-color: var(--admin-bg-muted);
color: var(--admin-text);
}
.stat-inner {
border: 1px solid rgba(0, 0, 0, 0.06);
background: rgba(0, 0, 0, 0.04);
}
html.is-dark {
.stat-inner {
border-color: var(--admin-border);
background: rgba(255, 255, 255, 0.06);
}
.bg-green-100 .stat-inner {
background: rgba(34, 197, 94, 0.12);
border-color: rgba(34, 197, 94, 0.22);
}
.bg-red-100 .stat-inner {
background: rgba(239, 68, 68, 0.12);
border-color: rgba(239, 68, 68, 0.22);
}
.bg-amber-100 .stat-inner {
background: rgba(245, 158, 11, 0.12);
border-color: rgba(245, 158, 11, 0.22);
}
.bg-amber-100 {
background-color: rgba(245, 158, 11, 0.14) !important;
color: var(--admin-text);
}
.bg-white\/70 {
background-color: rgba(255, 255, 255, 0.06) !important;
}
.ant-table-wrapper,
.ant-table,
.ant-table-container,
.ant-table-content,
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
border-color: var(--admin-border) !important;
}
.table-toolbar {
background: transparent !important;
}
.bg-white {
background-color: var(--admin-bg-elevated) !important;
}
.bg-green-100 {
background-color: rgba(34, 197, 94, 0.14) !important;
color: var(--admin-text);
}
.bg-red-100 {
background-color: rgba(239, 68, 68, 0.14) !important;
color: var(--admin-text);
}
.bg-blue-100 {
background-color: rgba(59, 130, 246, 0.14) !important;
color: var(--admin-text);
}
.bg-yellow-100 {
background-color: rgba(234, 179, 8, 0.14) !important;
color: var(--admin-text);
}
.bg-orange-100,
.bg-orange-50 {
background-color: rgba(249, 115, 22, 0.14) !important;
color: var(--admin-text);
}
.bg-blue-50 {
background-color: rgba(59, 130, 246, 0.12) !important;
color: var(--admin-text);
}
.bg-fuchsia-100 {
background-color: rgba(217, 70, 239, 0.14) !important;
}
.bg-gray-100 {
background-color: var(--admin-bg-muted) !important;
color: var(--admin-text);
}
.bg-gray-50 {
background-color: var(--admin-bg-hover) !important;
color: var(--admin-text);
}
.bg-gray-50\/60 {
background-color: rgba(48, 48, 48, 0.65) !important;
color: var(--admin-text);
}
.border-gray-100,
.border-gray-200,
.border-gray-300 {
border-color: var(--admin-border) !important;
}
.text-gray-400,
.text-gray-500 {
color: var(--admin-text-secondary) !important;
}
.text-gray-600,
.text-gray-700 {
color: var(--admin-text-muted) !important;
}
.text-gray-800,
.text-gray-900 {
color: var(--admin-text) !important;
}
.text-green-700 {
color: #4ade80 !important;
}
.text-red-600 {
color: #f87171 !important;
}
.ant-menu-light.ant-menu-horizontal {
background: transparent !important;
border-bottom: 1px solid var(--admin-border) !important;
color: var(--admin-text-muted) !important;
}
.ant-menu-light.ant-menu-horizontal > .ant-menu-item,
.ant-menu-light.ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title {
color: var(--admin-text-muted) !important;
}
.ant-menu-light.ant-menu-horizontal > .ant-menu-item-selected,
.ant-menu-light.ant-menu-horizontal > .ant-menu-submenu-selected > .ant-menu-submenu-title {
color: var(--admin-menu-selected-text) !important;
background: var(--admin-menu-selected) !important;
}
.ant-menu-light.ant-menu-horizontal > .ant-menu-item-selected::after,
.ant-menu-light.ant-menu-horizontal > .ant-menu-submenu-selected::after {
display: none !important;
}
.ant-menu-dark.ant-menu-horizontal {
background: transparent !important;
border-bottom: 1px solid var(--admin-border) !important;
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item,
.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title {
color: var(--admin-text-muted) !important;
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected,
.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected > .ant-menu-submenu-title {
color: var(--admin-menu-selected-text) !important;
background: var(--admin-menu-selected) !important;
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected .ant-menu-item-icon,
.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected .ant-menu-item-icon,
.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected .ant-menu-submenu-arrow {
color: var(--admin-menu-selected-text) !important;
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected::after,
.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected::after {
display: none !important;
}
.ant-modal-content,
.ant-modal-header,
.ant-modal-body {
background-color: #1f1f1f !important;
color: rgba(255, 255, 255, 0.85) !important;
}
.ant-modal-footer {
background-color: #1f1f1f !important;
border-top: 1px solid var(--admin-border-strong) !important;
}
.ant-modal-title,
.ant-modal-close-x {
color: rgba(255, 255, 255, 0.85) !important;
}
.ant-tabs-tab {
color: rgba(255, 255, 255, 0.65) !important;
}
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
color: var(--admin-gold-light) !important;
}
.ant-tabs-ink-bar {
background: var(--admin-gold-light) !important;
}
.ant-card {
background: #1f1f1f !important;
border-color: var(--admin-border-strong) !important;
color: rgba(255, 255, 255, 0.85) !important;
}
.ant-card-body {
padding: 12px !important;
}
.ant-card-head {
background: #1f1f1f !important;
border-color: var(--admin-border-strong) !important;
color: rgba(255, 255, 255, 0.85) !important;
}
.ant-input,
.ant-input-affix-wrapper,
.ant-select-selector,
.ant-picker,
.ant-divider {
border-color: var(--admin-border-strong) !important;
}
.ant-descriptions-item-label,
.ant-descriptions-item-content {
color: rgba(255, 255, 255, 0.85) !important;
}
.top-menu-nav .ant-menu-horizontal > .ant-menu-item,
.top-menu-nav .ant-menu-horizontal > .ant-menu-submenu,
.top-menu-nav .ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title {
padding: 0 10px !important;
height: 36px !important;
line-height: 36px !important;
margin-top: 0 !important;
top: 0 !important;
transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease !important;
}
.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected,
.ant-menu.ant-menu-dark .ant-menu-item-selected {
background-color: var(--admin-menu-selected) !important;
color: var(--admin-menu-selected-text) !important;
}
.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .ant-menu-item-icon,
.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .anticon,
.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .ant-menu-title-content,
.ant-menu.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon,
.ant-menu.ant-menu-dark .ant-menu-item-selected .anticon,
.ant-menu.ant-menu-dark .ant-menu-item-selected .ant-menu-title-content {
color: var(--admin-menu-selected-text) !important;
}
.ant-menu-dark .ant-menu-item:not(.ant-menu-item-selected):hover,
.ant-menu-dark .ant-menu-submenu:not(.ant-menu-submenu-selected) > .ant-menu-submenu-title:hover {
background-color: var(--admin-bg-hover) !important;
color: var(--admin-gold-light) !important;
}
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
color: var(--admin-menu-selected-text) !important;
background: var(--admin-gold-light) !important;
border-color: var(--admin-gold-light) !important;
}
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover,
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active {
color: var(--admin-menu-selected-text) !important;
background: var(--admin-gold-hover) !important;
border-color: var(--admin-gold-hover) !important;
}
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before,
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover::before,
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active::before {
background-color: var(--admin-gold-light) !important;
}
.ant-radio-button-wrapper:hover {
color: var(--admin-gold-hover) !important;
}
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
color: var(--admin-gold-light) !important;
border-color: var(--admin-gold-light) !important;
}
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before {
background-color: var(--admin-gold-light) !important;
}
.ant-input:focus,
.ant-input-focused,
.ant-input-affix-wrapper-focused,
.ant-input-affix-wrapper:focus,
.ant-select-focused:not(.ant-select-disabled) .ant-select-selector,
.ant-picker-focused {
border-color: var(--admin-gold-light) !important;
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18) !important;
}
}
+13 -7
View File
@@ -26,9 +26,15 @@ html{
@header-height:56px; @header-height:56px;
@prefixCls-font-family: 'iran-sans'; @prefixCls-font-family: 'iran-sans';
@ant-prefix: @prefixCls; @ant-prefix: @prefixCls;
@primary-color:#2563eb; // #727071; //#abc2f7; //#f4474d; @gold-color: #c9a227;
@second-color: #3b82f6; // #727071; //#abc2f7; //#f4474d; @gold-color-light: #d4af37;
@third-color: #B2B2B2; // #727071; //#abc2f7; //#f4474d; @gold-color-hover: #e0bc3a;
@gold-color-dark: #92670a;
@gold-color-muted: rgba(201, 162, 39, 0.14);
@primary-color: @gold-color-light;
@second-color: @gold-color-hover;
@third-color: @gold-color;
@bg-color-1: #F9FAFB; @bg-color-1: #F9FAFB;
@bg-color-2: #F6F6F4; @bg-color-2: #F6F6F4;
@@ -37,10 +43,10 @@ html{
@border-radius-base: 4px; @border-radius-base: 4px;
html{ html{
--ant-primary-color:@primary-color; --ant-primary-color: @gold-color-light;
--ant-primary-color-hover:@second-color; --ant-primary-color-hover: @gold-color-hover;
--ant-primary-color-active:@third-color; --ant-primary-color-active: @gold-color;
--ant-primary-1:#f2f3ff;//#f6f6f4; --ant-primary-1: rgba(201, 162, 39, 0.12);
--border-radius-base:@border-radius-base; --border-radius-base:@border-radius-base;
} }
+78 -18
View File
@@ -1,20 +1,24 @@
<template> <template>
<AButton :type="props.type" :size="props.size" :class="[prefixCls, _class]"> <AButton
<!-- py-1 px-2--> :type="props.type"
<div :size="props.size"
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2" :class="[prefixCls, _class, { [`${prefixCls}--icon-suffix`]: suffixIcon }]"
:class="[{ 'flex-row-reverse': suffixIcon }]" >
> <span v-if="icon && !suffixIcon" class="base-button__icon">
<slot v-if="$slots.default"></slot> <BaseIcon :size="resolvedIconSize" :icon="icon" :class="iconClass" />
</span>
<BaseIcon :size="iconSize" :icon="icon" v-if="icon" :class="iconClass" /> <span v-if="$slots.default" class="base-button__label">
</div> <slot />
</span>
<span v-if="icon && suffixIcon" class="base-button__icon">
<BaseIcon :size="resolvedIconSize" :icon="icon" :class="iconClass" />
</span>
</AButton> </AButton>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { usePrefix } from '@core/composable/usePrefix' import { usePrefix } from '@core/composable/usePrefix'
import { computed } from 'vue' import { computed, useSlots } from 'vue'
import { baseButtonIconSize } from '@root/config' import { baseButtonIconSize } from '@root/config'
interface Props { interface Props {
@@ -24,7 +28,6 @@ interface Props {
suffixIcon?: boolean suffixIcon?: boolean
type?: string type?: string
size?: string size?: string
// prefixIcon?: string
} }
const slot = useSlots() const slot = useSlots()
@@ -40,17 +43,74 @@ const { prefixCls } = usePrefix('base-button')
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
iconSize: baseButtonIconSize, iconSize: baseButtonIconSize,
// iconLeft: false,
iconClass: '' iconClass: ''
}) })
const resolvedIconSize = computed(() => {
if (props.iconSize !== baseButtonIconSize) {
return props.iconSize
}
switch (props.size) {
case 'small':
return '16'
case 'large':
return '20'
default:
return '18'
}
})
</script> </script>
<style lang="less"> <style lang="less">
@prefix:~'@{prefixCls}-base-button'; @prefix:~'@{prefixCls}-base-button';
.@{prefix}{
//width: fit-content; .@{prefix} {
padding: 0; display: inline-flex !important;
height: auto; align-items: center !important;
/*padding: 8px;*/ justify-content: center !important;
gap: 6px;
height: auto !important;
min-height: 32px;
padding: 4px 15px !important;
line-height: 1 !important;
white-space: nowrap;
overflow: visible;
vertical-align: middle;
&.ant-btn-sm {
min-height: 32px;
padding: 5px 12px !important;
font-size: 14px;
gap: 6px;
} }
&.ant-btn-lg {
min-height: 40px;
padding: 6px 16px !important;
}
&--icon-suffix {
flex-direction: row-reverse;
}
.base-button__icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
line-height: 0;
:deep(.hr-tamin-icons),
:deep(svg) {
display: block;
}
}
.base-button__label {
display: inline-flex;
align-items: center;
line-height: 1 !important;
}
}
</style> </style>
+27 -10
View File
@@ -12,7 +12,7 @@
//todo convert to async load //todo convert to async load
import icons from '@core/assets/icons/icon.json' import icons from '@core/assets/icons/icon.json'
import { usePrefix } from '@core/composable/usePrefix' import { usePrefix } from '@core/composable/usePrefix'
import { computed, onMounted, ref } from 'vue' import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { baseIconSize } from '@root/config' import { baseIconSize } from '@root/config'
interface Props { interface Props {
icon: string icon: string
@@ -27,20 +27,37 @@ const props = withDefaults(defineProps<Props>(), {
const { prefixCls } = usePrefix('icons') const { prefixCls } = usePrefix('icons')
const tempIcon = ref<string>() const tempIcon = ref<string>()
const refIcon = ref<HTMLDivElement>() const refIcon = ref<HTMLDivElement>()
//@ts-ignore
tempIcon.value = icons[`${props.icon}`] as string function resolveIcon(iconName: string) {
return icons[`${iconName}` as keyof typeof icons] as string
}
function applySize() {
if (!props.size || !refIcon.value) return
const svg = refIcon.value.children[0] as SVGElement | undefined
if (!svg) return
svg.setAttribute('width', String(props.size))
svg.setAttribute('height', String(props.size))
}
tempIcon.value = resolveIcon(props.icon)
watch(
() => props.icon,
(iconName) => {
tempIcon.value = resolveIcon(iconName)
nextTick(() => applySize())
}
)
const color = computed(() => { const color = computed(() => {
return props.color return props.color
}) })
onMounted(() => { onMounted(() => {
if (props.size) { applySize()
const svg: SVGElement = refIcon.value?.children[0] as SVGElement
// console.log(svg)
svg.setAttribute('width', props.size as string)
svg.setAttribute('height', props.size as string)
// console.log(props.icon)
}
}) })
</script> </script>
@@ -2,7 +2,7 @@
<div :class="[prefixCls, 'min-w-0 max-w-full']"> <div :class="[prefixCls, 'min-w-0 max-w-full']">
<ASpin tip="در حال دریافت اطلاعات..." :indicator="indicator" :spinning="loading"> <ASpin tip="در حال دریافت اطلاعات..." :indicator="indicator" :spinning="loading">
<div class="scrollable-container" id="components-affix-demo-target" ref="containerRef"> <div class="scrollable-container" id="components-affix-demo-target" ref="containerRef">
<div class="table-toolbar sticky top-0 z-10 mb-2 shrink-0 bg-white"> <div class="table-toolbar sticky top-0 z-10 mb-2 shrink-0">
<div class="flex flex-col gap-2 xl:flex-row xl:items-center xl:justify-between"> <div class="flex flex-col gap-2 xl:flex-row xl:items-center xl:justify-between">
<div <div
v-if="showOptionHeader" v-if="showOptionHeader"
@@ -707,12 +707,16 @@ defineExpose({
width: 100%; width: 100%;
} }
.table-toolbar {
background: transparent;
}
.table-toolbar-extra:empty { .table-toolbar-extra:empty {
display: none; display: none;
} }
.ant-table-wrapper { .ant-table-wrapper {
border: 1px solid #eee; border: 1px solid var(--admin-border);
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
} }
.ant-spin-container, .ant-spin-container,
@@ -673,7 +673,7 @@ defineExpose({
overflow-y: auto; overflow-y: auto;
} }
.ant-table-wrapper { .ant-table-wrapper {
border: 1px solid #eee; border: 1px solid var(--admin-border);
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
} }
.ant-spin-container, .ant-spin-container,
+83
View File
@@ -0,0 +1,83 @@
<template>
<AMenuItemGroup title="پوسته">
<AMenuItem
v-for="opt in themeOptions"
:key="opt.value"
@click="darkmode.setSchema(opt.value)"
>
<div class="theme-menu-item" :class="{ 'theme-menu-item--active': colorSchema === opt.value }">
<div class="theme-menu-item__main">
<Icon :icon="opt.icon" size="18" />
<span>{{ opt.label }}</span>
</div>
<Icon
v-if="colorSchema === opt.value"
icon="vuesax-linear:tick-circle"
size="16"
class="theme-menu-item__check"
/>
</div>
</AMenuItem>
<AMenuItem v-if="colorSchema === 'auto'" disabled class="theme-menu-hint-item">
<span class="theme-menu-hint">وضعیت فعلی: {{ isDark ? 'تاریک' : 'روشن' }}</span>
</AMenuItem>
</AMenuItemGroup>
<AMenuDivider />
</template>
<script lang="ts" setup>
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
import type { DarkModeSchema } from '@core/stores/darkmode'
import { storeToRefs } from 'pinia'
const { isDark, darkmode } = useAdminTheme()
const { colorSchema } = storeToRefs(darkmode)
const themeOptions: Array<{ value: DarkModeSchema; label: string; icon: string }> = [
{ value: 'light', label: 'روشن', icon: 'vuesax-linear:sun-1' },
{ value: 'dark', label: 'تاریک', icon: 'vuesax-linear:moon' },
{ value: 'auto', label: 'سیستم', icon: 'vuesax-linear:monitor' }
]
</script>
<style lang="less" scoped>
.theme-menu-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
min-width: 10rem;
}
.theme-menu-item__main {
display: flex;
align-items: center;
gap: 8px;
}
.theme-menu-item--active {
font-weight: 600;
}
.theme-menu-item__check {
color: var(--admin-gold-light, #d4af37);
}
.theme-menu-hint-item {
height: auto !important;
line-height: 1.3 !important;
cursor: default !important;
opacity: 1 !important;
}
.theme-menu-hint {
font-size: 11px;
color: rgba(0, 0, 0, 0.45);
}
html.is-dark .theme-menu-hint {
color: rgba(255, 255, 255, 0.45);
}
</style>
+146
View File
@@ -0,0 +1,146 @@
<template>
<div class="theme-toggle">
<span v-if="showLabel" class="theme-toggle__label">پوسته</span>
<APopover
v-model:open="open"
trigger="click"
placement="bottomLeft"
overlay-class-name="theme-toggle-popover"
>
<template #content>
<div class="theme-toggle__menu">
<button
v-for="opt in themeOptions"
:key="opt.value"
type="button"
class="theme-toggle__option"
:class="{ 'theme-toggle__option--active': colorSchema === opt.value }"
@click="selectSchema(opt.value)"
>
<BaseIcon :icon="opt.icon" size="18" />
<span>{{ opt.label }}</span>
</button>
<div v-if="colorSchema === 'auto'" class="theme-toggle__hint">
وضعیت فعلی: {{ isDark ? 'تاریک' : 'روشن' }}
</div>
</div>
</template>
<BaseButton
type="text"
:icon="triggerIcon"
icon-size="20"
aria-label="انتخاب پوسته"
/>
</APopover>
</div>
</template>
<script lang="ts" setup>
import BaseIcon from '@core/components/BaseIcon/src/Icon.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
import type { DarkModeSchema } from '@core/stores/darkmode'
import { computed, ref } from 'vue'
import { storeToRefs } from 'pinia'
withDefaults(
defineProps<{
showLabel?: boolean
}>(),
{
showLabel: true
}
)
const open = ref(false)
const { isDark, darkmode } = useAdminTheme()
const { colorSchema } = storeToRefs(darkmode)
const themeOptions: Array<{ value: DarkModeSchema; label: string; icon: string }> = [
{ value: 'light', label: 'روشن', icon: 'vuesax-linear:sun-1' },
{ value: 'dark', label: 'تاریک', icon: 'vuesax-linear:moon' },
{ value: 'auto', label: 'سیستم', icon: 'vuesax-linear:monitor' }
]
const triggerIcon = computed(() => {
if (colorSchema.value === 'dark') return 'vuesax-linear:moon'
if (colorSchema.value === 'light') return 'vuesax-linear:sun-1'
return 'vuesax-linear:monitor'
})
function selectSchema(schema: DarkModeSchema) {
darkmode.setSchema(schema)
open.value = false
}
</script>
<style lang="less" scoped>
.theme-toggle {
display: inline-flex;
align-items: center;
gap: 4px;
}
.theme-toggle__label {
font-size: 13px;
color: var(--admin-text-secondary);
white-space: nowrap;
}
.theme-toggle__menu {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 9rem;
}
.theme-toggle__option {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--admin-text);
font-size: 13px;
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
&:hover {
background: var(--admin-bg-hover);
color: var(--admin-gold-light);
}
&--active {
background: var(--admin-menu-selected);
color: var(--admin-menu-selected-text);
font-weight: 600;
}
}
.theme-toggle__hint {
margin-top: 4px;
padding-top: 8px;
border-top: 1px solid var(--admin-border);
font-size: 11px;
color: var(--admin-text-secondary);
}
</style>
<style lang="less">
.theme-toggle-popover {
.ant-popover-inner {
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border-strong);
}
.ant-popover-inner-content {
padding: 8px;
}
.ant-popover-arrow-content::before {
background: var(--admin-bg-elevated);
}
}
</style>
+17
View File
@@ -0,0 +1,17 @@
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useDarkmode } from '@core/stores/darkmode'
export function useAdminTheme() {
const darkmode = useDarkmode()
const { isDark, colorSchema } = storeToRefs(darkmode)
const menuTheme = computed(() => (isDark.value ? 'dark' : 'light') as 'dark' | 'light')
return {
isDark,
colorSchema,
menuTheme,
darkmode,
}
}
+13 -1
View File
@@ -10,6 +10,14 @@
</template> </template>
<div class="flex flex-col gap-4"> <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"> <div class="flex justify-between">
<span class="text-gray-400 text-sm">نوار کناری (Sidebar)</span> <span class="text-gray-400 text-sm">نوار کناری (Sidebar)</span>
<ASwitch :checked="true" size="small" /> <ASwitch :checked="true" size="small" />
@@ -59,10 +67,14 @@
import { usePrefix } from '@core/composable/usePrefix' import { usePrefix } from '@core/composable/usePrefix'
import { useSettingsStore } from '@core/stores/settings' import { useSettingsStore } from '@core/stores/settings'
import { useLayoutStore } from '@core/stores/layout' import { useLayoutStore } from '@core/stores/layout'
import { useDarkmode } from '@core/stores/darkmode'
import { storeToRefs } from 'pinia'
//const visible = ref<boolean>(false) //const visible = ref<boolean>(false)
const { prefixCls } = usePrefix('base-settings') const { prefixCls } = usePrefix('base-settings')
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const layoutStore = useLayoutStore() const layoutStore = useLayoutStore()
const darkmode = useDarkmode()
const { colorSchema } = storeToRefs(darkmode)
</script> </script>
<style lang="less"> <style lang="less">
@@ -71,7 +83,7 @@ const layoutStore = useLayoutStore()
.layout-item{ .layout-item{
cursor: pointer; cursor: pointer;
border:2px solid #eee; border:2px solid var(--admin-border);
padding:5px; padding:5px;
&.active{ &.active{
border-color: @primary-color; border-color: @primary-color;
+5 -3
View File
@@ -2,7 +2,7 @@
<!-- :class="[, isMobile ? null : 'shadow']"--> <!-- :class="[, isMobile ? null : 'shadow']"-->
<!-- :theme="sideTheme"--> <!-- :theme="sideTheme"-->
<ALayoutSider <ALayoutSider
theme="light" :theme="menuTheme"
:class="[prefixCls]" :class="[prefixCls]"
:width="layoutStore.sideMenu.width" :width="layoutStore.sideMenu.width"
:collapsible="layoutStore.sideMenu.collapsible" :collapsible="layoutStore.sideMenu.collapsible"
@@ -96,10 +96,12 @@
import { useLayoutStore } from '@core/stores/layout' import { useLayoutStore } from '@core/stores/layout'
import { usePrefix } from '@core/composable/usePrefix' import { usePrefix } from '@core/composable/usePrefix'
import BasicMenu from '@core/layouts/composnts/BasicMenu.vue' import BasicMenu from '@core/layouts/composnts/BasicMenu.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
//import { coreVersion, version } from '../utils' //import { coreVersion, version } from '../utils'
//import { isDate } from 'lodash' //import { isDate } from 'lodash'
const layoutStore = useLayoutStore() const layoutStore = useLayoutStore()
const { prefixCls } = usePrefix('basic-side-menu') const { prefixCls } = usePrefix('basic-side-menu')
const { menuTheme } = useAdminTheme()
</script> </script>
<style lang="less"> <style lang="less">
@@ -124,13 +126,13 @@ const { prefixCls } = usePrefix('basic-side-menu')
} }
.logo{ .logo{
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--admin-border);
overflow: hidden; overflow: hidden;
transition: all 0.3s; transition: all 0.3s;
} }
.side-version{ .side-version{
border-top: 1px solid #eee; border-top: 1px solid var(--admin-border);
@apply p-2; @apply p-2;
max-height: 50px max-height: 50px
} }
+40 -13
View File
@@ -1,8 +1,8 @@
<template> <template>
<ALayoutHeader <ALayoutHeader
theme="light" :theme="menuTheme"
:class="[prefixCls]" :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}`"--> <!-- :style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"-->
<div class="flex w-full min-w-0 items-center overflow-hidden"> <div class="flex w-full min-w-0 items-center overflow-hidden">
@@ -26,8 +26,8 @@
<ADropdown placement="bottomLeft" class="ml-5"> <ADropdown placement="bottomLeft" class="ml-5">
<a class="ant-dropdown-link" @click.prevent> <a class="ant-dropdown-link" @click.prevent>
<!-- Hover me--> <!-- Hover me-->
<div class="flex items-center gap-1 text-gray-400"> <div class="flex items-center gap-1 text-[var(--admin-text-secondary)]">
<AAvatar :src="placeholderM" /> <AAvatar :src="placeholderM" class="header-avatar" />
<Icon icon="vuesax-linear:arrow-down" size="15" /> <Icon icon="vuesax-linear:arrow-down" size="15" />
</div> </div>
</a> </a>
@@ -40,6 +40,7 @@
</div> </div>
</AMenuItem> </AMenuItem>
<AMenuDivider /> <AMenuDivider />
<ThemeMenuSection />
<AMenuItem @click="myInfoVisible = true"> <AMenuItem @click="myInfoVisible = true">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<Icon icon="vuesax-linear:profile" size="18" /> <Icon icon="vuesax-linear:profile" size="18" />
@@ -164,11 +165,14 @@ import type { FormInstance } from 'ant-design-vue'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { useWindowSize } from '@vueuse/core' import { useWindowSize } from '@vueuse/core'
import {getConfigApp} from "@/utils/config"; import {getConfigApp} from "@/utils/config";
import ThemeMenuSection from '@core/components/ThemeMenuSection.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const { prefixCls } = usePrefix('basic-header') const { prefixCls } = usePrefix('basic-header')
const layoutStore = useLayoutStore() const layoutStore = useLayoutStore()
const store = useStaticApiListsStore() const store = useStaticApiListsStore()
const userStore = useUserStore() const userStore = useUserStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const { menuTheme } = useAdminTheme()
const { width } = useWindowSize() const { width } = useWindowSize()
@@ -223,7 +227,11 @@ function handleShowSettings() {
} }
function clearLocalStorage() { function clearLocalStorage() {
const colorSchema = localStorage.getItem('color-schema')
localStorage.clear() localStorage.clear()
if (colorSchema) {
localStorage.setItem('color-schema', colorSchema)
}
location.reload() location.reload()
} }
</script> </script>
@@ -232,16 +240,35 @@ function clearLocalStorage() {
@prefix: ~'@{prefixCls}-basic-header'; @prefix: ~'@{prefixCls}-basic-header';
.@{prefix} { .@{prefix} {
padding: unset; padding: 0 12px !important;
line-height: normal !important;
overflow: hidden; overflow: hidden;
max-width: 100%; max-width: 100%;
//height: unset; display: flex;
//@apply flex flex-col justify-center ; align-items: center;
//height: 64px; border-bottom: 1px solid var(--admin-border);
//line-height: unset; background-color: var(--admin-bg-surface);
border-bottom: 2px solid #eee;
// height: @header-height; > .flex {
background-color: #fff; height: 100%;
//@apply backdrop-blur bg-red-200; 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> </style>
+101 -73
View File
@@ -14,6 +14,7 @@
> >
<AMenu <AMenu
:mode="layoutStore.sideMenu.mode" :mode="layoutStore.sideMenu.mode"
:theme="menuTheme"
v-model:selectedKeys="selectedKeys" v-model:selectedKeys="selectedKeys"
:disabled-overflow="isTopMenu" :disabled-overflow="isTopMenu"
> >
@@ -64,6 +65,7 @@ import { useLayoutStore } from '@core/stores/layout'
import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue' import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router' import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import { useRouterStore } from '@core/stores/router' import { useRouterStore } from '@core/stores/router'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const SCROLL_STEP = 180 const SCROLL_STEP = 180
const EDGE_THRESHOLD = 2 const EDGE_THRESHOLD = 2
@@ -73,6 +75,7 @@ const layoutStore = useLayoutStore()
const currentRoute = useRoute() const currentRoute = useRoute()
const selectedKeys = ref<(RouteRecordName | undefined)[]>([]) const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
const routerStore = useRouterStore() const routerStore = useRouterStore()
const { menuTheme } = useAdminTheme()
const menuScrollRef = ref<HTMLElement | null>(null) const menuScrollRef = ref<HTMLElement | null>(null)
const showLeftArrow = ref(false) const showLeftArrow = ref(false)
const showRightArrow = ref(false) const showRightArrow = ref(false)
@@ -224,6 +227,77 @@ useResizeObserver(menuScrollRef, () => {
position: relative; position: relative;
min-width: 0; min-width: 0;
flex: 1; 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 { .top-menu-nav__btn {
@@ -233,31 +307,31 @@ useResizeObserver(menuScrollRef, () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 30px; width: 28px;
height: 36px; height: 32px;
border: none; border: none;
border-radius: 6px; border-radius: 6px;
background: #fff; background: var(--admin-bg-elevated);
color: @primary-color; color: var(--admin-gold-light);
cursor: pointer; cursor: pointer;
transform: translateY(-50%); 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; transition: background-color 0.2s ease;
&:hover { &:hover {
background: lighten(@primary-color, 53%); background: var(--admin-bg-hover);
} }
&--left { &--left {
left: 0; left: 0;
padding-right: 4px; padding-right: 2px;
background: linear-gradient(to right, #fff 55%, rgba(255, 255, 255, 0.85)); background: linear-gradient(to right, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade));
} }
&--right { &--right {
right: 0; right: 0;
padding-left: 4px; padding-left: 2px;
background: linear-gradient(to left, #fff 55%, rgba(255, 255, 255, 0.85)); background: linear-gradient(to left, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade));
} }
} }
@@ -267,48 +341,14 @@ useResizeObserver(menuScrollRef, () => {
overflow-y: hidden; overflow-y: hidden;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
height: 100%;
display: flex;
align-items: center;
scrollbar-width: none; scrollbar-width: none;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; 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, .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 { &-side-menu {
.ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover, .ant-menu:not(.ant-menu-dark) .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-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover {
transition: background-color 0.9ms linear; transition: background-color 0.9ms linear;
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
color: unset !important; color: unset !important;
background-color: #f3f3f3 !important; background-color: var(--admin-bg-hover) !important;
} }
} }
.ant-menu { .ant-menu:not(.ant-menu-horizontal) {
padding: 10px; padding: 10px;
user-select: none; user-select: none;
.ant-menu-item,
.ant-menu-submenu-title {
@apply px-2 !important;
}
} }
.ant-menu-title-content { .ant-menu-title-content {
font-size: 15px; font-size: 14px;
} }
.ant-menu-item, .ant-menu:not(.ant-menu-horizontal) .ant-menu-item.ant-menu-item-selected {
.ant-menu-submenu-title { &::after {
@apply px-2 !important; content: unset;
}
} }
.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span,
@@ -380,12 +414,6 @@ useResizeObserver(menuScrollRef, () => {
height: 36px; height: 36px;
} }
.ant-menu-item.ant-menu-item-selected {
&::after {
content: unset;
}
}
.ant-menu.ant-menu-sub.ant-menu-inline { .ant-menu.ant-menu-sub.ant-menu-inline {
background-color: unset !important; background-color: unset !important;
} }
+6 -4
View File
@@ -64,11 +64,13 @@ const menu = computed(() => {
@prefix:~ '@{prefixCls}-basic-sub-menu'; @prefix:~ '@{prefixCls}-basic-sub-menu';
.@{prefix}{ .@{prefix}{
&.ant-menu-submenu-selected{ &.ant-menu-submenu-selected {
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
background-color: var(--ant-primary-1); background-color: var(--admin-menu-selected);
.ant-menu-submenu-arrow{ color: var(--admin-menu-selected-text);
color: var(--ant-primary)
.ant-menu-submenu-arrow {
color: var(--admin-menu-selected-text);
} }
} }
+19 -4
View File
@@ -12,6 +12,7 @@
import { computed, watchEffect } from 'vue' import { computed, watchEffect } from 'vue'
import { usePreferredDark, useStorage } from '@vueuse/core' import { usePreferredDark, useStorage } from '@vueuse/core'
import { acceptHMRUpdate, defineStore } from 'pinia' import { acceptHMRUpdate, defineStore } from 'pinia'
import { syncAntDarkStyle } from '@core/utils/syncAntDarkStyle'
export const DARK_MODE_BODY_CLASS = 'is-dark' export const DARK_MODE_BODY_CLASS = 'is-dark'
export type DarkModeSchema = 'auto' | 'dark' | 'light' export type DarkModeSchema = 'auto' | 'dark' | 'light'
@@ -24,13 +25,16 @@ export const initDarkmode = () => {
*/ */
watchEffect(() => { watchEffect(() => {
if (!import.meta.env.SSR) { if (!import.meta.env.SSR) {
const body = document.documentElement const root = document.documentElement
const isDark = darkmode.isDark
if (darkmode.isDark) { if (isDark) {
body.classList.add(DARK_MODE_BODY_CLASS) root.classList.add(DARK_MODE_BODY_CLASS)
} else { } else {
body.classList.remove(DARK_MODE_BODY_CLASS) root.classList.remove(DARK_MODE_BODY_CLASS)
} }
void syncAntDarkStyle(isDark)
} }
}) })
} }
@@ -56,9 +60,20 @@ export const useDarkmode = defineStore('darkmode', () => {
isDark.value = !target.checked isDark.value = !target.checked
} }
function toggle() {
isDark.value = !isDark.value
}
function setSchema(schema: DarkModeSchema) {
colorSchema.value = schema
}
return { return {
isDark, isDark,
colorSchema,
onChange, onChange,
toggle,
setSchema,
} }
}) })
+1 -1
View File
@@ -24,7 +24,7 @@ export const useLayoutStore = defineStore('layout', () => {
})*/ })*/
const header = reactive({ const header = reactive({
height: '45px', height: '56px',
fixed: true fixed: true
}) })
+161
View File
@@ -0,0 +1,161 @@
const STYLE_ATTR = 'data-antd-dark'
const BRAND_OVERRIDES = `
html.is-dark body {
background-color: #141414;
}
html.is-dark {
--ant-primary-color: #d4af37;
--ant-primary-color-hover: #e0bc3a;
--ant-primary-color-active: #c9a227;
--admin-menu-selected: #d4af37;
--admin-menu-selected-text: #141414;
}
html.is-dark .ant-layout-header {
background: #141414 !important;
border-bottom: 1px solid var(--admin-border-strong) !important;
padding: 0 12px !important;
}
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal {
background: transparent !important;
}
html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected,
html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected,
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected,
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected > .ant-menu-submenu-title {
background-color: var(--admin-menu-selected) !important;
color: var(--admin-menu-selected-text) !important;
}
html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .ant-menu-item-icon,
html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .anticon,
html.is-dark .ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected .ant-menu-title-content,
html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon,
html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected .anticon,
html.is-dark .ant-menu.ant-menu-dark .ant-menu-item-selected .ant-menu-title-content,
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-item-selected .ant-menu-item-icon,
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected .ant-menu-item-icon,
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu-selected .ant-menu-submenu-arrow {
color: var(--admin-menu-selected-text) !important;
}
html.is-dark .ant-menu-dark .ant-menu-item:not(.ant-menu-item-selected):hover,
html.is-dark .ant-menu-dark .ant-menu-submenu:not(.ant-menu-submenu-selected) > .ant-menu-submenu-title:hover,
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover,
html.is-dark .top-menu-nav .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu: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;
}
html.is-dark .hr-tamin-base-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
height: auto !important;
line-height: 1 !important;
}
html.is-dark .hr-tamin-base-button.ant-btn-sm {
min-height: 32px !important;
padding: 5px 12px !important;
overflow: visible !important;
}
html.is-dark .ant-card-body {
padding: 12px !important;
}
html.is-dark .ant-card,
html.is-dark .ant-card-head {
border-color: var(--admin-border-strong) !important;
}
html.is-dark .ant-input,
html.is-dark .ant-input-affix-wrapper,
html.is-dark .ant-select-selector,
html.is-dark .ant-picker {
border-color: var(--admin-border-strong) !important;
}
html.is-dark .ant-input:focus,
html.is-dark .ant-input-focused,
html.is-dark .ant-input-affix-wrapper-focused,
html.is-dark .ant-input-affix-wrapper:focus,
html.is-dark .ant-select-focused:not(.ant-select-disabled) .ant-select-selector,
html.is-dark .ant-picker-focused {
border-color: var(--admin-gold-light) !important;
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18) !important;
}
html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
color: var(--admin-menu-selected-text) !important;
background: var(--admin-gold-light) !important;
border-color: var(--admin-gold-light) !important;
}
html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover,
html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active {
color: var(--admin-menu-selected-text) !important;
background: var(--admin-gold-hover) !important;
border-color: var(--admin-gold-hover) !important;
}
html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before,
html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover::before,
html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active::before {
background-color: var(--admin-gold-light) !important;
}
html.is-dark .ant-radio-button-wrapper:hover {
color: var(--admin-gold-hover) !important;
}
html.is-dark .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
color: var(--admin-gold-light) !important;
border-color: var(--admin-gold-light) !important;
}
html.is-dark .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before {
background-color: var(--admin-gold-light) !important;
}
html.is-dark .ant-btn-primary {
background: var(--admin-gold-light) !important;
border-color: var(--admin-gold-light) !important;
}
html.is-dark .ant-btn-primary:not([disabled]):hover,
html.is-dark .ant-btn-primary:not([disabled]):focus {
background: var(--admin-gold-hover) !important;
border-color: var(--admin-gold-hover) !important;
}
html.is-dark .ant-btn-default:not([disabled]):hover,
html.is-dark .ant-btn-default:not([disabled]):focus {
color: var(--admin-gold-light) !important;
border-color: var(--admin-gold-light) !important;
}
@media (max-width: 376px) {
html.is-dark .ant-card-body {
padding: 5px !important;
}
}
`
let darkStyleEl: HTMLStyleElement | null = null
let loadingPromise: Promise<void> | null = null
async function injectDarkStyle() {
if (darkStyleEl) return
if (!loadingPromise) {
loadingPromise = import('ant-design-vue/dist/antd.dark.css?inline').then((mod) => {
darkStyleEl = document.createElement('style')
darkStyleEl.setAttribute(STYLE_ATTR, '')
darkStyleEl.textContent = `${mod.default}\n${BRAND_OVERRIDES}`
document.head.appendChild(darkStyleEl)
})
}
await loadingPromise
}
function removeDarkStyle() {
darkStyleEl?.remove()
darkStyleEl = null
loadingPromise = null
}
export async function syncAntDarkStyle(isDark: boolean) {
if (import.meta.env.SSR) return
if (isDark) {
await injectDarkStyle()
} else {
removeDarkStyle()
}
}
@@ -28,25 +28,25 @@ const props = defineProps<Prop>()
const bgColor = computed(() => { const bgColor = computed(() => {
switch (props.color) { switch (props.color) {
case 'fuchsia': case 'fuchsia':
return 'bg-fuchsia-100' return 'bg-fuchsia-100 dark:bg-fuchsia-500/20'
case 'orange': case 'orange':
return 'bg-orange-100' return 'bg-orange-100 dark:bg-orange-500/20'
case 'green': case 'green':
return 'bg-green-100' return 'bg-green-100 dark:bg-green-500/20'
case 'blue': case 'blue':
return 'bg-blue-100' return 'bg-blue-100 dark:bg-blue-500/20'
} }
}) })
const textColor = computed(() => { const textColor = computed(() => {
switch (props.color) { switch (props.color) {
case 'fuchsia': case 'fuchsia':
return 'text-fuchsia-600' return 'text-fuchsia-600 dark:text-fuchsia-300'
case 'orange': case 'orange':
return 'text-orange-600' return 'text-orange-600 dark:text-orange-300'
case 'green': case 'green':
return 'text-green-600' return 'text-green-600 dark:text-green-300'
case 'blue': case 'blue':
return 'text-blue-600' return 'text-blue-600 dark:text-blue-300'
} }
}) })
</script> </script>
+8 -6
View File
@@ -859,7 +859,9 @@ onMounted(() => {
} }
.selected_item { .selected_item {
@apply ! bg- [ #0A1B37 ] ! text-white rounded-lg; background: #2563eb !important;
color: #fff !important;
border-radius: 0.5rem;
} }
.dashboard-chart-toolbar { .dashboard-chart-toolbar {
@@ -869,10 +871,10 @@ onMounted(() => {
gap: 0.5rem; gap: 0.5rem;
button { button {
border: 1px solid #d1d5db; border: 1px solid var(--admin-border-strong);
border-radius: 0.5rem; border-radius: 0.5rem;
background: #fff; background: var(--admin-bg-elevated);
color: #374151; color: var(--admin-text);
cursor: pointer; cursor: pointer;
font-family: inherit; font-family: inherit;
font-size: 0.75rem; font-size: 0.75rem;
@@ -881,8 +883,8 @@ onMounted(() => {
&.active, &.active,
&:hover { &:hover {
background: #0a1b37; background: #2563eb;
border-color: #0a1b37; border-color: #2563eb;
color: #fff; color: #fff;
} }
} }
+3 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="bg-white w-fit p-12 rounded-lg m-auto h-[35rem]" :class="[prefixCls]"> <div class="admin-bg-elevated w-fit p-12 rounded-lg m-auto h-[35rem]" :class="[prefixCls]">
<div class="logo flex justify-center w-full flex-col items-center"> <div class="logo flex justify-center w-full flex-col items-center">
<img :src="getConfigApp('avatar')" class="w-[150px]" /> <img :src="getConfigApp('avatar')" class="w-[150px]" />
</div> </div>
@@ -221,6 +221,8 @@ const getcode=()=>{
@prefix: ~'@{prefixCls}-login-panel'; @prefix: ~'@{prefixCls}-login-panel';
.@{prefix} { .@{prefix} {
background-color: var(--admin-bg-elevated);
.logo { .logo {
width: 330px; width: 330px;
height: 148px; height: 148px;
+26 -2
View File
@@ -1,14 +1,18 @@
<template> <template>
<div <div
:style="{ backgroundImage: `url(${bg1})` }" :style="{ backgroundImage: `url(${bg1})` }"
class="w-screen h-screen top-0 right-0 fixed bg-cover flex z-[1000]" class="login-page w-screen h-screen top-0 right-0 fixed bg-cover flex z-[1000]"
> >
<div class="login-page__toggle">
<ThemeToggle />
</div>
<LoginPanel @enter="enterTest" class="shadow-2xl" /> <LoginPanel @enter="enterTest" class="shadow-2xl" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import LoginPanel from './components/LoginPanel.vue' import LoginPanel from './components/LoginPanel.vue'
import ThemeToggle from '@core/components/ThemeToggle.vue'
import bg1 from '../login/assets/img/loginBack.svg' import bg1 from '../login/assets/img/loginBack.svg'
const emits = defineEmits(['enter']) const emits = defineEmits(['enter'])
@@ -19,4 +23,24 @@ function enterTest() {
} }
</script> </script>
<style scoped></style> <style scoped>
.login-page {
position: relative;
}
.login-page__toggle {
position: absolute;
top: 16px;
left: 16px;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 9999px;
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border);
box-shadow: 0 2px 8px var(--admin-shadow);
}
</style>
@@ -67,10 +67,10 @@ const help = computed(() => helpText(props.row))
<style lang="less" scoped> <style lang="less" scoped>
.setting-card { .setting-card {
border: 1px solid #e8e8e8; border: 1px solid var(--admin-border);
border-radius: 10px; border-radius: 10px;
padding: 14px 16px; padding: 14px 16px;
background: #fff; background: var(--admin-bg-elevated);
min-height: 108px; min-height: 108px;
transition: border-color 0.2s, box-shadow 0.2s; transition: border-color 0.2s, box-shadow 0.2s;
height: 100%; height: 100%;
@@ -83,7 +83,7 @@ const help = computed(() => helpText(props.row))
} }
&--readonly { &--readonly {
background: #fafafa; background: var(--admin-bg-muted);
} }
&--saving { &--saving {
@@ -109,14 +109,14 @@ const help = computed(() => helpText(props.row))
&__title { &__title {
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
color: rgba(0, 0, 0, 0.88); color: var(--admin-text);
line-height: 1.45; line-height: 1.45;
} }
&__key { &__key {
margin-top: 6px; margin-top: 6px;
font-size: 12px; font-size: 12px;
color: rgba(0, 0, 0, 0.45); color: var(--admin-text-secondary);
direction: ltr; direction: ltr;
text-align: right; text-align: right;
word-break: break-all; word-break: break-all;
@@ -125,7 +125,7 @@ const help = computed(() => helpText(props.row))
&__help { &__help {
margin-top: 4px; margin-top: 4px;
font-size: 11px; font-size: 11px;
color: rgba(0, 0, 0, 0.45); color: var(--admin-text-secondary);
line-height: 1.35; line-height: 1.35;
} }
@@ -143,7 +143,7 @@ const help = computed(() => helpText(props.row))
&__status { &__status {
margin-top: 8px; margin-top: 8px;
font-size: 11px; font-size: 11px;
color: rgba(0, 0, 0, 0.45); color: var(--admin-text-secondary);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
+13 -13
View File
@@ -589,8 +589,8 @@ async function ok() {
align-items: flex-end; align-items: flex-end;
margin-bottom: 20px; margin-bottom: 20px;
padding: 14px 16px; padding: 14px 16px;
background: #fafafa; background: var(--admin-bg-elevated);
border: 1px solid #f0f0f0; border: 1px solid var(--admin-border);
border-radius: 8px; border-radius: 8px;
&__search { &__search {
@@ -621,7 +621,7 @@ async function ok() {
&__label { &__label {
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
color: rgba(0, 0, 0, 0.55); color: var(--admin-text-secondary);
line-height: 1; line-height: 1;
} }
@@ -633,10 +633,10 @@ async function ok() {
.settings-section-title { .settings-section-title {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: rgba(0, 0, 0, 0.55); color: var(--admin-text-secondary);
margin-bottom: 12px; margin-bottom: 12px;
padding-bottom: 6px; padding-bottom: 6px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid var(--admin-border);
&--spaced { &--spaced {
margin-top: 28px; margin-top: 28px;
@@ -659,11 +659,11 @@ async function ok() {
.settings-collapse :deep(.ant-collapse-item) { .settings-collapse :deep(.ant-collapse-item) {
margin-bottom: 12px; margin-bottom: 12px;
background: #fff; background: var(--admin-bg-elevated);
border: 1px solid #f0f0f0; border: 1px solid var(--admin-border);
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); box-shadow: 0 1px 3px var(--admin-shadow);
transition: box-shadow 0.2s ease, border-color 0.2s ease; transition: box-shadow 0.2s ease, border-color 0.2s ease;
} }
@@ -674,10 +674,10 @@ async function ok() {
.settings-collapse :deep(.ant-collapse-header) { .settings-collapse :deep(.ant-collapse-header) {
padding: 14px 18px !important; padding: 14px 18px !important;
background: linear-gradient(90deg, #fafafa 0%, #fff 100%); background: var(--admin-bg-muted);
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
color: #333; color: var(--admin-text);
} }
.settings-collapse-header { .settings-collapse-header {
@@ -695,15 +695,15 @@ async function ok() {
.settings-collapse-title { .settings-collapse-title {
flex: 1; flex: 1;
font-weight: 600; font-weight: 600;
color: #2c2c2c; color: var(--admin-text);
} }
.settings-empty-group { .settings-empty-group {
padding: 12px 16px; padding: 12px 16px;
font-size: 12px; font-size: 12px;
color: #999; color: var(--admin-text-secondary);
text-align: center; text-align: center;
background: #fafafa; background: var(--admin-bg-muted);
border-radius: 6px; border-radius: 6px;
margin-top: 8px; margin-top: 8px;
} }
@@ -2,17 +2,21 @@
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden"> <div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست مشتریان"> <BasicShowContext title="لیست مشتریان">
<template #extra> <template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')"> <BaseButton
<div>افزودن</div> v-if="isMobile"
<BaseIcon icon="vuesax-linear:user-add" Size="16px" /> type="primary"
size="small"
icon="vuesax-linear:user-add"
@click="emits('add')"
>
افزودن
</BaseButton> </BaseButton>
</template> </template>
<div class="flex h-full min-h-0 flex-col"> <div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl"> <BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader> <template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')"> <BaseButton type="primary" size="small" icon="vuesax-linear:user-add" @click="emits('add')">
<div>افزودن مشتری</div> افزودن مشتری
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
+2 -2
View File
@@ -104,7 +104,7 @@
<a-tabs v-if="userInfo" v-model:activeKey="activeKey"> <a-tabs v-if="userInfo" v-model:activeKey="activeKey">
<a-tab-pane key="اطلاعات جامع" tab="اطلاعات جامع"> <a-tab-pane key="اطلاعات جامع" tab="اطلاعات جامع">
<div class="space-y-2"> <div class="space-y-2">
<div class="rounded-lg bg-gray-100 w-full flex flex-col gap-3 p-3 md:flex-row md:justify-between"> <div class="admin-stat-muted rounded-lg w-full flex flex-col gap-3 p-3 md:flex-row md:justify-between">
<div> <div>
<div class="font-bold">{{ userInfo.name }}</div> <div class="font-bold">{{ userInfo.name }}</div>
<div class="mt-1">{{ userInfo?.mobile }}</div> <div class="mt-1">{{ userInfo?.mobile }}</div>
@@ -187,7 +187,7 @@
<div <div
v-for="(item, index) in userInfo.wallet" v-for="(item, index) in userInfo.wallet"
:key="index" :key="index"
class="grid grid-cols-2 bg-orange-50 gap-2 rounded-lg p-2" class="grid grid-cols-2 bg-orange-100 dark:bg-orange-500/15 gap-2 rounded-lg p-2"
> >
<BasicInfoItem <BasicInfoItem
title="نام قلک" title="نام قلک"
@@ -1,10 +1,9 @@
<template> <template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden"> <div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست کارت های هدیه"> <BasicShowContext title="لیست کارت های هدیه">
<template v-if="!customerId" #extra> <template v-if="!customerId && isMobile" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add', 'gift')"> <BaseButton type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add', 'gift')">
<div>افزودن</div> افزودن
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
@@ -12,19 +11,17 @@
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl"> <BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader> <template #extraHeader>
<div class="flex gap-2"> <div class="flex gap-2">
<BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add', 'gift')"> <BaseButton v-if="!customerId" type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add', 'gift')">
<div>افزودن کارت هدیه</div> افزودن کارت هدیه
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</BaseButton> </BaseButton>
<BaseButton <BaseButton
v-if="!customerId && isStoreApp()" v-if="!customerId && isStoreApp()"
type="primary" type="primary"
size="small" size="small"
class="flex" icon="vuesax-linear:discount-circle"
@click="emits('add', 'discount')" @click="emits('add', 'discount')"
> >
<div>افزودن کد تخفیف</div> افزودن کد تخفیف
<BaseIcon icon="vuesax-linear:discount-circle" Size="16px" />
</BaseButton> </BaseButton>
</div> </div>
</template> </template>
@@ -1,18 +1,16 @@
<template> <template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden"> <div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست سفارشات"> <BasicShowContext title="لیست سفارشات">
<template v-if="!customerId" #extra> <template v-if="!customerId && isMobile" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')"> <BaseButton type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add')">
<div>افزودن</div> افزودن
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<div class="flex h-full min-h-0 flex-col"> <div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl"> <BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader> <template #extraHeader>
<BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add')"> <BaseButton v-if="!customerId" type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add')">
<div>افزودن سفارش</div> افزودن سفارش
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<template #action="{ column, record }"> <template #action="{ column, record }">
@@ -3,14 +3,14 @@
<BasicShowContext title="تاریخچه قیمت"> <BasicShowContext title="تاریخچه قیمت">
<template #extra> <template #extra>
<BaseButton <BaseButton
v-if="isMobile"
type="primary" type="primary"
size="small" size="small"
class="flex shrink-0 md:hidden" icon="vuesax-linear:refresh"
:loading="syncing" :loading="syncing"
@click="emits('sync')" @click="emits('sync')"
> >
<div>به‌روزرسانی قیمت</div> به‌روزرسانی قیمت
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
@@ -20,12 +20,11 @@
<BaseButton <BaseButton
type="primary" type="primary"
size="small" size="small"
class="flex" icon="vuesax-linear:refresh"
:loading="syncing" :loading="syncing"
@click="emits('sync')" @click="emits('sync')"
> >
<div>به‌روزرسانی قیمت</div> به‌روزرسانی قیمت
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
@@ -63,13 +63,7 @@
import type { TableInput } from '@core/type/baseTable' import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable' import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue' import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { import { changeProductArchive } from '@/api/product'
changeCustomerActivation,
changeCustomerArchive,
changeCustomerOrderActivation,
getCustomerArchiveTable,
getCustomerTable
} from '@/api/customer'
import { cols } from '../hooks/archiveColumns' import { cols } from '../hooks/archiveColumns'
import { useUserStore } from '@/store/userStore' import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix' import { usePrefix } from '@core/composable/usePrefix'
@@ -105,37 +99,12 @@ const tblProps = ref<any>({
}) })
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerActivation(id)
reload()
message.success('تغییر وضعیت با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
async function changeCustomerOrderActivationFunction(id: number) {
try {
loading.value = true
await changeCustomerOrderActivation(id)
reload()
message.success('تغییر وضعیت سفارش دهی با موفقیت انجام شد')
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
async function archiveFunction(id: number) { async function archiveFunction(id: number) {
try { try {
loading.value = true loading.value = true
await changeCustomerArchive(id) await changeProductArchive(id)
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>]) await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
message.success('مشتری با موفقیت از بایگانی خارج شد.') message.success('محصول با موفقیت از بایگانی خارج شد.')
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} finally { } finally {
@@ -36,17 +36,21 @@
<BasicShowContext title="لیست محصولات" class="min-h-0 flex-1"> <BasicShowContext title="لیست محصولات" class="min-h-0 flex-1">
<template #extra> <template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')"> <BaseButton
<div>افزودن</div> v-if="isMobile"
<BaseIcon icon="vuesax-linear:box" Size="16px" /> type="primary"
size="small"
icon="vuesax-linear:box"
@click="emits('add')"
>
افزودن محصول
</BaseButton> </BaseButton>
</template> </template>
<div class="flex h-full min-h-0 flex-col"> <div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl"> <BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader> <template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')"> <BaseButton type="primary" size="small" icon="vuesax-linear:box" @click="emits('add')">
<div>افزودن محصول</div> افزودن محصول
<BaseIcon icon="vuesax-linear:box" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
@@ -1,6 +1,11 @@
import type { BaseTableColumn } from '@core/type/baseTable' import type { BaseTableColumn } from '@core/type/baseTable'
import CustomTag from '@/components/CustomTag.vue' import CustomTag from '@/components/CustomTag.vue'
import { isStoreApp } from '@/utils/config' import { isStoreApp } from '@/utils/config'
import { usePrice } from '@core/utils/usePrice'
function formatMoney(value: number | null | undefined) {
return usePrice(value ?? 0, true, '').trim()
}
const inventoryColumn: BaseTableColumn = { const inventoryColumn: BaseTableColumn = {
title: 'موجودی', title: 'موجودی',
@@ -47,7 +52,8 @@ export function getProductColumns(): BaseTableColumn[] {
title: 'قیمت', title: 'قیمت',
dataIndex: 'price', dataIndex: 'price',
key: 'price', key: 'price',
type: 'Text' type: 'Text',
customRender: ({ record }) => formatMoney(record.price)
}, },
{ {
title: 'تعدیل قیمت (٪)', title: 'تعدیل قیمت (٪)',
@@ -79,13 +85,15 @@ export function getProductColumns(): BaseTableColumn[] {
title: 'هزینه نقدی موقع خرید', title: 'هزینه نقدی موقع خرید',
dataIndex: 'cash_fee_buy', dataIndex: 'cash_fee_buy',
key: 'cash_fee_buy', key: 'cash_fee_buy',
type: 'Number' type: 'Number',
customRender: ({ record }) => formatMoney(record.cash_fee_buy)
}, },
{ {
title: 'هزینه نقدی موقع فروش', title: 'هزینه نقدی موقع فروش',
dataIndex: 'cash_fee_sell', dataIndex: 'cash_fee_sell',
key: 'cash_fee_sell', key: 'cash_fee_sell',
type: 'Number' type: 'Number',
customRender: ({ record }) => formatMoney(record.cash_fee_sell)
}, },
...(isStoreApp() ? [inventoryColumn] : []), ...(isStoreApp() ? [inventoryColumn] : []),
{ {
+5 -7
View File
@@ -1,19 +1,17 @@
<template> <template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden"> <div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست پیامک ها"> <BasicShowContext title="لیست پیامک ها">
<template v-if="!mobile" #extra> <template v-if="!mobile && isMobileScreen" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')"> <BaseButton type="primary" size="small" icon="vuesax-linear:sms" @click="emits('add')">
<div>ارسال پیامک</div> ارسال پیامک
<BaseIcon icon="vuesax-linear:sms" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<div class="flex h-full min-h-0 flex-col"> <div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobileScreen" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl"> <BaseTable v-if="!isMobileScreen" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader> <template #extraHeader>
<BaseButton v-if="!mobile" type="primary" size="small" class="flex" @click="emits('add')"> <BaseButton v-if="!mobile" type="primary" size="small" icon="vuesax-linear:sms" @click="emits('add')">
<div>ارسال پیامک</div> ارسال پیامک
<BaseIcon icon="vuesax-linear:sms" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<template #action="{ column, record }"> <template #action="{ column, record }">
+10 -6
View File
@@ -2,18 +2,22 @@
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden"> <div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست مدیران"> <BasicShowContext title="لیست مدیران">
<template #extra> <template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')"> <BaseButton
<div>افزودن مدیر</div> v-if="isMobile"
<BaseIcon icon="vuesax-linear:user-add" Size="16px" /> type="primary"
size="small"
icon="vuesax-linear:user-add"
@click="emits('add')"
>
افزودن مدیر
</BaseButton> </BaseButton>
</template> </template>
<div class="flex h-full min-h-0 flex-col"> <div class="flex h-full min-h-0 flex-col">
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl"> <BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader> <template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')"> <BaseButton type="primary" size="small" icon="vuesax-linear:user-add" @click="emits('add')">
<div>افزودن مدیر</div> افزودن مدیر
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
</BaseButton> </BaseButton>
</template> </template>
<template #action="{ column, record }"> <template #action="{ column, record }">
@@ -9,11 +9,11 @@
> >
<div class="mb-2 font-medium">مجموع موجودی کیف پول</div> <div class="mb-2 font-medium">مجموع موجودی کیف پول</div>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center"> <div class="stat-inner rounded-lg px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">ریال</div> <div class="text-xs text-gray-500">ریال</div>
<div class="mt-1 font-bold break-all">{{ usePrice(sums?.cash ?? 0) }}</div> <div class="mt-1 font-bold break-all">{{ usePrice(sums?.cash ?? 0) }}</div>
</div> </div>
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center"> <div class="stat-inner rounded-lg px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">گرم</div> <div class="text-xs text-gray-500">گرم</div>
<div class="mt-1 font-bold"> <div class="mt-1 font-bold">
{{ usePrice(Number(sums?.cashToGold ?? 0).toFixed(2), true, 'گرم') }} {{ usePrice(Number(sums?.cashToGold ?? 0).toFixed(2), true, 'گرم') }}
@@ -28,13 +28,13 @@
> >
<div class="mb-2 font-medium">مجموع موجودی کیف طلا</div> <div class="mb-2 font-medium">مجموع موجودی کیف طلا</div>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center"> <div class="stat-inner rounded-lg px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">گرم</div> <div class="text-xs text-gray-500">گرم</div>
<div class="mt-1 font-bold"> <div class="mt-1 font-bold">
{{ usePrice(sums?.gold ?? 0, true, 'گرم') }} {{ usePrice(sums?.gold ?? 0, true, 'گرم') }}
</div> </div>
</div> </div>
<div class="rounded-lg border border-gray-200 bg-white/70 px-2 py-1.5 text-center"> <div class="stat-inner rounded-lg px-2 py-1.5 text-center">
<div class="text-xs text-gray-500">ریال</div> <div class="text-xs text-gray-500">ریال</div>
<div class="mt-1 font-bold break-all">{{ usePrice(sums?.goldToCash ?? 0) }}</div> <div class="mt-1 font-bold break-all">{{ usePrice(sums?.goldToCash ?? 0) }}</div>
</div> </div>
+1 -1
View File
@@ -72,7 +72,7 @@
@close="confirmWeightVisible = false" @close="confirmWeightVisible = false"
> >
<div v-for="(item, index) in weightTransactions" :key="index"> <div v-for="(item, index) in weightTransactions" :key="index">
<div class="bg-gray-100 rounded-lg p-4 mt-2"> <div class="admin-stat-muted rounded-lg p-4 mt-2">
<div class="mb-4">{{ item?.title }} ({{ weightCount(item) }} عدد)</div> <div class="mb-4">{{ item?.title }} ({{ weightCount(item) }} عدد)</div>
<ADivider class="!my-0 "/> <ADivider class="!my-0 "/>
<div v-if="weightCount(item) > 0"> <div v-if="weightCount(item) > 0">
@@ -2,7 +2,7 @@
<div> <div>
<div class="mx-3 relative"> <div class="mx-3 relative">
<div class="absolute z-10 w-full"> <div class="absolute z-10 w-full">
<div class="flex justify-between items-center bg-gray-50 rounded p-3 w-full"> <div class="flex justify-between items-center admin-stat-muted rounded p-3 w-full">
<span class="text-base font-bold">اقلام سفارش</span> <span class="text-base font-bold">اقلام سفارش</span>
<Button type="primary" size="small" v-if="edit" @click="updateOrders" <Button type="primary" size="small" v-if="edit" @click="updateOrders"
>ثبت اطلاعات >ثبت اطلاعات