diff --git a/src/pages/menuPages/configuration/SettingCard.vue b/src/pages/menuPages/configuration/SettingCard.vue index 11359cf..1aca43d 100644 --- a/src/pages/menuPages/configuration/SettingCard.vue +++ b/src/pages/menuPages/configuration/SettingCard.vue @@ -41,7 +41,7 @@ import SettingValueField from './SettingValueField.vue' import { helpText, - KEY_LABEL_FALLBACK, + resolveSettingTitle, resolveSettingFieldType, type SettingFieldType } from './settingTypes' @@ -63,9 +63,7 @@ const emit = defineEmits<{ const settingKey = computed(() => props.row.key ?? '') const settingType = computed(() => props.row.type) const description = computed(() => props.row.description) -const title = computed( - () => props.row.title_fa || (props.row.key && KEY_LABEL_FALLBACK[props.row.key]) || props.row.key || '—' -) +const title = computed(() => resolveSettingTitle(props.row)) const fieldType = computed(() => resolveSettingFieldType({ key: props.row.key, type: props.row.type }) diff --git a/src/pages/menuPages/configuration/index.vue b/src/pages/menuPages/configuration/index.vue index beec0de..ed5ddb5 100644 --- a/src/pages/menuPages/configuration/index.vue +++ b/src/pages/menuPages/configuration/index.vue @@ -188,6 +188,7 @@ import { isSettingLocked, isSettingActive, isSwitchOn, + normalizeSettingRow, resolveSettingFieldType } from './settingTypes' @@ -416,10 +417,11 @@ watch( async function initConfig() { try { loading.value = true - baseData.value = + const rows = (isSystemMode.value ? await getSystemConfiguration() : await getEditableConfiguration()) ?? [] + baseData.value = rows.map(normalizeSettingRow) if (isSystemMode.value) { applyBannerEnabledFromRows(baseData.value) } else { diff --git a/src/pages/menuPages/configuration/settingTypes.ts b/src/pages/menuPages/configuration/settingTypes.ts index 5eb7056..3470247 100644 --- a/src/pages/menuPages/configuration/settingTypes.ts +++ b/src/pages/menuPages/configuration/settingTypes.ts @@ -262,7 +262,7 @@ export function switchLabels(key: string | undefined): { on: string; off: string return { on: 'فعال', off: 'غیرفعال' } } -/** توضیحات ساده و فارسی برای پنل — بر توضیح دیتابیس (که ممکن است نام فنی انگلیسی داشته باشد) اولویت دارد */ +/** توضیحات fallback — وقتی در کاتالوگ تعریف شده، بر description دیتابیس اولویت دارد */ const KEY_HELP_FALLBACK: Record = { name_fa: 'نام فارسی برند که در پیامک‌ها، عنوان صفحات و بخش‌های عمومی اپ به مشتری نشان داده می‌شود.', @@ -287,7 +287,7 @@ const KEY_HELP_FALLBACK: Record = { fee_service_charge_percentage: 'درصد کارمزد هر معامله از مبلغ سفارش (اعشاری؛ مثلاً ۰٫۰۰۵ یعنی نیم در هزار).', fee_deduct_from_gold: - 'روشن: کارمزد از مقدار طلا کسر می‌شود — در فروش مبلغ ریالی دقیق (مثلاً ۱۰۰ میلیون) واریز می‌شود و کارمزد از طلای کیف کم می‌شود. خاموش (پیش‌فرض): کارمزد از ریال فروش کم می‌شود — رفتار قبلی.', + 'روشن: مشتری دقیقاً همان مبلغ یا وزنی را که وارد کرده دریافت می‌کند. در فروش، مثلاً ۱۰۰ میلیون ریال درخواست → همان ۱۰۰ میلیون واریز و کارمزد از طلای کیف کسر می‌شود. در خرید، مثلاً ۱۰ گرم درخواست → همان ۱۰ گرم واریز و کارمزد به ریال قابل پرداخت اضافه می‌شود. خاموش (پیش‌فرض): در فروش کارمزد از مبلغ ریالی واریزی کم می‌شود (مشتری مبلغ کمتری می‌گیرد). در خرید کارمزد از وزن طلای واریزی کم می‌شود (مشتری طلای کمتری می‌گیرد).', fee_free: 'تعداد اولین سفارش‌های هر کاربر که از کارمزد معاف می‌شوند. صفر یعنی غیرفعال.', min_withdraw: @@ -313,7 +313,7 @@ const KEY_HELP_FALLBACK: Record = { register_referral_bonus_message: 'روشن: متن پاداش معرف در پیامک خوش‌آمد ثبت‌نام گنجانده می‌شود.', referral_gift_on_first_order: - 'روشن: پاداش معرف و معرف‌شونده پس از اولین سفارش واریز می‌شود. خاموش: واریز هنگام ثبت‌نام/تأیید احراز (رفتار قبلی).', + 'روشن: پاداش معرف و معرف‌شونده پس از تکمیل اولین سفارش معرف‌شونده به کیف واریز می‌شود. خاموش: پاداش بلافاصله پس از ثبت‌نام موفق یا تأیید احراز هویت واریز می‌شود.', referral_limit: 'حداکثر تعداد دعوت مجاز برای هر کاربر. خالی = بدون محدودیت.', referral_bonus_amount: @@ -418,7 +418,7 @@ const KEY_HELP_FALLBACK: Record = { 'تنظیم قدیمی — از «منبع قیمت» استفاده کنید. روشن نگه داشتن توصیه نمی‌شود.' } -/** برچسب فارسی ساده — اگر سرور برچسبی نفرستد یا برچسب فنی باشد */ +/** برچسب فارسی — وقتی در کاتالوگ تعریف شده، بر title_fa دیتابیس اولویت دارد */ export const KEY_LABEL_FALLBACK: Record = { close_price: 'بستن خودکار خارج از ساعت کاری', startDay: 'ساعت شروع', @@ -428,7 +428,7 @@ export const KEY_LABEL_FALLBACK: Record = { buy_fee: 'اضافه قیمت خرید', sell_fee: 'کسر قیمت فروش', fee_service_charge_percentage: 'درصد کارمزد معامله', - fee_deduct_from_gold: 'کارمزد از مقدار طلا', + fee_deduct_from_gold: 'اعمال مبلغ دقیق در معامله مشتری', fee_free: 'معافیت کارمزد سفارش اول', min_withdraw: 'حداقل مبلغ برداشت', min_first_buy: 'حداقل اولین خرید', @@ -451,20 +451,125 @@ export const KEY_LABEL_FALLBACK: Record = { deposit_sms_support_number: 'شماره پشتیبانی واریز' } +/** غیرقابل ویرایش در پنل — هم‌تراز deploy_settings_const.sql */ +export const SETTING_LOCKED_KEYS = new Set([ + 'admin_url', 'apk_download_link', 'app_url', + 'bale_bot_token', 'banner_enabled', + 'chart_id', 'chart_use_local_data', + 'external_order_api_enabled', + 'group_sms', + 'has_page_earn_money', + 'is_store', + 'logo_login', 'logo_register', 'title_pwa', 'title_fav', + 'main_product', 'main_wallet_products', 'metal_type', 'main_metal', + 'montify_name', + 'montzar_base_url', 'montzar_closed', + 'montzar_diff', 'montzar_last_product_log', 'montzar_live_price', 'montzar_mazaneh', + 'montzar_order_response', 'montzar_token', + 'montzar_truly_buy_mazaneh', 'montzar_truly_sell_mazaneh', + 'node_client_id', 'node_shared_secret', + 'payping_fin_customer_id', 'payping_fin_sync_enabled', 'payping_fin_token', 'payping_fin_url', + 'payping_gateway_items', 'payping_gateway_name', + 'payping_round_toman', 'payping_start_url', 'payping_strict_failed_withdraw', 'payping_verify_url', + 'price_extreme_date', 'price_provider', 'profit_gold_api_enabled', + 'send_to_tahesab', 'service_charge_percentage', + 'sse_path', + 'tahesab_bank_account', 'tahesab_db', 'tahesab_host', 'tahesab_port', 'tahesab_token', + 'telegram_bot_token', + 'unit', + 'up_price', 'down_price', + 'wss_enabled', + 'zaarbox_api_url', 'zaarbox_price_key' +]) + +/** قابل ویرایش در پنل — هم‌تراز deploy_settings_const.sql */ +export const SETTING_EDITABLE_KEYS = new Set([ + 'name_fa', + 'auto_active_customer', 'balance_report_exclude_customer_id', + 'buy_fee', 'charge_notif', 'close_price', + 'deposit_sms_support_number', 'endDay', + 'fee_deduct_from_gold', 'fee_free', 'fee_service_charge_percentage', 'fee_single', + 'gift_for_referral', 'gift_for_referrer', + 'invite_link_text', + 'jibit_verification', + 'main_product_price', + 'min_first_buy', 'min_withdraw', 'minimum_sell', + 'montzar_mobile', 'montzar_open_buy', 'montzar_open_sell', + 'montzar_password', 'montzar_price', 'montzar_product_id', + 'open_register', + 'referral_bonus_amount', 'referral_id', 'referral_limit', 'referral_gift_on_first_order', + 'register_jibit_verify', 'register_referral_bonus_message', 'register_referral_sms_to_referrer', + 'sell_fee', 'show_last_price', 'startDay', + 'support_messenger', 'support_messenger_id', 'support_messenger_text', + 'text_about', 'text_rule' +]) + +/** عنوان فارسی — کاتالوگ پنل بر title_fa دیتابیس اولویت دارد */ +export function resolveSettingTitle(row: Pick): string { + const key = (row.key ?? '').trim() + if (key && KEY_LABEL_FALLBACK[key]) return KEY_LABEL_FALLBACK[key] + return row.title_fa?.trim() || key || '—' +} + +/** توضیح — کاتالوگ پنل بر description دیتابیس اولویت دارد */ +export function resolveSettingDescription(row: Pick): string { + const key = (row.key ?? '').trim() + const help = key ? KEY_HELP_FALLBACK[key] : undefined + if (help) { + const fieldType = resolveSettingFieldType(row) + if (fieldType === 'select') { + if (SELECT_OPTIONS_BY_KEY[key]) return `${help}|${SELECT_OPTIONS_BY_KEY[key]}` + const dbDesc = (row.description ?? '').trim() + if (dbDesc.includes('|')) { + const options = dbDesc.split('|').pop()?.trim() + if (options) return `${help}|${options}` + } + } + return help + } + return row.description ?? '' +} + +/** can_edit — کاتالوگ پنل بر مقدار دیتابیس اولویت دارد */ +export function resolveSettingCanEdit(row: Pick): Const['can_edit'] { + const key = (row.key ?? '').trim() + if (SETTING_LOCKED_KEYS.has(key)) return 0 + if (SETTING_EDITABLE_KEYS.has(key)) return 1 + return row.can_edit ?? 1 +} + +/** اعمال override متادیتا (عنوان، توضیح، نوع، can_edit) روی ردیف API */ +export function normalizeSettingRow(row: Const): Const { + const key = (row.key ?? '').trim() + if (!key) return row + + const withType = { + ...row, + type: row.type || KEY_TYPE_FALLBACK[key] || row.type + } + + return { + ...withType, + title_fa: resolveSettingTitle(withType), + description: resolveSettingDescription(withType), + can_edit: resolveSettingCanEdit(withType), + type: resolveSettingFieldType(withType) + } +} + export function helpText(row: Const): string { const key = (row.key ?? '').trim() if (key && KEY_HELP_FALLBACK[key]) { return KEY_HELP_FALLBACK[key] } - const desc = (row.description || '').trim() - if (!desc) { - return '' - } + const desc = resolveSettingDescription(row).trim() + if (!desc) return '' + if (resolveSettingFieldType(row) === 'select' && desc.includes('|')) { return desc.split('|')[0]?.trim() ?? '' } - if (resolveSettingFieldType(row) === 'select' && desc.includes(',')) { + if (resolveSettingFieldType(row) === 'select' && desc.includes(',') && !/[روشن|خاموش]/.test(desc)) { return '' } return desc