setting better performance

This commit is contained in:
2026-06-07 10:58:23 +03:30
parent c1b33b5063
commit 2cee31105d
6 changed files with 278 additions and 49 deletions
+202 -45
View File
@@ -43,54 +43,56 @@
</div>
<a-spin :spinning="loading && !baseData.length">
<template v-if="switchRows.length">
<div class="settings-section-title">روشن / خاموش</div>
<a-row :gutter="[16, 16]" class="settings-grid">
<a-col
v-for="row in switchRows"
:key="row.id"
:xs="24"
:sm="12"
:lg="8"
>
<SettingCard
:row="row"
:model-value="draftValue(row)"
:readonly="isReadonly(row)"
:saving="isSaving(row.id)"
:saved-flash="isSavedFlash(row.id)"
@update:value="onValueChange(row, $event)"
@update:is-active="onActiveChange(row, $event)"
/>
</a-col>
</a-row>
</template>
<a-collapse
v-model:activeKey="activeKeys"
class="settings-collapse"
:bordered="false"
>
<a-collapse-panel
v-for="group in SETTING_GROUPS"
:key="group.id"
:collapsible="groupedFiltered[group.id]?.length ? undefined : 'disabled'"
>
<template #header>
<div class="settings-collapse-header">
<BaseIcon :icon="group.icon" size="18" class="settings-collapse-icon" />
<span class="settings-collapse-title">{{ group.title }}</span>
<a-badge
v-if="groupedFiltered[group.id]"
:count="groupedFiltered[group.id].length"
:number-style="{ backgroundColor: '#d4af37', color: '#fff', fontSize: '11px' }"
/>
</div>
</template>
<template v-if="otherRows.length">
<div class="settings-section-title settings-section-title--spaced">سایر تنظیمات</div>
<a-row :gutter="[16, 16]" class="settings-grid">
<a-col
v-for="row in otherRows"
:key="row.id"
:xs="24"
:sm="12"
:lg="rowColSpan(row)"
>
<SettingCard
:row="row"
:model-value="draftValue(row)"
:readonly="isReadonly(row)"
:saving="isSaving(row.id)"
:saved-flash="isSavedFlash(row.id)"
@update:value="onValueChange(row, $event)"
@update:is-active="onActiveChange(row, $event)"
/>
</a-col>
</a-row>
</template>
<a-row :gutter="[16, 16]" class="settings-grid">
<a-col
v-for="row in groupedFiltered[group.id] || []"
:key="row.id"
:xs="24"
:sm="12"
:lg="rowColSpan(row)"
>
<SettingCard
:row="row"
:model-value="draftValue(row)"
:readonly="isReadonly(row)"
:saving="isSaving(row.id)"
:saved-flash="isSavedFlash(row.id)"
@update:value="onValueChange(row, $event)"
@update:is-active="onActiveChange(row, $event)"
/>
</a-col>
</a-row>
<div v-if="!groupedFiltered[group.id]?.length" class="settings-empty-group">
موردی در این گروه یافت نشد
</div>
</a-collapse-panel>
</a-collapse>
<a-empty
v-if="!loading && !switchRows.length && !otherRows.length"
v-if="!loading && Object.keys(groupedFiltered).length === 0"
description="تنظیمی یافت نشد"
class="mt-8"
/>
@@ -161,6 +163,7 @@
<script setup lang="ts">
import { debounce } from 'lodash'
import { watch } from 'vue'
import BasicShowContent from '@/components/BasicShowContext.vue'
import BasicModal from '@/components/BasicModal.vue'
import { inputRules } from '@/utils/inputRules'
@@ -273,6 +276,98 @@ const otherRows = computed(() =>
filteredRows.value.filter((row) => resolveSettingFieldType(row) !== 'switch')
)
// =============================================================================
// گروه‌بندی تنظیمات (برای کالپس جذاب + سرچ هوشمند)
// =============================================================================
const SETTING_GROUPS: Array<{
id: string
title: string
icon: string
match: (key: string) => boolean
}> = [
{
id: 'general',
title: 'عمومی و اطلاعات اپ',
icon: 'vuesax-linear:home-2',
match: (k) => /^(name_|app_|admin_|server_|bace_|key$|color_|logo_|title_pwa|title_fav|metal_type)/.test(k)
},
{
id: 'pricing',
title: 'قیمت‌گذاری و کارمزدها',
icon: 'vuesax-linear:dollar-circle',
match: (k) =>
/^(fee_|buy_fee|sell_fee|min_|referral_|gift_for|price_provider|zaarbox_api_url|chart_id)/.test(k) ||
k.includes('charge_percentage')
},
{
id: 'montzar',
title: 'سرویس همکار (montzar)',
icon: 'vuesax-linear:link',
match: (k) => k.startsWith('montzar_')
},
{
id: 'features',
title: 'ویژگی‌ها و صفحات',
icon: 'vuesax-linear:menu',
match: (k) =>
/^(has_page_|has_chat|display_montzar|close_price|startDay|endDay|auto_active|open_register|charge_notif|group_sms|show_last_price|product_update|product_auto_close|goldir_price|tabanGohar_price|main_product_price)/.test(k)
},
{
id: 'integrations',
title: 'اتصالات خارجی و درگاه‌ها',
icon: 'vuesax-linear:card',
match: (k) => /^(tahesab_|payping_|jibit_|telegram_|bale_|liman_|support_messenger|external_order|profit_gold)/.test(k)
},
{
id: 'misc',
title: 'سایر تنظیمات (متفرقه)',
icon: 'vuesax-linear:more-circle',
match: () => true // catch-all
}
]
function getGroupId(key: string): string {
for (const g of SETTING_GROUPS) {
if (g.match(key)) return g.id
}
return 'misc'
}
const groupedFiltered = computed(() => {
const map: Record<string, Const[]> = {}
for (const g of SETTING_GROUPS) map[g.id] = []
for (const row of filteredRows.value) {
const gid = getGroupId(row.key || '')
map[gid].push(row)
}
// حذف گروه‌های خالی
Object.keys(map).forEach((k) => {
if (map[k].length === 0) delete map[k]
})
return map
})
// کالپس‌های باز — وقتی سرچ فعال است فقط گروه‌های دارای نتیجه باز می‌شوند
const defaultOpenGroups: string[] = []
const activeKeys = ref<string[]>(defaultOpenGroups)
watch(search, (q) => {
const query = q.trim().toLowerCase()
if (!query) {
// برگشت به حالت پیش‌فرض
if (activeKeys.value.length === 0 || activeKeys.value.every(k => !defaultOpenGroups.includes(k))) {
activeKeys.value = [...defaultOpenGroups]
}
return
}
// فقط گروه‌هایی که حداقل یک آیتم matching دارند
const matching: string[] = []
for (const gid of Object.keys(groupedFiltered.value)) {
if (groupedFiltered.value[gid].length > 0) matching.push(gid)
}
activeKeys.value = matching.length ? matching : defaultOpenGroups
}, { flush: 'post' })
function rowColSpan(row: Const): number {
return resolveSettingFieldType(row) === 'longtext' ? 24 : 8
}
@@ -516,4 +611,66 @@ async function ok() {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
}
/* ======================= Collapse Groups Attractive UI ======================= */
.settings-collapse {
background: transparent;
}
.settings-collapse :deep(.ant-collapse-item) {
margin-bottom: 12px;
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.settings-collapse :deep(.ant-collapse-item:hover) {
box-shadow: 0 4px 12px rgba(212, 175, 55, 0.08);
border-color: #f4e9c8;
}
.settings-collapse :deep(.ant-collapse-header) {
padding: 14px 18px !important;
background: linear-gradient(90deg, #fafafa 0%, #fff 100%);
font-weight: 600;
font-size: 14px;
color: #333;
}
.settings-collapse-header {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.settings-collapse-icon {
color: #d4af37;
flex-shrink: 0;
}
.settings-collapse-title {
flex: 1;
font-weight: 600;
color: #2c2c2c;
}
.settings-empty-group {
padding: 12px 16px;
font-size: 12px;
color: #999;
text-align: center;
background: #fafafa;
border-radius: 6px;
margin-top: 8px;
}
/* وقتی سرچ فعال است، پنل‌های باز جلوه بهتری داشته باشند */
.settings-collapse :deep(.ant-collapse-item-active) {
border-color: #f4e9c8;
box-shadow: 0 6px 16px rgba(212, 175, 55, 0.12);
}
</style>