added chat 2

This commit is contained in:
2026-07-30 18:30:32 +03:30
parent 99698e345a
commit f4733214ed
26 changed files with 6921 additions and 53 deletions
@@ -30,7 +30,7 @@
<a-button
type="text"
danger
:disabled="disabled || rows.length <= 1"
:disabled="disabled"
aria-label="حذف"
@click="removeRow(index)"
>
@@ -49,7 +49,7 @@
</a-button>
<p v-if="rows.length === 0" class="support-messengers-field__hint">
حداقل یک پیام‌رسان با شناسه معتبر اضافه کنید.
لیست خالی است — در اپ کاربر اگر چت فعال باشد، پشتیبانی مستقیم به صفحه چت می‌رود.
</p>
</div>
</template>
@@ -111,7 +111,7 @@ function addRow() {
function removeRow(index: number) {
const next = rows.value.filter((_, i) => i !== index)
commit(next.length > 0 ? next : [{ messenger: 'bale', id: '' }])
commit(next)
}
</script>
+13 -1
View File
@@ -174,7 +174,7 @@ import SettingValueField from './SettingValueField.vue'
import SettingCard from './SettingCard.vue'
import { refreshAppModeMenu } from '@/router'
import { useUserStore } from '@/store/userStore'
import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config'
import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, applySupportChatMenuEnabledFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config'
import {
EDITABLE_SETTING_GROUPS,
SYSTEM_SETTING_GROUPS,
@@ -426,12 +426,14 @@ async function initConfig() {
applyPriceTriggerEnabledFromRows(baseData.value)
applyAppNotificationEnabledFromRows(baseData.value)
applyPriceProviderFromRows(baseData.value)
applySupportChatMenuEnabledFromRows(baseData.value)
// در صفحهٔ عادی price_provider (can_edit=0) نیست — از کل تنظیمات بخوان
if (!baseData.value.some((row) => row.key === 'price_provider')) {
try {
const { getConfiguration } = await import('@/api/configuration')
const all = await getConfiguration()
applyPriceProviderFromRows(Array.isArray(all) ? all : [])
applySupportChatMenuEnabledFromRows(Array.isArray(all) ? all : [])
} catch {
/* کش قبلی می‌ماند */
}
@@ -483,6 +485,16 @@ function syncMenuFlagsFromSetting(key: string | undefined, value: string) {
}
if (key === 'has_page_notification') {
setAppNotificationEnabledFromSettings(isSwitchOn(value))
return
}
if (key === 'show_to_users_chat' || key === 'has_chat' || key === 'chat_api') {
applySupportChatMenuEnabledFromRows([{ key, value }])
// همگام با settings داخل userInfo تا گیت روت/هدر بعد از رفرش هم درست باشد
const list = userStore.userInfo?.settings
if (Array.isArray(list)) {
const row = list.find((s: any) => s?.key === key)
if (row) row.value = value
}
}
}
@@ -21,7 +21,7 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [
title: 'دسترسی مشتری به قابلیت‌ها',
icon: 'vuesax-linear:people',
match: (k) =>
/^(banner_enabled|tracking_systems_enabled|jibit_verification|register_jibit_verify|show_product_details|show_product_trade_status|show_product_card_meta|products_site_link_enabled|products_site_link_url|products_site_link_text)$/.test(k)
/^(banner_enabled|tracking_systems_enabled|jibit_verification|register_jibit_verify|show_product_details|show_product_trade_status|show_product_card_meta|products_site_link_enabled|products_site_link_url|products_site_link_text|has_chat)$/.test(k)
},
{
id: 'trading_hours',
@@ -134,10 +134,17 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
title: 'دسترسی مشتری به قابلیت‌ها',
icon: 'vuesax-linear:people',
match: (k) =>
/^(enable_conditional_order|has_page_price_trigger|has_page_notification|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|tracking_systems_enabled|permision__enabled)$/.test(
/^(enable_conditional_order|has_page_price_trigger|has_page_notification|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|tracking_systems_enabled|permision__enabled|show_to_users_chat)$/.test(
k
)
},
{
id: 'support_chat',
title: 'چت پشتیبانی',
icon: 'vuesax-linear:messages-2',
match: (k) =>
/^(chat_api|chat_api_key|chat_upload_url|chat_download_url)$/.test(k)
},
{
id: 'general',
title: 'عمومی و برند',
@@ -76,29 +76,27 @@ export function parseSupportMessengers(value: string | undefined | null): Suppor
)
}
/** پارس برای UI — ردیف‌های ناقص (بدون id) هم نگه داشته می‌شوند */
/** پارس برای UI — لیست می‌تواند خالی باشد (چت مستقیم) */
export function parseSupportMessengersDraft(
value: string | undefined | null,
): SupportMessengerEntryDraft[] {
const raw = (value ?? '').trim()
if (!raw) return [{ messenger: 'bale', id: '' }]
if (!raw) return []
try {
const decoded = JSON.parse(raw)
if (!Array.isArray(decoded) || decoded.length === 0) {
return [{ messenger: 'bale', id: '' }]
return []
}
const rows = decoded
return decoded
.map((row) => ({
messenger: String(row?.messenger ?? '').trim().toLowerCase(),
id: String(row?.id ?? '').trim(),
}))
.filter((row) => isSupportMessengerType(row.messenger))
return rows.length > 0 ? rows : [{ messenger: 'bale', id: '' }]
} catch {
return [{ messenger: 'bale', id: '' }]
return []
}
}
@@ -328,6 +326,12 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
pargar_item_id_rial: 'number',
pargar_item_id_gold: 'number',
support_messengers: 'longtext',
show_to_users_chat: 'switch',
has_chat: 'switch',
chat_api: 'text',
chat_api_key: 'text',
chat_upload_url: 'text',
chat_download_url: 'text',
tracking_systems: 'longtext',
tracking_systems_enabled: 'switch',
support_messenger_id: 'text',
@@ -391,6 +395,13 @@ const SELECT_OPTIONS_BY_KEY: Record<string, string> = {
/** سوییچ‌هایی که مقدار required/خالی دارند (نه true/false) */
export const REFERRAL_REQUIRED_KEY = 'referral_id'
/** سوییچ‌هایی که مقدار activate/deactivate دارند (نه true/false) */
export const ACTIVATE_DEACTIVATE_SWITCH_KEYS = new Set(['show_to_users_chat'])
export function isActivateDeactivateSwitch(key: string | undefined): boolean {
return Boolean(key && ACTIVATE_DEACTIVATE_SWITCH_KEYS.has(key))
}
export function isReferralRequired(value: string | undefined): boolean {
return (value ?? '').trim().toLowerCase() === 'required'
}
@@ -436,7 +447,7 @@ export function parseSelectOptions(
export function isSwitchOn(value: string | undefined): boolean {
const v = (value ?? '').trim().toLowerCase()
return v === 'true' || v === '1' || v === 'yes'
return v === 'true' || v === '1' || v === 'yes' || v === 'activate'
}
export function switchToString(checked: boolean): string {
@@ -447,6 +458,10 @@ export function resolveSwitchChecked(key: string | undefined, value: string | un
if (key === REFERRAL_REQUIRED_KEY) {
return isReferralRequired(value)
}
if (isActivateDeactivateSwitch(key)) {
const v = (value ?? '').trim().toLowerCase()
return v === 'activate' || v === 'true' || v === '1' || v === 'yes'
}
return isSwitchOn(value)
}
@@ -454,6 +469,9 @@ export function switchValueFromChecked(key: string | undefined, checked: boolean
if (key === REFERRAL_REQUIRED_KEY) {
return referralRequiredToString(checked)
}
if (isActivateDeactivateSwitch(key)) {
return checked ? 'activate' : 'deactivate'
}
return switchToString(checked)
}
@@ -463,7 +481,10 @@ export function formatValueForSave(
key?: string
): string {
if (type === 'switch') {
return switchValueFromChecked(key, isSwitchOn(value) || (key === REFERRAL_REQUIRED_KEY && isReferralRequired(value)))
return switchValueFromChecked(
key,
resolveSwitchChecked(key, value)
)
}
if (type === 'price') {
return value === '' ? '0' : String(value).replace(/,/g, '')
@@ -666,7 +687,19 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
node_shared_secret:
'کلید مشترک امضای درخواست‌ها به سرویس سفارش خارجی. محرمانه نگه دارید.',
support_messengers:
'فقط مواردی که در این لیست ثبت شوند در منوی شناور پشتیبانی اپ مشتری نمایش داده می‌شوند. برای تماس تلفنی یا پیامک، نوع «تماس تلفنی» / «پیامک» را با شماره اضافه کنید.',
'فقط مواردی که در این لیست ثبت شوند در منوی شناور پشتیبانی اپ مشتری نمایش داده می‌شوند. برای تماس تلفنی یا پیامک، نوع «تماس تلفنی» / «پیامک» را با شماره اضافه کنید. لیست می‌تواند خالی باشد؛ اگر چت فعال باشد، پشتیبانی مستقیم به صفحه چت می‌رود.',
show_to_users_chat:
'روشن: قابلیت چت پشتیبانی برای این دیپلوی فعال است (منوی ادمین و پیش‌نیاز نمایش به کاربر). خاموش: چت در ادمین و اپ کاربر مخفی است. سوییچ مشتری (has_chat) جداگانه در تنظیمات عادی است.',
has_chat:
'روشن: چت پشتیبانی در اپ مشتری نمایش داده می‌شود (اگر نمایش چت به کاربران هم روشن باشد). خاموش: دکمه و صفحه چت برای کاربر مخفی است.',
chat_api:
'هاست سرور چت جدا (بدون https)، مثلاً chat.example.ir — برای WebSocket و ساخت روم.',
chat_api_key:
'کلید API سرور چت جدا.',
chat_upload_url:
'URL کامل آپلود مدیا برای چت پشتیبانی.',
chat_download_url:
'پایهٔ URL دانلود/نمایش فایل‌های چت پشتیبانی.',
tracking_systems:
'لیست سایت‌های ری‌گیری (name/url) — از منوی «سایت‌های ری‌گیری» ویرایش کنید.',
tracking_systems_enabled:
@@ -906,6 +939,12 @@ export const KEY_LABEL_FALLBACK: Record<string, string> = {
zarin_name_g: 'نام گروه حساب مشتریان زرین',
zarin_uid_prefix: 'پیشوند عددی uid زرین',
support_messengers: 'پیام‌رسان‌های پشتیبانی',
show_to_users_chat: 'نمایش چت به کاربران',
has_chat: 'چت پشتیبانی',
chat_api: 'API چت',
chat_api_key: 'کلید API چت',
chat_upload_url: 'آدرس آپلود فایل چت',
chat_download_url: 'آدرس دانلود فایل چت',
tracking_systems: 'سایت‌های ری‌گیری',
tracking_systems_enabled: 'نمایش سایت‌های ری‌گیری',
permision__enabled: 'نمایش منوی مجوزها',
@@ -974,7 +1013,8 @@ export const SETTING_LOCKED_KEYS = new Set([
'unit',
'up_price', 'down_price',
'wss_enabled',
'zaarbox_api_url'
'zaarbox_api_url',
'show_to_users_chat', 'chat_api', 'chat_api_key', 'chat_upload_url', 'chat_download_url'
])
/** قابل ویرایش در پنل — هم‌تراز deploy_settings_const.sql */
@@ -1008,6 +1048,7 @@ export const SETTING_EDITABLE_KEYS = new Set([
'products_site_link_enabled', 'products_site_link_url', 'products_site_link_text',
'startDay',
'support_messengers', 'support_messenger_text',
'has_chat',
'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_sheba',
'text_about', 'text_rule',
'zaarbox_price_key',