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']
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
APopover: typeof import('ant-design-vue/es')['Popover']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
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']
RouterLink: typeof import('vue-router')['RouterLink']
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']
}
}
+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">
<!-- todo -->
<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>
<body>
<div id="app"></div>
+10 -2
View File
@@ -11,7 +11,10 @@ import {
export default defineConfig({
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: {
colors: {
@@ -20,7 +23,12 @@ export default defineConfig({
}
},
presets: [
presetUno(),
presetUno({
dark: {
dark: 'html.is-dark',
light: 'html:not(.is-dark)',
},
}),
presetAttributify(),
presetIcons(),
presetTypography(),
+9 -5
View File
@@ -6,17 +6,21 @@
</template>
<script lang="ts" setup>
import { onMounted} from 'vue'
import {useConfigStore} from "@/store/configStore";
import { onMounted } from 'vue'
import { useConfigStore } from '@/store/configStore'
import { initDarkmode } from '@core/stores/darkmode'
const router = useRouter()
const config=useConfigStore()
const config = useConfigStore()
initDarkmode()
function test() {
console.log('roussss', router)
router.push({ name: 'users' })
}
onMounted(async ()=>{
await config.getConfigData()
onMounted(async () => {
await config.getConfigData()
})
/*const router = useRouter()
+1 -1
View File
@@ -22,7 +22,7 @@ export const changeProductActivation = async (id: number) =>
export const changeCustomerOrderActivation = async (id: number) =>
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}`)
export const confirmUser = async (id: number) =>
+3 -4
View File
@@ -81,13 +81,12 @@
margin: 0 8px;
font-size: 20px;
text-align: center;
border: 1px solid #d1d5db;
border: 1px solid var(--admin-border-strong);
border-radius: 4px;
background-color: white;
//@apply bg-gray-50;
background-color: var(--admin-bg-surface);
&:focus-visible {
background-color: #fff;
background-color: var(--admin-bg-surface);
//display: none;
outline: none;
border: 1px solid @primary-color !important;
+2 -2
View File
@@ -29,7 +29,7 @@ const props = withDefaults(defineProps<Props>(), {})
.open {
width: 100%;
height: 1px;
background-color: #E5E7EB;
background-color: var(--admin-border-strong);
}
.rotate {
@@ -40,7 +40,7 @@ const props = withDefaults(defineProps<Props>(), {})
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
direction: rtl !important;
justify-content: space-between !important;
background-color: #fff !important;
background-color: var(--admin-bg-surface) !important;
font-weight: bold;
}
+2 -2
View File
@@ -206,8 +206,8 @@ function handleCancel() {
position: sticky;
bottom: 0;
z-index: 2;
background: #fff;
background: var(--admin-bg-elevated);
padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
border-top: 1px solid #f0f0f0;
border-top: 1px solid var(--admin-border);
}
</style>
+5 -5
View File
@@ -29,7 +29,7 @@
footer
>
<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 class="font-bold">{{ props.name }}</div>
<div class="text-primary-type mt-1">{{ props.type }}</div>
@@ -40,15 +40,15 @@
</div>
</div>
</div>
<div class="bg-[#CACED8] w-full h-[1px] my-8" />
<div class="text-[#949DB0]">{{ props.itemsTitle }}</div>
<div class="my-8 h-px w-full bg-[var(--admin-border)]" />
<div class="text-[var(--admin-text-secondary)]">{{ props.itemsTitle }}</div>
<div class="grid md:grid-cols-4 mt-5 gap-5 p-5">
<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>
<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>
</div>
+7 -2
View File
@@ -4,7 +4,7 @@
v-model:selectedKeys="selectedKeys"
:style="{ width: props.width }"
:mode="props.mode"
:theme="props.theme"
:theme="resolvedTheme"
>
<BasicMenuItem :menu="props.menu" />
</AMenu>
@@ -17,6 +17,9 @@ import { defineProps } from 'vue'
//import Icon from '@core/components/BaseIcon/src/Icon.vue'
import BasicMenuItem from '@/components/BasicMenu/components/BasicMenuItem.vue'
import type { RouteRecordRaw } from 'vue-router'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const { menuTheme } = useAdminTheme()
const selectedKeys = ref<string[]>([])
const router = useRouter()
@@ -39,11 +42,13 @@ interface Prop {
theme?: MenuTheme
width?: string
}
const props = withDefaults(defineProps<Prop>(), {
mode: 'inline',
theme: 'light',
width: '20rem'
})
const resolvedTheme = computed(() => props.theme ?? menuTheme.value)
onMounted(() => {
selectedKeys.value = [router.currentRoute.value.name as string]
//emits('onChange', '1')
+9 -9
View File
@@ -104,9 +104,9 @@ function navigateTo(item: RouteRecordRaw) {
justify-content: space-around;
gap: 4px;
padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
background: #fff;
border-top: 1px solid #f0f0f0;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
background: var(--admin-bg-surface);
border-top: 1px solid var(--admin-border);
box-shadow: 0 -2px 12px var(--admin-shadow);
}
.mobile-bottom-nav__item {
@@ -122,14 +122,14 @@ function navigateTo(item: RouteRecordRaw) {
background: transparent;
cursor: pointer;
position: relative;
color: #595959;
color: var(--admin-text-secondary);
&--active {
color: #1677ff;
color: var(--admin-menu-selected-text);
.mobile-bottom-nav__icon-wrap {
background: #e6f4ff;
color: #1677ff;
background: var(--admin-menu-selected);
color: var(--admin-menu-selected-text);
}
&::after {
@@ -141,7 +141,7 @@ function navigateTo(item: RouteRecordRaw) {
width: 28px;
height: 3px;
border-radius: 3px 3px 0 0;
background: #1677ff;
background: var(--admin-menu-selected-text);
}
}
@@ -157,7 +157,7 @@ function navigateTo(item: RouteRecordRaw) {
width: 40px;
height: 32px;
border-radius: 10px;
color: #292d32;
color: var(--admin-text-muted);
:deep(svg path) {
stroke: currentColor;
+29 -9
View File
@@ -22,6 +22,7 @@
</div>
</AMenuItem>
<AMenuDivider />
<ThemeMenuSection />
<AMenuItem @click="myInfoVisible = true">
<div class="flex items-center gap-1">
<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 type { FormInstance } from 'ant-design-vue'
import { cloneDeep } from 'lodash'
import ThemeMenuSection from '@core/components/ThemeMenuSection.vue'
const { prefixCls } = usePrefix('basic-header')
const userStore = useUserStore()
@@ -129,7 +131,11 @@ async function ok() {
}
function clearLocalStorage() {
const colorSchema = localStorage.getItem('color-schema')
localStorage.clear()
if (colorSchema) {
localStorage.setItem('color-schema', colorSchema)
}
location.reload()
}
@@ -142,9 +148,9 @@ function handleRefresh() {
@prefix: ~'@{prefixCls}-basic-header';
.@{prefix} {
padding: 12px 16px;
background-color: #f5f5f5;
border-bottom: none;
padding: 0;
background-color: var(--admin-bg-surface);
border-bottom: 1px solid var(--admin-border);
}
.mobile-header-bar {
@@ -153,17 +159,18 @@ function handleRefresh() {
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 16px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 16px;
width: 100%;
padding: 10px 12px;
background: var(--admin-bg-surface);
border: none;
border-radius: 0;
}
.mobile-header-bar__name {
max-width: 34%;
font-size: 15px;
font-weight: 600;
color: #1f1f1f;
color: var(--admin-text);
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
@@ -195,7 +202,7 @@ function handleRefresh() {
padding: 0;
border: none;
background: transparent;
color: #292d32;
color: var(--admin-text);
cursor: pointer;
line-height: 0;
@@ -203,4 +210,17 @@ function handleRefresh() {
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>
+4 -4
View File
@@ -92,11 +92,11 @@ function handleSelect(item: RouteRecordRaw) {
}
.mobile-overflow-menu__trigger--active {
color: #1677ff;
color: var(--admin-menu-selected-text);
.mobile-overflow-menu__icon-wrap {
background: #e6f4ff;
color: #1677ff;
background: var(--admin-menu-selected);
color: var(--admin-menu-selected-text);
}
&::after {
@@ -108,7 +108,7 @@ function handleSelect(item: RouteRecordRaw) {
width: 28px;
height: 3px;
border-radius: 3px 3px 0 0;
background: #1677ff;
background: var(--admin-menu-selected-text);
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
<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="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" />
</div>
<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>
<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' : '']"
@click="clickable ? $emit('click') : undefined"
>
+1
View File
@@ -4,6 +4,7 @@
@import (less) './scroll.less';
@import (less) "./fonts.less";
@import (less) './var.less';
@import (less) './theme.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;
@prefixCls-font-family: 'iran-sans';
@ant-prefix: @prefixCls;
@primary-color:#2563eb; // #727071; //#abc2f7; //#f4474d;
@second-color: #3b82f6; // #727071; //#abc2f7; //#f4474d;
@third-color: #B2B2B2; // #727071; //#abc2f7; //#f4474d;
@gold-color: #c9a227;
@gold-color-light: #d4af37;
@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-2: #F6F6F4;
@@ -37,10 +43,10 @@ html{
@border-radius-base: 4px;
html{
--ant-primary-color:@primary-color;
--ant-primary-color-hover:@second-color;
--ant-primary-color-active:@third-color;
--ant-primary-1:#f2f3ff;//#f6f6f4;
--ant-primary-color: @gold-color-light;
--ant-primary-color-hover: @gold-color-hover;
--ant-primary-color-active: @gold-color;
--ant-primary-1: rgba(201, 162, 39, 0.12);
--border-radius-base:@border-radius-base;
}
+78 -18
View File
@@ -1,20 +1,24 @@
<template>
<AButton :type="props.type" :size="props.size" :class="[prefixCls, _class]">
<!-- py-1 px-2-->
<div
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2"
:class="[{ 'flex-row-reverse': suffixIcon }]"
>
<slot v-if="$slots.default"></slot>
<BaseIcon :size="iconSize" :icon="icon" v-if="icon" :class="iconClass" />
</div>
<AButton
:type="props.type"
:size="props.size"
:class="[prefixCls, _class, { [`${prefixCls}--icon-suffix`]: suffixIcon }]"
>
<span v-if="icon && !suffixIcon" class="base-button__icon">
<BaseIcon :size="resolvedIconSize" :icon="icon" :class="iconClass" />
</span>
<span v-if="$slots.default" class="base-button__label">
<slot />
</span>
<span v-if="icon && suffixIcon" class="base-button__icon">
<BaseIcon :size="resolvedIconSize" :icon="icon" :class="iconClass" />
</span>
</AButton>
</template>
<script lang="ts" setup>
import { usePrefix } from '@core/composable/usePrefix'
import { computed } from 'vue'
import { computed, useSlots } from 'vue'
import { baseButtonIconSize } from '@root/config'
interface Props {
@@ -24,7 +28,6 @@ interface Props {
suffixIcon?: boolean
type?: string
size?: string
// prefixIcon?: string
}
const slot = useSlots()
@@ -40,17 +43,74 @@ const { prefixCls } = usePrefix('base-button')
const props = withDefaults(defineProps<Props>(), {
iconSize: baseButtonIconSize,
// iconLeft: false,
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>
<style lang="less">
@prefix:~'@{prefixCls}-base-button';
.@{prefix}{
//width: fit-content;
padding: 0;
height: auto;
/*padding: 8px;*/
.@{prefix} {
display: inline-flex !important;
align-items: center !important;
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>
+27 -10
View File
@@ -12,7 +12,7 @@
//todo convert to async load
import icons from '@core/assets/icons/icon.json'
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'
interface Props {
icon: string
@@ -27,20 +27,37 @@ const props = withDefaults(defineProps<Props>(), {
const { prefixCls } = usePrefix('icons')
const tempIcon = ref<string>()
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(() => {
return props.color
})
onMounted(() => {
if (props.size) {
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)
}
applySize()
})
</script>
@@ -2,7 +2,7 @@
<div :class="[prefixCls, 'min-w-0 max-w-full']">
<ASpin tip="در حال دریافت اطلاعات..." :indicator="indicator" :spinning="loading">
<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
v-if="showOptionHeader"
@@ -707,12 +707,16 @@ defineExpose({
width: 100%;
}
.table-toolbar {
background: transparent;
}
.table-toolbar-extra:empty {
display: none;
}
.ant-table-wrapper {
border: 1px solid #eee;
border: 1px solid var(--admin-border);
border-radius: var(--border-radius-base);
}
.ant-spin-container,
@@ -673,7 +673,7 @@ defineExpose({
overflow-y: auto;
}
.ant-table-wrapper {
border: 1px solid #eee;
border: 1px solid var(--admin-border);
border-radius: var(--border-radius-base);
}
.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>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between gap-3">
<span class="text-gray-400 text-sm">حالت نمایش</span>
<ARadioGroup v-model:value="colorSchema" size="small">
<ARadioButton value="light">روشن</ARadioButton>
<ARadioButton value="dark">تاریک</ARadioButton>
<ARadioButton value="auto">خودکار</ARadioButton>
</ARadioGroup>
</div>
<div class="flex justify-between">
<span class="text-gray-400 text-sm">نوار کناری (Sidebar)</span>
<ASwitch :checked="true" size="small" />
@@ -59,10 +67,14 @@
import { usePrefix } from '@core/composable/usePrefix'
import { useSettingsStore } from '@core/stores/settings'
import { useLayoutStore } from '@core/stores/layout'
import { useDarkmode } from '@core/stores/darkmode'
import { storeToRefs } from 'pinia'
//const visible = ref<boolean>(false)
const { prefixCls } = usePrefix('base-settings')
const settingsStore = useSettingsStore()
const layoutStore = useLayoutStore()
const darkmode = useDarkmode()
const { colorSchema } = storeToRefs(darkmode)
</script>
<style lang="less">
@@ -71,7 +83,7 @@ const layoutStore = useLayoutStore()
.layout-item{
cursor: pointer;
border:2px solid #eee;
border:2px solid var(--admin-border);
padding:5px;
&.active{
border-color: @primary-color;
+5 -3
View File
@@ -2,7 +2,7 @@
<!-- :class="[, isMobile ? null : 'shadow']"-->
<!-- :theme="sideTheme"-->
<ALayoutSider
theme="light"
:theme="menuTheme"
:class="[prefixCls]"
:width="layoutStore.sideMenu.width"
:collapsible="layoutStore.sideMenu.collapsible"
@@ -96,10 +96,12 @@
import { useLayoutStore } from '@core/stores/layout'
import { usePrefix } from '@core/composable/usePrefix'
import BasicMenu from '@core/layouts/composnts/BasicMenu.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
//import { coreVersion, version } from '../utils'
//import { isDate } from 'lodash'
const layoutStore = useLayoutStore()
const { prefixCls } = usePrefix('basic-side-menu')
const { menuTheme } = useAdminTheme()
</script>
<style lang="less">
@@ -124,13 +126,13 @@ const { prefixCls } = usePrefix('basic-side-menu')
}
.logo{
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--admin-border);
overflow: hidden;
transition: all 0.3s;
}
.side-version{
border-top: 1px solid #eee;
border-top: 1px solid var(--admin-border);
@apply p-2;
max-height: 50px
}
+40 -13
View File
@@ -1,8 +1,8 @@
<template>
<ALayoutHeader
theme="light"
:theme="menuTheme"
:class="[prefixCls]"
:style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"
:style="`height:${layoutStore.header.height}`"
>
<!-- :style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"-->
<div class="flex w-full min-w-0 items-center overflow-hidden">
@@ -26,8 +26,8 @@
<ADropdown placement="bottomLeft" class="ml-5">
<a class="ant-dropdown-link" @click.prevent>
<!-- Hover me-->
<div class="flex items-center gap-1 text-gray-400">
<AAvatar :src="placeholderM" />
<div class="flex items-center gap-1 text-[var(--admin-text-secondary)]">
<AAvatar :src="placeholderM" class="header-avatar" />
<Icon icon="vuesax-linear:arrow-down" size="15" />
</div>
</a>
@@ -40,6 +40,7 @@
</div>
</AMenuItem>
<AMenuDivider />
<ThemeMenuSection />
<AMenuItem @click="myInfoVisible = true">
<div class="flex items-center gap-1">
<Icon icon="vuesax-linear:profile" size="18" />
@@ -164,11 +165,14 @@ import type { FormInstance } from 'ant-design-vue'
import { cloneDeep } from 'lodash'
import { useWindowSize } from '@vueuse/core'
import {getConfigApp} from "@/utils/config";
import ThemeMenuSection from '@core/components/ThemeMenuSection.vue'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const { prefixCls } = usePrefix('basic-header')
const layoutStore = useLayoutStore()
const store = useStaticApiListsStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const { menuTheme } = useAdminTheme()
const { width } = useWindowSize()
@@ -223,7 +227,11 @@ function handleShowSettings() {
}
function clearLocalStorage() {
const colorSchema = localStorage.getItem('color-schema')
localStorage.clear()
if (colorSchema) {
localStorage.setItem('color-schema', colorSchema)
}
location.reload()
}
</script>
@@ -232,16 +240,35 @@ function clearLocalStorage() {
@prefix: ~'@{prefixCls}-basic-header';
.@{prefix} {
padding: unset;
padding: 0 12px !important;
line-height: normal !important;
overflow: hidden;
max-width: 100%;
//height: unset;
//@apply flex flex-col justify-center ;
//height: 64px;
//line-height: unset;
border-bottom: 2px solid #eee;
// height: @header-height;
background-color: #fff;
//@apply backdrop-blur bg-red-200;
display: flex;
align-items: center;
border-bottom: 1px solid var(--admin-border);
background-color: var(--admin-bg-surface);
> .flex {
height: 100%;
align-items: center;
}
.header-avatar {
border: 2px solid var(--admin-border-strong);
}
.hr-tamin-base-button.ant-btn-text {
color: var(--admin-text-secondary);
background: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
&:hover,
&:focus {
color: var(--admin-text);
background: var(--admin-bg-hover) !important;
}
}
}
</style>
+101 -73
View File
@@ -14,6 +14,7 @@
>
<AMenu
:mode="layoutStore.sideMenu.mode"
:theme="menuTheme"
v-model:selectedKeys="selectedKeys"
:disabled-overflow="isTopMenu"
>
@@ -64,6 +65,7 @@ import { useLayoutStore } from '@core/stores/layout'
import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import { useRouterStore } from '@core/stores/router'
import { useAdminTheme } from '@core/composable/useAdminTheme'
const SCROLL_STEP = 180
const EDGE_THRESHOLD = 2
@@ -73,6 +75,7 @@ const layoutStore = useLayoutStore()
const currentRoute = useRoute()
const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
const routerStore = useRouterStore()
const { menuTheme } = useAdminTheme()
const menuScrollRef = ref<HTMLElement | null>(null)
const showLeftArrow = ref(false)
const showRightArrow = ref(false)
@@ -224,6 +227,77 @@ useResizeObserver(menuScrollRef, () => {
position: relative;
min-width: 0;
flex: 1;
height: 100%;
display: flex;
align-items: center;
.top-menu-scroll .ant-menu.ant-menu-horizontal {
display: flex;
flex-wrap: nowrap;
align-items: center;
width: max-content;
height: 40px;
line-height: 40px;
border-bottom: none !important;
padding: 0 4px !important;
background: transparent !important;
}
.top-menu-scroll .ant-menu-horizontal > .ant-menu-item,
.top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu,
.top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title {
top: 0 !important;
margin-top: 0 !important;
height: 36px !important;
line-height: 36px !important;
padding: 0 10px !important;
margin-inline: 2px !important;
border-radius: 6px;
display: inline-flex;
align-items: center;
transition: background-color 0.15s ease, color 0.15s ease !important;
}
.top-menu-scroll .ant-menu-horizontal > .ant-menu-item::after,
.top-menu-scroll .ant-menu-horizontal > .ant-menu-submenu::after {
display: none !important;
}
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected,
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected {
background-color: var(--admin-menu-selected);
color: var(--admin-menu-selected-text);
}
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item-selected,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected > .ant-menu-submenu-title {
background-color: var(--admin-menu-selected) !important;
color: var(--admin-menu-selected-text) !important;
}
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item-selected .ant-menu-item-icon,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected .ant-menu-item-icon,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu-selected .ant-menu-submenu-arrow {
color: var(--admin-menu-selected-text) !important;
}
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-item:not(.ant-menu-item-selected):hover,
.top-menu-scroll .ant-menu-horizontal.ant-menu-dark > .ant-menu-submenu:not(.ant-menu-submenu-selected):hover > .ant-menu-submenu-title {
background-color: var(--admin-bg-hover) !important;
color: var(--admin-gold-light) !important;
box-shadow: inset 0 0 0 1px var(--admin-border-strong) !important;
}
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected::after,
.top-menu-scroll .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected::after {
display: none !important;
}
.top-menu-scroll .ant-menu-horizontal .ant-menu-item-icon + .ant-menu-title-content,
.top-menu-scroll .ant-menu-horizontal .ant-menu-item-icon + span {
margin-inline-start: 6px !important;
margin-inline-end: 0 !important;
}
}
.top-menu-nav__btn {
@@ -233,31 +307,31 @@ useResizeObserver(menuScrollRef, () => {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 36px;
width: 28px;
height: 32px;
border: none;
border-radius: 6px;
background: #fff;
color: @primary-color;
background: var(--admin-bg-elevated);
color: var(--admin-gold-light);
cursor: pointer;
transform: translateY(-50%);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
box-shadow: 0 0 0 1px var(--admin-border);
transition: background-color 0.2s ease;
&:hover {
background: lighten(@primary-color, 53%);
background: var(--admin-bg-hover);
}
&--left {
left: 0;
padding-right: 4px;
background: linear-gradient(to right, #fff 55%, rgba(255, 255, 255, 0.85));
padding-right: 2px;
background: linear-gradient(to right, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade));
}
&--right {
right: 0;
padding-left: 4px;
background: linear-gradient(to left, #fff 55%, rgba(255, 255, 255, 0.85));
padding-left: 2px;
background: linear-gradient(to left, var(--admin-gradient-surface) 60%, var(--admin-gradient-surface-fade));
}
}
@@ -267,48 +341,14 @@ useResizeObserver(menuScrollRef, () => {
overflow-y: hidden;
flex: 1;
min-width: 0;
height: 100%;
display: flex;
align-items: center;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.ant-menu {
display: flex;
flex-wrap: nowrap;
width: max-content;
border-bottom: none;
padding-top: 4px;
padding-bottom: 4px;
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item,
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu {
padding: 0 5px;
}
.ant-menu-horizontal > .ant-menu-item::after,
.ant-menu-horizontal > .ant-menu-submenu::after {
right: 0 !important;
left: 0 !important;
}
.ant-menu-horizontal > .ant-menu-item-selected::after,
.ant-menu-horizontal > .ant-menu-submenu-selected::after {
position: absolute;
bottom: 0;
border-bottom: 2px solid @primary-color;
transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: '';
}
.ant-menu-item-selected {
background-color: lighten(@primary-color, 53%);
}
.ant-menu-item {
margin-left: 12px;
}
}
.ant-menu-item,
@@ -321,40 +361,34 @@ useResizeObserver(menuScrollRef, () => {
}
}
/** start style for horizontal menu */
.ant-menu-horizontal {
padding: 0 !important;
li {
padding: 5px;
}
}
/** end style for horizontal menu */
&-side-menu {
.ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover,
.ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover {
.ant-menu:not(.ant-menu-dark) .ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover,
.ant-menu:not(.ant-menu-dark) .ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover {
transition: background-color 0.9ms linear;
border-radius: var(--border-radius-base);
color: unset !important;
background-color: #f3f3f3 !important;
background-color: var(--admin-bg-hover) !important;
}
}
.ant-menu {
.ant-menu:not(.ant-menu-horizontal) {
padding: 10px;
user-select: none;
.ant-menu-item,
.ant-menu-submenu-title {
@apply px-2 !important;
}
}
.ant-menu-title-content {
font-size: 15px;
font-size: 14px;
}
.ant-menu-item,
.ant-menu-submenu-title {
@apply px-2 !important;
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item.ant-menu-item-selected {
&::after {
content: unset;
}
}
.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span,
@@ -380,12 +414,6 @@ useResizeObserver(menuScrollRef, () => {
height: 36px;
}
.ant-menu-item.ant-menu-item-selected {
&::after {
content: unset;
}
}
.ant-menu.ant-menu-sub.ant-menu-inline {
background-color: unset !important;
}
+6 -4
View File
@@ -64,11 +64,13 @@ const menu = computed(() => {
@prefix:~ '@{prefixCls}-basic-sub-menu';
.@{prefix}{
&.ant-menu-submenu-selected{
&.ant-menu-submenu-selected {
border-radius: var(--border-radius-base);
background-color: var(--ant-primary-1);
.ant-menu-submenu-arrow{
color: var(--ant-primary)
background-color: var(--admin-menu-selected);
color: var(--admin-menu-selected-text);
.ant-menu-submenu-arrow {
color: var(--admin-menu-selected-text);
}
}
+19 -4
View File
@@ -12,6 +12,7 @@
import { computed, watchEffect } from 'vue'
import { usePreferredDark, useStorage } from '@vueuse/core'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { syncAntDarkStyle } from '@core/utils/syncAntDarkStyle'
export const DARK_MODE_BODY_CLASS = 'is-dark'
export type DarkModeSchema = 'auto' | 'dark' | 'light'
@@ -24,13 +25,16 @@ export const initDarkmode = () => {
*/
watchEffect(() => {
if (!import.meta.env.SSR) {
const body = document.documentElement
const root = document.documentElement
const isDark = darkmode.isDark
if (darkmode.isDark) {
body.classList.add(DARK_MODE_BODY_CLASS)
if (isDark) {
root.classList.add(DARK_MODE_BODY_CLASS)
} 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
}
function toggle() {
isDark.value = !isDark.value
}
function setSchema(schema: DarkModeSchema) {
colorSchema.value = schema
}
return {
isDark,
colorSchema,
onChange,
toggle,
setSchema,
}
})
+1 -1
View File
@@ -24,7 +24,7 @@ export const useLayoutStore = defineStore('layout', () => {
})*/
const header = reactive({
height: '45px',
height: '56px',
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(() => {
switch (props.color) {
case 'fuchsia':
return 'bg-fuchsia-100'
return 'bg-fuchsia-100 dark:bg-fuchsia-500/20'
case 'orange':
return 'bg-orange-100'
return 'bg-orange-100 dark:bg-orange-500/20'
case 'green':
return 'bg-green-100'
return 'bg-green-100 dark:bg-green-500/20'
case 'blue':
return 'bg-blue-100'
return 'bg-blue-100 dark:bg-blue-500/20'
}
})
const textColor = computed(() => {
switch (props.color) {
case 'fuchsia':
return 'text-fuchsia-600'
return 'text-fuchsia-600 dark:text-fuchsia-300'
case 'orange':
return 'text-orange-600'
return 'text-orange-600 dark:text-orange-300'
case 'green':
return 'text-green-600'
return 'text-green-600 dark:text-green-300'
case 'blue':
return 'text-blue-600'
return 'text-blue-600 dark:text-blue-300'
}
})
</script>
+8 -6
View File
@@ -859,7 +859,9 @@ onMounted(() => {
}
.selected_item {
@apply ! bg- [ #0A1B37 ] ! text-white rounded-lg;
background: #2563eb !important;
color: #fff !important;
border-radius: 0.5rem;
}
.dashboard-chart-toolbar {
@@ -869,10 +871,10 @@ onMounted(() => {
gap: 0.5rem;
button {
border: 1px solid #d1d5db;
border: 1px solid var(--admin-border-strong);
border-radius: 0.5rem;
background: #fff;
color: #374151;
background: var(--admin-bg-elevated);
color: var(--admin-text);
cursor: pointer;
font-family: inherit;
font-size: 0.75rem;
@@ -881,8 +883,8 @@ onMounted(() => {
&.active,
&:hover {
background: #0a1b37;
border-color: #0a1b37;
background: #2563eb;
border-color: #2563eb;
color: #fff;
}
}
+3 -1
View File
@@ -1,5 +1,5 @@
<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">
<img :src="getConfigApp('avatar')" class="w-[150px]" />
</div>
@@ -221,6 +221,8 @@ const getcode=()=>{
@prefix: ~'@{prefixCls}-login-panel';
.@{prefix} {
background-color: var(--admin-bg-elevated);
.logo {
width: 330px;
height: 148px;
+26 -2
View File
@@ -1,14 +1,18 @@
<template>
<div
: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" />
</div>
</template>
<script setup lang="ts">
import LoginPanel from './components/LoginPanel.vue'
import ThemeToggle from '@core/components/ThemeToggle.vue'
import bg1 from '../login/assets/img/loginBack.svg'
const emits = defineEmits(['enter'])
@@ -19,4 +23,24 @@ function enterTest() {
}
</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>
.setting-card {
border: 1px solid #e8e8e8;
border: 1px solid var(--admin-border);
border-radius: 10px;
padding: 14px 16px;
background: #fff;
background: var(--admin-bg-elevated);
min-height: 108px;
transition: border-color 0.2s, box-shadow 0.2s;
height: 100%;
@@ -83,7 +83,7 @@ const help = computed(() => helpText(props.row))
}
&--readonly {
background: #fafafa;
background: var(--admin-bg-muted);
}
&--saving {
@@ -109,14 +109,14 @@ const help = computed(() => helpText(props.row))
&__title {
font-weight: 600;
font-size: 14px;
color: rgba(0, 0, 0, 0.88);
color: var(--admin-text);
line-height: 1.45;
}
&__key {
margin-top: 6px;
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
color: var(--admin-text-secondary);
direction: ltr;
text-align: right;
word-break: break-all;
@@ -125,7 +125,7 @@ const help = computed(() => helpText(props.row))
&__help {
margin-top: 4px;
font-size: 11px;
color: rgba(0, 0, 0, 0.45);
color: var(--admin-text-secondary);
line-height: 1.35;
}
@@ -143,7 +143,7 @@ const help = computed(() => helpText(props.row))
&__status {
margin-top: 8px;
font-size: 11px;
color: rgba(0, 0, 0, 0.45);
color: var(--admin-text-secondary);
display: flex;
align-items: center;
gap: 6px;
+13 -13
View File
@@ -589,8 +589,8 @@ async function ok() {
align-items: flex-end;
margin-bottom: 20px;
padding: 14px 16px;
background: #fafafa;
border: 1px solid #f0f0f0;
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border);
border-radius: 8px;
&__search {
@@ -621,7 +621,7 @@ async function ok() {
&__label {
font-size: 12px;
font-weight: 500;
color: rgba(0, 0, 0, 0.55);
color: var(--admin-text-secondary);
line-height: 1;
}
@@ -633,10 +633,10 @@ async function ok() {
.settings-section-title {
font-size: 13px;
font-weight: 600;
color: rgba(0, 0, 0, 0.55);
color: var(--admin-text-secondary);
margin-bottom: 12px;
padding-bottom: 6px;
border-bottom: 1px solid #f0f0f0;
border-bottom: 1px solid var(--admin-border);
&--spaced {
margin-top: 28px;
@@ -659,11 +659,11 @@ async function ok() {
.settings-collapse :deep(.ant-collapse-item) {
margin-bottom: 12px;
background: #fff;
border: 1px solid #f0f0f0;
background: var(--admin-bg-elevated);
border: 1px solid var(--admin-border);
border-radius: 10px;
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;
}
@@ -674,10 +674,10 @@ async function ok() {
.settings-collapse :deep(.ant-collapse-header) {
padding: 14px 18px !important;
background: linear-gradient(90deg, #fafafa 0%, #fff 100%);
background: var(--admin-bg-muted);
font-weight: 600;
font-size: 14px;
color: #333;
color: var(--admin-text);
}
.settings-collapse-header {
@@ -695,15 +695,15 @@ async function ok() {
.settings-collapse-title {
flex: 1;
font-weight: 600;
color: #2c2c2c;
color: var(--admin-text);
}
.settings-empty-group {
padding: 12px 16px;
font-size: 12px;
color: #999;
color: var(--admin-text-secondary);
text-align: center;
background: #fafafa;
background: var(--admin-bg-muted);
border-radius: 6px;
margin-top: 8px;
}
@@ -2,17 +2,21 @@
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست مشتریان">
<template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
<BaseButton
v-if="isMobile"
type="primary"
size="small"
icon="vuesax-linear:user-add"
@click="emits('add')"
>
افزودن
</BaseButton>
</template>
<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">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مشتری</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
<BaseButton type="primary" size="small" icon="vuesax-linear:user-add" @click="emits('add')">
افزودن مشتری
</BaseButton>
</template>
<template #action="{ record }">
+2 -2
View File
@@ -104,7 +104,7 @@
<a-tabs v-if="userInfo" v-model:activeKey="activeKey">
<a-tab-pane key="اطلاعات جامع" tab="اطلاعات جامع">
<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 class="font-bold">{{ userInfo.name }}</div>
<div class="mt-1">{{ userInfo?.mobile }}</div>
@@ -187,7 +187,7 @@
<div
v-for="(item, index) in userInfo.wallet"
: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
title="نام قلک"
@@ -1,10 +1,9 @@
<template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست کارت های هدیه">
<template v-if="!customerId" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add', 'gift')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
<template v-if="!customerId && isMobile" #extra>
<BaseButton type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add', 'gift')">
افزودن
</BaseButton>
</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">
<template #extraHeader>
<div class="flex gap-2">
<BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add', 'gift')">
<div>افزودن کارت هدیه</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
<BaseButton v-if="!customerId" type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add', 'gift')">
افزودن کارت هدیه
</BaseButton>
<BaseButton
v-if="!customerId && isStoreApp()"
type="primary"
size="small"
class="flex"
icon="vuesax-linear:discount-circle"
@click="emits('add', 'discount')"
>
<div>افزودن کد تخفیف</div>
<BaseIcon icon="vuesax-linear:discount-circle" Size="16px" />
افزودن کد تخفیف
</BaseButton>
</div>
</template>
@@ -1,18 +1,16 @@
<template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست سفارشات">
<template v-if="!customerId" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
<template v-if="!customerId && isMobile" #extra>
<BaseButton type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add')">
افزودن
</BaseButton>
</template>
<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">
<template #extraHeader>
<BaseButton v-if="!customerId" type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن سفارش</div>
<BaseIcon icon="vuesax-linear:receipt-add" Size="16px" />
<BaseButton v-if="!customerId" type="primary" size="small" icon="vuesax-linear:receipt-add" @click="emits('add')">
افزودن سفارش
</BaseButton>
</template>
<template #action="{ column, record }">
@@ -3,14 +3,14 @@
<BasicShowContext title="تاریخچه قیمت">
<template #extra>
<BaseButton
v-if="isMobile"
type="primary"
size="small"
class="flex shrink-0 md:hidden"
icon="vuesax-linear:refresh"
:loading="syncing"
@click="emits('sync')"
>
<div>به‌روزرسانی قیمت</div>
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
به‌روزرسانی قیمت
</BaseButton>
</template>
@@ -20,12 +20,11 @@
<BaseButton
type="primary"
size="small"
class="flex"
icon="vuesax-linear:refresh"
:loading="syncing"
@click="emits('sync')"
>
<div>به‌روزرسانی قیمت</div>
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
به‌روزرسانی قیمت
</BaseButton>
</template>
<template #action="{ record }">
@@ -63,13 +63,7 @@
import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import {
changeCustomerActivation,
changeCustomerArchive,
changeCustomerOrderActivation,
getCustomerArchiveTable,
getCustomerTable
} from '@/api/customer'
import { changeProductArchive } from '@/api/product'
import { cols } from '../hooks/archiveColumns'
import { useUserStore } from '@/store/userStore'
import { usePrefix } from '@core/composable/usePrefix'
@@ -105,37 +99,12 @@ const tblProps = ref<any>({
})
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) {
try {
loading.value = true
await changeCustomerArchive(id)
await changeProductArchive(id)
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
message.success('مشتری با موفقیت از بایگانی خارج شد.')
message.success('محصول با موفقیت از بایگانی خارج شد.')
} catch (e) {
console.error(e)
} finally {
@@ -36,17 +36,21 @@
<BasicShowContext title="لیست محصولات" class="min-h-0 flex-1">
<template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن</div>
<BaseIcon icon="vuesax-linear:box" Size="16px" />
<BaseButton
v-if="isMobile"
type="primary"
size="small"
icon="vuesax-linear:box"
@click="emits('add')"
>
افزودن محصول
</BaseButton>
</template>
<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">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن محصول</div>
<BaseIcon icon="vuesax-linear:box" Size="16px" />
<BaseButton type="primary" size="small" icon="vuesax-linear:box" @click="emits('add')">
افزودن محصول
</BaseButton>
</template>
<template #action="{ record }">
@@ -1,6 +1,11 @@
import type { BaseTableColumn } from '@core/type/baseTable'
import CustomTag from '@/components/CustomTag.vue'
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 = {
title: 'موجودی',
@@ -47,7 +52,8 @@ export function getProductColumns(): BaseTableColumn[] {
title: 'قیمت',
dataIndex: 'price',
key: 'price',
type: 'Text'
type: 'Text',
customRender: ({ record }) => formatMoney(record.price)
},
{
title: 'تعدیل قیمت (٪)',
@@ -79,13 +85,15 @@ export function getProductColumns(): BaseTableColumn[] {
title: 'هزینه نقدی موقع خرید',
dataIndex: 'cash_fee_buy',
key: 'cash_fee_buy',
type: 'Number'
type: 'Number',
customRender: ({ record }) => formatMoney(record.cash_fee_buy)
},
{
title: 'هزینه نقدی موقع فروش',
dataIndex: 'cash_fee_sell',
key: 'cash_fee_sell',
type: 'Number'
type: 'Number',
customRender: ({ record }) => formatMoney(record.cash_fee_sell)
},
...(isStoreApp() ? [inventoryColumn] : []),
{
+5 -7
View File
@@ -1,19 +1,17 @@
<template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست پیامک ها">
<template v-if="!mobile" #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>ارسال پیامک</div>
<BaseIcon icon="vuesax-linear:sms" Size="16px" />
<template v-if="!mobile && isMobileScreen" #extra>
<BaseButton type="primary" size="small" icon="vuesax-linear:sms" @click="emits('add')">
ارسال پیامک
</BaseButton>
</template>
<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">
<template #extraHeader>
<BaseButton v-if="!mobile" type="primary" size="small" class="flex" @click="emits('add')">
<div>ارسال پیامک</div>
<BaseIcon icon="vuesax-linear:sms" Size="16px" />
<BaseButton v-if="!mobile" type="primary" size="small" icon="vuesax-linear:sms" @click="emits('add')">
ارسال پیامک
</BaseButton>
</template>
<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">
<BasicShowContext title="لیست مدیران">
<template #extra>
<BaseButton type="primary" size="small" class="flex shrink-0 md:hidden" @click="emits('add')">
<div>افزودن مدیر</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
<BaseButton
v-if="isMobile"
type="primary"
size="small"
icon="vuesax-linear:user-add"
@click="emits('add')"
>
افزودن مدیر
</BaseButton>
</template>
<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">
<template #extraHeader>
<BaseButton type="primary" size="small" class="flex" @click="emits('add')">
<div>افزودن مدیر</div>
<BaseIcon icon="vuesax-linear:user-add" Size="16px" />
<BaseButton type="primary" size="small" icon="vuesax-linear:user-add" @click="emits('add')">
افزودن مدیر
</BaseButton>
</template>
<template #action="{ column, record }">
@@ -9,11 +9,11 @@
>
<div class="mb-2 font-medium">مجموع موجودی کیف پول</div>
<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="mt-1 font-bold break-all">{{ usePrice(sums?.cash ?? 0) }}</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="mt-1 font-bold">
{{ usePrice(Number(sums?.cashToGold ?? 0).toFixed(2), true, 'گرم') }}
@@ -28,13 +28,13 @@
>
<div class="mb-2 font-medium">مجموع موجودی کیف طلا</div>
<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="mt-1 font-bold">
{{ usePrice(sums?.gold ?? 0, true, 'گرم') }}
</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="mt-1 font-bold break-all">{{ usePrice(sums?.goldToCash ?? 0) }}</div>
</div>
+1 -1
View File
@@ -72,7 +72,7 @@
@close="confirmWeightVisible = false"
>
<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>
<ADivider class="!my-0 "/>
<div v-if="weightCount(item) > 0">
@@ -2,7 +2,7 @@
<div>
<div class="mx-3 relative">
<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>
<Button type="primary" size="small" v-if="edit" @click="updateOrders"
>ثبت اطلاعات