admin big changes
This commit is contained in:
@@ -3,24 +3,13 @@
|
||||
class="setting-card"
|
||||
:class="{
|
||||
'setting-card--readonly': readonly,
|
||||
'setting-card--saving': saving,
|
||||
'setting-card--inactive': !isActive && fieldType !== 'switch'
|
||||
'setting-card--saving': saving
|
||||
}"
|
||||
>
|
||||
<div class="setting-card__head">
|
||||
<div class="setting-card__title-row">
|
||||
<span v-if="readonly" class="setting-card__lock" title="فقطخواندنی">🔒</span>
|
||||
<span class="setting-card__title">{{ title }}</span>
|
||||
<a-switch
|
||||
v-if="fieldType !== 'switch'"
|
||||
:checked="isActive"
|
||||
size="small"
|
||||
class="setting-card__active-switch"
|
||||
@change="onActiveChange"
|
||||
>
|
||||
<template #checkedChildren>فعال</template>
|
||||
<template #unCheckedChildren>غیرفعال</template>
|
||||
</a-switch>
|
||||
</div>
|
||||
<div class="setting-card__key">کلید: {{ settingKey }}</div>
|
||||
<div v-if="help" class="setting-card__help">{{ help }}</div>
|
||||
@@ -62,7 +51,6 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:value': [value: string]
|
||||
'update:isActive': [value: boolean]
|
||||
}>()
|
||||
|
||||
const settingKey = computed(() => props.row.key ?? '')
|
||||
@@ -74,12 +62,6 @@ const fieldType = computed<SettingFieldType>(() =>
|
||||
resolveSettingFieldType({ key: props.row.key, type: props.row.type })
|
||||
)
|
||||
|
||||
const isActive = computed(() => props.row.is_active !== false && props.row.is_active !== 0)
|
||||
|
||||
function onActiveChange(checked: boolean) {
|
||||
emit('update:isActive', checked)
|
||||
}
|
||||
|
||||
const help = computed(() => helpText(props.row))
|
||||
</script>
|
||||
|
||||
@@ -108,11 +90,6 @@ const help = computed(() => helpText(props.row))
|
||||
border-color: #91caff;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
opacity: 0.72;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
&__head {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -123,11 +100,6 @@ const help = computed(() => helpText(props.row))
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__active-switch {
|
||||
margin-right: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__lock {
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
:disabled="disabled"
|
||||
@change="onSwitchChange"
|
||||
>
|
||||
<template #checkedChildren>فعال</template>
|
||||
<template #unCheckedChildren>غیرفعال</template>
|
||||
<template #checkedChildren>{{ labels.on }}</template>
|
||||
<template #unCheckedChildren>{{ labels.off }}</template>
|
||||
</a-switch>
|
||||
|
||||
<ZaarboxPriceKeyField
|
||||
@@ -63,10 +63,11 @@
|
||||
<script setup lang="ts">
|
||||
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
|
||||
import {
|
||||
isSwitchOn,
|
||||
parseSelectOptions,
|
||||
resolveSettingFieldType,
|
||||
switchToString,
|
||||
resolveSwitchChecked,
|
||||
switchLabels,
|
||||
switchValueFromChecked,
|
||||
type SettingFieldType
|
||||
} from './settingTypes'
|
||||
|
||||
@@ -101,10 +102,12 @@ const selectItems = computed(() =>
|
||||
})
|
||||
)
|
||||
|
||||
const switchChecked = computed(() => isSwitchOn(props.modelValue))
|
||||
const labels = computed(() => switchLabels(props.settingKey))
|
||||
|
||||
const switchChecked = computed(() => resolveSwitchChecked(props.settingKey, props.modelValue))
|
||||
|
||||
function onSwitchChange(checked: boolean) {
|
||||
emit('update:value', switchToString(checked))
|
||||
emit('update:value', switchValueFromChecked(props.settingKey, checked))
|
||||
}
|
||||
|
||||
function onSelectChange(val: string | undefined) {
|
||||
|
||||
@@ -14,16 +14,6 @@
|
||||
</a-input>
|
||||
|
||||
<div class="settings-toolbar__filters">
|
||||
<label class="settings-filter-field">
|
||||
<span class="settings-filter-field__label">وضعیت</span>
|
||||
<a-select
|
||||
v-model:value="filterActive"
|
||||
allow-clear
|
||||
placeholder="همه"
|
||||
class="settings-filter-field__control"
|
||||
:options="statusFilterOptions"
|
||||
/>
|
||||
</label>
|
||||
<label class="settings-filter-field">
|
||||
<span class="settings-filter-field__label">نوع فیلد</span>
|
||||
<a-select
|
||||
@@ -80,7 +70,6 @@
|
||||
:saving="isSaving(row.id)"
|
||||
:saved-flash="isSavedFlash(row.id)"
|
||||
@update:value="onValueChange(row, $event)"
|
||||
@update:is-active="onActiveChange(row, $event)"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -187,11 +176,6 @@ import {
|
||||
|
||||
const settingTypeOptions = SETTING_TYPE_OPTIONS
|
||||
|
||||
const statusFilterOptions = [
|
||||
{ value: 'active', label: 'فعال' },
|
||||
{ value: 'inactive', label: 'غیرفعال' }
|
||||
]
|
||||
|
||||
const typeSelectOptions = SETTING_TYPE_OPTIONS.map((t) => ({
|
||||
value: t.key,
|
||||
label: t.value
|
||||
@@ -202,11 +186,6 @@ const drafts = ref<Record<number, string>>({})
|
||||
const addConfigVisible = ref(false)
|
||||
const search = ref('')
|
||||
const filterType = ref<string | undefined>()
|
||||
const filterActive = ref<string | undefined>()
|
||||
|
||||
function isRowActive(row: Const): boolean {
|
||||
return row.is_active !== false && row.is_active !== 0
|
||||
}
|
||||
|
||||
const savingIds = ref<Set<number>>(new Set())
|
||||
const savedFlashIds = ref<Set<number>>(new Set())
|
||||
@@ -249,8 +228,6 @@ function isEditable(row: Const): boolean {
|
||||
|
||||
function matchesFilter(row: Const): boolean {
|
||||
const q = search.value.trim().toLowerCase()
|
||||
if (filterActive.value === 'active' && !isRowActive(row)) return false
|
||||
if (filterActive.value === 'inactive' && isRowActive(row)) return false
|
||||
if (filterType.value) {
|
||||
const t = resolveSettingFieldType(row)
|
||||
if (t !== filterType.value) return false
|
||||
@@ -297,16 +274,29 @@ const SETTING_GROUPS: Array<{
|
||||
id: 'general',
|
||||
title: 'عمومی و اطلاعات اپ',
|
||||
icon: 'vuesax-linear:home-2',
|
||||
match: (k) => /^(name_|app_|admin_|server_|bace_|key$|color_|logo_|title_pwa|title_fav|metal_type|unit$)/.test(k)
|
||||
match: (k) => /^(name_fa|app_url|admin_url|metal_type|unit$|open_register|auto_active_customer|is_store)/.test(k)
|
||||
},
|
||||
{
|
||||
id: 'chart',
|
||||
title: 'نمودار قیمت',
|
||||
icon: 'vuesax-linear:chart-2',
|
||||
match: (k) => /^chart_/.test(k)
|
||||
},
|
||||
{
|
||||
id: 'pricing',
|
||||
title: 'قیمتگذاری و کارمزدها',
|
||||
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) ||
|
||||
/^(fee_|buy_fee|sell_fee|min_|price_provider|zaarbox_|main_product$|show_last_price|close_price|startDay|endDay)/.test(k) ||
|
||||
k.includes('charge_percentage')
|
||||
},
|
||||
{
|
||||
id: 'referral',
|
||||
title: 'معرف، پاداش و ثبتنام',
|
||||
icon: 'vuesax-linear:people',
|
||||
match: (k) =>
|
||||
/^(referral_|gift_for|register_referral|register_jibit|has_page_earn_money|jibit_verification)/.test(k)
|
||||
},
|
||||
{
|
||||
id: 'montzar',
|
||||
title: 'سرویس همکار (montzar)',
|
||||
@@ -315,22 +305,22 @@ const SETTING_GROUPS: Array<{
|
||||
},
|
||||
{
|
||||
id: 'features',
|
||||
title: 'ویژگیها و صفحات',
|
||||
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|is_store|store_status)/.test(k)
|
||||
match: (k) => /^(charge_notif|group_sms|main_wallet_products|price_from_nasiryan)/.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)
|
||||
match: (k) =>
|
||||
/^(tahesab_|payping_|telegram_|bale_|support_messenger|external_order|profit_gold|node_)/.test(k)
|
||||
},
|
||||
{
|
||||
id: 'misc',
|
||||
title: 'سایر تنظیمات (متفرقه)',
|
||||
title: 'سایر تنظیمات',
|
||||
icon: 'vuesax-linear:more-circle',
|
||||
match: () => true // catch-all
|
||||
match: () => true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -434,12 +424,6 @@ function onValueChange(row: Const, value: string) {
|
||||
scheduleSave(row)
|
||||
}
|
||||
|
||||
async function onActiveChange(row: Const, isActive: boolean) {
|
||||
if (row.id == null) return
|
||||
if (isRowActive(row) === isActive) return
|
||||
await persistRow(row, { isActive })
|
||||
}
|
||||
|
||||
function scheduleSave(row: Const) {
|
||||
const id = row.id!
|
||||
if (!debouncedSave.has(id)) {
|
||||
@@ -457,21 +441,18 @@ async function persistRowValue(id: number) {
|
||||
await saveSettingRow(row)
|
||||
}
|
||||
|
||||
async function persistRow(row: Const, overrides?: { isActive?: boolean }) {
|
||||
await saveSettingRow(row, overrides)
|
||||
async function persistRow(row: Const) {
|
||||
await saveSettingRow(row)
|
||||
}
|
||||
|
||||
async function saveSettingRow(row: Const, overrides?: { isActive?: boolean }) {
|
||||
async function saveSettingRow(row: Const) {
|
||||
if (row.id == null) return
|
||||
const id = row.id
|
||||
|
||||
const type = resolveSettingFieldType(row)
|
||||
const raw = drafts.value[id] ?? row.value ?? ''
|
||||
const value = formatValueForSave(raw, type)
|
||||
const isActive =
|
||||
overrides?.isActive !== undefined ? overrides.isActive : isRowActive(row)
|
||||
|
||||
if (overrides?.isActive === undefined && value === (row.value ?? '')) return
|
||||
const value = formatValueForSave(raw, type, row.key)
|
||||
if (value === (row.value ?? '')) return
|
||||
|
||||
savingIds.value.add(id)
|
||||
savedFlashIds.value.delete(id)
|
||||
@@ -482,12 +463,12 @@ async function saveSettingRow(row: Const, overrides?: { isActive?: boolean }) {
|
||||
key: row.key,
|
||||
value,
|
||||
description: row.description ?? '',
|
||||
is_active: isActive,
|
||||
is_active: true,
|
||||
title_fa: row.title_fa ?? '',
|
||||
type
|
||||
})
|
||||
row.value = value
|
||||
row.is_active = isActive
|
||||
row.is_active = true
|
||||
drafts.value[id] = value
|
||||
if (row.key === 'is_store') {
|
||||
setStoreModeFromSettings(isSwitchOn(value))
|
||||
@@ -536,7 +517,7 @@ async function ok() {
|
||||
|
||||
const payload = {
|
||||
key: form.value.key,
|
||||
value: formatValueForSave(form.value.value ?? '', type),
|
||||
value: formatValueForSave(form.value.value ?? '', type, form.value.key),
|
||||
description,
|
||||
is_active: form.value.is_active,
|
||||
title_fa: form.value.title_fa ?? '',
|
||||
|
||||
@@ -3,18 +3,21 @@ import { isStoreApp } from '@/utils/config'
|
||||
|
||||
export type SettingFieldType = 'text' | 'number' | 'switch' | 'select' | 'longtext'
|
||||
|
||||
/** کلیدهای legacy — در پنل نمایش داده نمیشوند (جایگزین: price_provider) */
|
||||
/** کلیدهای legacy — در پنل نمایش داده نمیشوند */
|
||||
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
||||
'main_product_price',
|
||||
'montzar_price',
|
||||
'etehadie_api_url',
|
||||
'goldir_price',
|
||||
'price_primary_source',
|
||||
'price_method'
|
||||
'price_method',
|
||||
'montzar_last_product_log',
|
||||
'montzar_order_response',
|
||||
'service_charge_percentage'
|
||||
])
|
||||
|
||||
/** فقط وقتی is_store فعال است در پنل دیده میشوند */
|
||||
export const STORE_ONLY_SETTING_KEYS = new Set(['store_status'])
|
||||
export const STORE_ONLY_SETTING_KEYS = new Set(['store_status', 'price_from_nasiryan'])
|
||||
|
||||
export function isSettingVisible(key: string | undefined | null): boolean {
|
||||
const k = (key || '').trim()
|
||||
@@ -33,8 +36,9 @@ export const SETTING_TYPE_OPTIONS = [
|
||||
]
|
||||
|
||||
const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
||||
chart_use_local_data: 'switch',
|
||||
price_provider: 'select',
|
||||
referral_id: 'select',
|
||||
referral_id: 'switch',
|
||||
payping_gateway_name: 'select',
|
||||
text_rule: 'longtext',
|
||||
text_about: 'longtext',
|
||||
@@ -43,66 +47,43 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
||||
buy_fee: 'number',
|
||||
sell_fee: 'number',
|
||||
fee_cash: 'number',
|
||||
fee_free: 'number',
|
||||
min_withdraw: 'number',
|
||||
min_first_buy: 'number',
|
||||
minimum_sell: 'number',
|
||||
gift_for_referrer: 'number',
|
||||
gift_for_referral: 'number',
|
||||
referral_bonus_amount: 'text',
|
||||
referral_count_for_gift: 'number',
|
||||
montzar_product_id: 'number',
|
||||
balance_report_exclude_customer_id: 'number',
|
||||
jibit_verification: 'switch',
|
||||
register_jibit_verify: 'switch',
|
||||
register_referral_sms_to_referrer: 'switch',
|
||||
register_referral_bonus_message: 'switch',
|
||||
open_register: 'switch',
|
||||
auto_active_customer: 'switch',
|
||||
close_price: 'switch',
|
||||
montzar_price: 'switch',
|
||||
main_product_price: 'switch',
|
||||
show_last_price: 'switch',
|
||||
send_to_tahesab: 'switch',
|
||||
profit_gold_api_enabled: 'switch',
|
||||
payping_round_toman: 'switch',
|
||||
payping_strict_failed_withdraw: 'switch',
|
||||
has_page_earn_money: 'switch',
|
||||
has_page_fund: 'switch',
|
||||
has_page_permission: 'switch',
|
||||
has_page_price_trigger: 'switch',
|
||||
has_page_physical_gold: 'switch',
|
||||
has_chat: 'switch',
|
||||
charge_notif: 'switch',
|
||||
group_sms: 'switch',
|
||||
display_montzar: 'switch',
|
||||
credit: 'switch',
|
||||
credit_daily: 'switch',
|
||||
credit_error_in_orders: 'switch',
|
||||
credit_rial_dynamic: 'switch',
|
||||
open_closed_messages: 'switch',
|
||||
new_style_create_order: 'switch',
|
||||
new_style_admin_layout: 'switch',
|
||||
register: 'switch',
|
||||
sharh: 'switch',
|
||||
multi_source: 'switch',
|
||||
get_orders_pdf: 'switch',
|
||||
get_orders_excel: 'switch',
|
||||
auto_order_buy_sell: 'switch',
|
||||
invite_link: 'switch',
|
||||
montzar_closed: 'switch',
|
||||
montzar_open_buy: 'switch',
|
||||
montzar_open_sell: 'switch',
|
||||
product_update_price_on_list: 'switch',
|
||||
product_auto_close_open_on_list: 'switch',
|
||||
payping_fin_sync_enabled: 'switch',
|
||||
external_order_api_enabled: 'switch',
|
||||
is_store: 'switch',
|
||||
store_status: 'select',
|
||||
credit_rial_price_unit: 'select',
|
||||
zaarbox_price_key: 'select',
|
||||
price_from_nasiryan: 'switch',
|
||||
node_client_id: 'text',
|
||||
node_shared_secret: 'text',
|
||||
support_messenger_id: 'text',
|
||||
support_messenger_text: 'text',
|
||||
support_messenger: 'select'
|
||||
support_messenger: 'select',
|
||||
zaarbox_price_key: 'select'
|
||||
}
|
||||
|
||||
const SELECT_LABELS_BY_KEY: Record<string, Record<string, string>> = {
|
||||
@@ -118,14 +99,22 @@ const SELECT_LABELS_BY_KEY: Record<string, Record<string, string>> = {
|
||||
|
||||
const SELECT_OPTIONS_BY_KEY: Record<string, string> = {
|
||||
price_provider: 'montzar,zaarbox,main_product',
|
||||
referral_id: 'required,',
|
||||
payping_gateway_name: 'sepal,',
|
||||
credit_rial_price_unit: 'gram,mesghal,unit',
|
||||
store_status: 'باز,بسته',
|
||||
unit: 'گرم,کیلوگرم',
|
||||
support_messenger: 'bale,telegram,whatsapp,rubika,eitaa,soroush'
|
||||
}
|
||||
|
||||
/** سوییچهایی که مقدار required/خالی دارند (نه true/false) */
|
||||
export const REFERRAL_REQUIRED_KEY = 'referral_id'
|
||||
|
||||
export function isReferralRequired(value: string | undefined): boolean {
|
||||
return (value ?? '').trim().toLowerCase() === 'required'
|
||||
}
|
||||
|
||||
export function referralRequiredToString(checked: boolean): string {
|
||||
return checked ? 'required' : ''
|
||||
}
|
||||
|
||||
export function resolveSettingFieldType(row: Pick<Const, 'key' | 'type'>): SettingFieldType {
|
||||
const raw = (row.type || '').trim().toLowerCase()
|
||||
if (['text', 'number', 'switch', 'select', 'longtext'].includes(raw)) {
|
||||
@@ -169,9 +158,27 @@ export function switchToString(checked: boolean): string {
|
||||
return checked ? 'true' : 'false'
|
||||
}
|
||||
|
||||
export function formatValueForSave(value: string, type: SettingFieldType): string {
|
||||
export function resolveSwitchChecked(key: string | undefined, value: string | undefined): boolean {
|
||||
if (key === REFERRAL_REQUIRED_KEY) {
|
||||
return isReferralRequired(value)
|
||||
}
|
||||
return isSwitchOn(value)
|
||||
}
|
||||
|
||||
export function switchValueFromChecked(key: string | undefined, checked: boolean): string {
|
||||
if (key === REFERRAL_REQUIRED_KEY) {
|
||||
return referralRequiredToString(checked)
|
||||
}
|
||||
return switchToString(checked)
|
||||
}
|
||||
|
||||
export function formatValueForSave(
|
||||
value: string,
|
||||
type: SettingFieldType,
|
||||
key?: string
|
||||
): string {
|
||||
if (type === 'switch') {
|
||||
return switchToString(isSwitchOn(value))
|
||||
return switchValueFromChecked(key, isSwitchOn(value) || (key === REFERRAL_REQUIRED_KEY && isReferralRequired(value)))
|
||||
}
|
||||
if (type === 'number') {
|
||||
return value === '' ? '0' : String(value).replace(/,/g, '')
|
||||
@@ -179,6 +186,16 @@ export function formatValueForSave(value: string, type: SettingFieldType): strin
|
||||
return value ?? ''
|
||||
}
|
||||
|
||||
export function switchLabels(key: string | undefined): { on: string; off: string } {
|
||||
if (key === REFERRAL_REQUIRED_KEY) {
|
||||
return { on: 'اجباری', off: 'اختیاری' }
|
||||
}
|
||||
if (key === 'chart_use_local_data') {
|
||||
return { on: 'داده خود سایت', off: 'Zaarbox' }
|
||||
}
|
||||
return { on: 'فعال', off: 'غیرفعال' }
|
||||
}
|
||||
|
||||
export function helpText(row: Const): string {
|
||||
const desc = (row.description || '').trim()
|
||||
if (!desc) return ''
|
||||
|
||||
Reference in New Issue
Block a user