diff --git a/src/model/product.ts b/src/model/product.ts
index fabb772..3607cac 100644
--- a/src/model/product.ts
+++ b/src/model/product.ts
@@ -30,12 +30,13 @@ export interface Product{
override_trading_hours?: boolean
use_last_price_if_zero?: boolean
has_physical_delivery?: boolean
- min_physical_delivery_grams?: number | null
+ min_physical_delivery_quantity?: number | null
title: string
updated_at: string
methodOption: number
prices: MethodePrices[]
direct_price: number
+ app_price?: boolean
price: number
price_profit?: number
price_offset_percent?: number
diff --git a/src/pages/menuPages/configuration/SettingCard.vue b/src/pages/menuPages/configuration/SettingCard.vue
index 1aca43d..80033a7 100644
--- a/src/pages/menuPages/configuration/SettingCard.vue
+++ b/src/pages/menuPages/configuration/SettingCard.vue
@@ -12,7 +12,18 @@
{{ title }}
(() =>
)
const help = computed(() => helpText(props.row))
+
+const switchHelp = computed(() =>
+ fieldType.value === 'switch' ? parseSwitchHelp(help.value) : null
+)
diff --git a/src/pages/menuPages/configuration/SettingValueField.vue b/src/pages/menuPages/configuration/SettingValueField.vue
index 5733155..baa7d3c 100644
--- a/src/pages/menuPages/configuration/SettingValueField.vue
+++ b/src/pages/menuPages/configuration/SettingValueField.vue
@@ -1,14 +1,12 @@
-
- {{ labels.on }}
- {{ labels.off }}
-
+ />
+
+
import MainWalletProductsField from './MainWalletProductsField.vue'
import PartnerServerUrlsField from './PartnerServerUrlsField.vue'
+import SettingToggle from './SettingToggle.vue'
import SupportMessengersField from './SupportMessengersField.vue'
+import TemplateTextField from './TemplateTextField.vue'
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
+import { hasTemplateField } from './templateFields'
import {
parseSelectOptions,
resolveSettingFieldType,
@@ -163,6 +173,7 @@ const isZaarboxPriceKey = computed(() => props.settingKey === 'zaarbox_price_key
const isMainWalletProducts = computed(() => props.settingKey === 'main_wallet_products')
const isSupportMessengers = computed(() => props.settingKey === 'support_messengers')
const isPartnerServerUrls = computed(() => props.settingKey === 'zaarbox_api_url')
+const isTemplateField = computed(() => hasTemplateField(props.settingKey))
const selectItems = computed(() =>
parseSelectOptions({
diff --git a/src/pages/menuPages/configuration/TemplateTextField.vue b/src/pages/menuPages/configuration/TemplateTextField.vue
new file mode 100644
index 0000000..4bf4ac3
--- /dev/null
+++ b/src/pages/menuPages/configuration/TemplateTextField.vue
@@ -0,0 +1,332 @@
+
+
+
+
+
+
+
+ {{ config.editorTitle }}
+
+
+
+
+
+
+
+ {{ config.hint }} متغیرها با % یا ٪ نوشته میشوند.
+
+
+
+
+
+
+
+
+ {{ group.title }}
+
+
+
+ %{{ v.token }}
+ {{ v.label }}
+
+
+
+
+
+
+
+
+
+
+ {{ config.previewTitle }}
+ نمونه
+
+
+
+
+
+ {{ config.legend }}
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/menuPages/configuration/settingTypes.ts b/src/pages/menuPages/configuration/settingTypes.ts
index 81a63b1..34b7378 100644
--- a/src/pages/menuPages/configuration/settingTypes.ts
+++ b/src/pages/menuPages/configuration/settingTypes.ts
@@ -290,6 +290,7 @@ const KEY_TYPE_FALLBACK: Record = {
price_channel_bale_to: 'text',
price_channel_telegram_to: 'text',
price_channel_rubika_to: 'text',
+ price_channel_message_text: 'longtext',
rubika_bot_token: 'text',
change_mesghal: 'number'
}
@@ -429,6 +430,54 @@ export function switchLabels(key: string | undefined): { on: string; off: string
return { on: 'فعال', off: 'غیرفعال' }
}
+export interface SwitchHelpParts {
+ note: string
+ on: string
+ off: string
+}
+
+/**
+ * تفکیک توضیح سوییچ به بخشهای «روشن» و «خاموش» برای نمایش خطبهخط.
+ * از نشانگرهای «روشن:»/«روشن =» و «خاموش:»/«خاموش =» استفاده میکند.
+ * اگر هیچ نشانگری نبود null برمیگرداند تا متن ساده نمایش داده شود.
+ */
+export function parseSwitchHelp(help: string | undefined | null): SwitchHelpParts | null {
+ const text = (help ?? '').trim()
+ if (!text) return null
+
+ const onMatch = /روشن\s*[:=]\s*/.exec(text)
+ const offMatch = /خاموش\s*[:=]\s*/.exec(text)
+ if (!onMatch && !offMatch) return null
+
+ const trim = (s: string) => s.replace(/^[\s،؛.:]+|[\s،؛:]+$/g, '').trim()
+
+ let note = ''
+ let on = ''
+ let off = ''
+
+ if (onMatch && offMatch) {
+ const onIdx = onMatch.index
+ const offIdx = offMatch.index
+ if (onIdx < offIdx) {
+ note = text.slice(0, onIdx)
+ on = text.slice(onIdx + onMatch[0].length, offIdx)
+ off = text.slice(offIdx + offMatch[0].length)
+ } else {
+ note = text.slice(0, offIdx)
+ off = text.slice(offIdx + offMatch[0].length, onIdx)
+ on = text.slice(onIdx + onMatch[0].length)
+ }
+ } else if (onMatch) {
+ note = text.slice(0, onMatch.index)
+ on = text.slice(onMatch.index + onMatch[0].length)
+ } else if (offMatch) {
+ note = text.slice(0, offMatch.index)
+ off = text.slice(offMatch.index + offMatch[0].length)
+ }
+
+ return { note: trim(note), on: trim(on), off: trim(off) }
+}
+
/** توضیحات fallback — وقتی در کاتالوگ تعریف شده، بر description دیتابیس اولویت دارد */
const KEY_HELP_FALLBACK: Record = {
name_fa:
@@ -442,13 +491,13 @@ const KEY_HELP_FALLBACK: Record = {
auto_active_customer:
'روشن: حساب بلافاصله پس از ثبتنام فعال میشود. خاموش: نیاز به تأیید دستی شما در پنل دارد.',
close_price:
- 'فیلتر اصلی فروشگاه. وقتی روشن باشد، خرید و فروش و نمایش قیمت فقط بین «ساعت شروع» و «ساعت پایان» انجام میشود و خارج از این بازه به مشتری «فروشگاه بسته است» نشان داده میشود. خاموش: محدودیت ساعتی سراسری اعمال نمیشود (مگر برای محصولی ساعت اختصاصی تعریف کرده باشید).',
+ 'فیلتر اصلی ساعت کاری فروشگاه. روشن: خرید، فروش و نمایش قیمت فقط بین «ساعت شروع» و «ساعت پایان» انجام میشود و خارج از این بازه به مشتری «فروشگاه بسته است» نشان داده میشود. خاموش: محدودیت ساعتی سراسری اعمال نمیشود (مگر برای محصولی ساعت اختصاصی تعریف کرده باشید).',
startDay:
'ساعت شروع فعالیت روزانه فروشگاه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال میشود. از ساعتشمار انتخاب کنید (مثلاً ۰۹:۰۰).',
endDay:
'ساعت پایان فعالیت روزانه فروشگاه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال میشود. بعد از این ساعت فروشگاه بسته میشود (مثلاً ۱۷:۰۰ یا ۲۲:۰۰).',
show_last_price:
- 'وقتی قیمت زنده در دسترس نباشد: روشن = آخرین قیمت ذخیرهشده نشان داده میشود؛ خاموش = بهجای صفر پیام «مظنه در دسترس نیست» نمایش داده میشود.',
+ 'رفتار هنگام در دسترس نبودن قیمت زنده. روشن: آخرین قیمت ذخیرهشده نشان داده میشود. خاموش: بهجای صفر پیام «مظنه در دسترس نیست» نمایش داده میشود.',
show_gold_ounce_price:
'روشن: اگر آدرس API تابلو (zaarbox_api_url) تنظیم شده باشد، قیمت انس طلا در اپ مشتری نمایش داده میشود. خاموش: نمایش داده نمیشود.',
fee_single:
@@ -482,9 +531,9 @@ const KEY_HELP_FALLBACK: Record = {
gift_for_referral:
'مقدار پاداش (معمولاً وزن فلز) که به معرفشونده واریز میشود. صفر = بدون پاداش. زمان واریز بسته به «پاداش معرف پس از اولین سفارش» است.',
register_referral_sms_to_referrer:
- 'روشن: پس از ثبتنام کاربر جدید، پیامک اطلاعرسانی برای معرف ارسال میشود.',
+ 'روشن: پس از ثبتنام کاربر جدید، پیامک اطلاعرسانی برای معرف ارسال میشود. خاموش: پیامکی برای معرف ارسال نمیشود.',
register_referral_bonus_message:
- 'روشن: متن پاداش معرف در پیامک خوشآمد ثبتنام گنجانده میشود.',
+ 'روشن: متن پاداش معرف در پیامک خوشآمد ثبتنام گنجانده میشود. خاموش: متن پاداش در پیامک خوشآمد نمیآید.',
referral_gift_on_first_order:
'روشن: پاداش معرف (gift_for_referrer) و معرفشونده (gift_for_referral) پس از تکمیل اولین سفارش معرفشونده به کیف واریز میشود — فقط اگر مقدار هر پاداش بیشتر از صفر باشد. خاموش: پاداش بلافاصله پس از ثبتنام موفق یا تأیید احراز هویت واریز میشود (باز هم فقط برای مقادیر بیشتر از صفر).',
referral_limit:
@@ -492,13 +541,13 @@ const KEY_HELP_FALLBACK: Record = {
referral_bonus_amount:
'مبلغ یا مقدار پاداش معرف برای نمایش در پیامکها و متنهای دعوت.',
jibit_verification:
- 'روشن: هنگام ثبت یا ویرایش کارت بانکی، تطبیق با سرویس احراز هویت جیبیت انجام میشود.',
+ 'روشن: هنگام ثبت یا ویرایش کارت بانکی، تطبیق با سرویس احراز هویت جیبیت انجام میشود. خاموش: کارت بدون تطبیق جیبیت ثبت میشود.',
register_jibit_verify:
- 'روشن: در ثبتنام، تطبیق کد ملی و شماره موبایل از طریق جیبیت انجام میشود.',
+ 'روشن: در ثبتنام، تطبیق کد ملی و شماره موبایل از طریق جیبیت انجام میشود. خاموش: ثبتنام بدون تطبیق جیبیت انجام میشود.',
has_page_earn_money:
- 'روشن: منوی «کسب درآمد / دعوت دوستان» در اپ مشتری نمایش داده میشود.',
+ 'روشن: منوی «کسب درآمد / دعوت دوستان» در اپ مشتری نمایش داده میشود. خاموش: این منو در اپ نمایش داده نمیشود.',
invite_link_text:
- 'متن پیام اشتراکگذاری دعوت. میتوانید از ٪نام برای نام کاربر و ٪لینک برای لینک ثبتنام با کد معرف استفاده کنید. خالی = متن پیشفرض اپ.',
+ 'متن پیام اشتراکگذاری دعوت. متن را بنویسید و متغیرها (نام کاربر، لینک ثبتنام) را با کلیک اضافه کنید؛ پیشنمایش نتیجه را با مقادیر نمونه نشان میدهد. خالی = متن پیشفرض اپ.',
montzar_mobile:
'شماره موبایل حساب اتصال به سرویس همکار قیمت.',
montzar_password:
@@ -510,13 +559,13 @@ const KEY_HELP_FALLBACK: Record = {
zaarbox_api_url:
'آدرس سرور(های) فید قیمت (/api/free). تا ۵ آدرس میتوانید اضافه کنید؛ اگر آدرس اول پاسخ ندهد بهترتیب سراغ بعدی میرود. آدرس فعلی در ردیف اول حفظ میشود.',
montzar_open_buy:
- 'روشن: ثبت سفارش خرید از طریق قیمت همکار مجاز است (وضعیت واقعی از سرویس خارجی خوانده میشود).',
+ 'وضعیت واقعی از سرویس خارجی خوانده میشود. روشن: ثبت سفارش خرید از طریق قیمت همکار مجاز است. خاموش: ثبت سفارش خرید از طریق قیمت همکار غیرفعال است.',
montzar_open_sell:
- 'روشن: ثبت سفارش فروش از طریق قیمت همکار مجاز است.',
+ 'وضعیت واقعی از سرویس خارجی خوانده میشود. روشن: ثبت سفارش فروش از طریق قیمت همکار مجاز است. خاموش: ثبت سفارش فروش از طریق قیمت همکار غیرفعال است.',
montzar_closed:
'وضعیت گزارششده از سرویس همکار؛ فقط برای نمایش — این سوییچ را دستی تغییر ندهید.',
external_order_api_enabled:
- 'روشن: دریافت کیف پول و ثبت سفارش از سرویس فروشگاه خارجی فعال است.',
+ 'روشن: دریافت کیف پول و ثبت سفارش از سرویس فروشگاه خارجی فعال است. خاموش: اتصال به سرویس فروشگاه خارجی غیرفعال است.',
node_client_id:
'شناسه کلاینت برای اتصال امن به سرویس سفارش خارجی.',
node_shared_secret:
@@ -532,15 +581,9 @@ const KEY_HELP_FALLBACK: Record = {
support_messenger_id:
'شناسه تماس در پیامرسان انتخابشده: یوزرنیم، آیدی یا لینک کامل. فقط روی آیکون پیامرسان و دکمه شناور اعمال میشود؛ تماس تلفنی از فیلد «شماره پشتیبانی» خوانده میشود.',
support_messenger_text:
- 'متن راهنمای واریز در مودال شارژ حساب. متغیرها: ٪نام یا %نام = نام صاحب حساب؛ ٪کارت یا %کارت = شماره کارت؛ ٪شبا یا %شبا = شبا. فاصله بعد از ٪ اختیاری است. اگر خالی باشد از متن پیشفرض اپ استفاده میشود.',
+ 'متن راهنمای واریز در مودال شارژ حساب. متن را بنویسید و متغیرها (نام صاحب حساب، شماره کارت، شبا) را با کلیک اضافه کنید؛ پیشنمایش نتیجه را با مقادیر نمونه نشان میدهد. خالی = متن پیشفرض اپ.',
deposit_support_message_text:
- `متن پیام هنگام کلیک «پیام به پشتیبانی» در مودال واریز (SMS و پیامرسان).
-
-متغیر:
-• %نام یا ٪نام = نام مشتری واردشده (اگر خالی بود، نام صاحب حساب از تنظیمات)
-
-پیشفرض اپ:
-باسلام، %نام هستم. کدپیگیری و مبلغ واریزی من:`,
+ 'متن پیام هنگام کلیک «پیام به پشتیبانی» در مودال واریز (SMS و پیامرسان). متن را بنویسید و متغیر «نام مشتری» را با کلیک اضافه کنید؛ پیشنمایش نتیجه را نشان میدهد. خالی = متن پیشفرض اپ.',
deposit_account_holder:
'نام صاحب حساب واریز — در متن راهنمای واریز با ٪نام یا %نام جایگزین میشود. در پیام پشتیبانی فقط وقتی نام مشتری موجود نباشد استفاده میشود.',
deposit_card_number:
@@ -550,11 +593,11 @@ const KEY_HELP_FALLBACK: Record = {
deposit_sms_support_number:
'شماره تماس پشتیبانی؛ با کلیک روی «تماس با پشتیبانی» در اپ، تماس تلفنی برقرار میشود. در صفحه واریز، درباره ما و قوانین هم نمایش داده میشود.',
charge_notif:
- 'روشن: پس از شارژ کیف توسط ادمین، پیامک به مشتری ارسال میشود.',
+ 'روشن: پس از شارژ کیف توسط ادمین، پیامک به مشتری ارسال میشود. خاموش: پیامکی پس از شارژ ارسال نمیشود.',
group_sms:
- 'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.',
+ 'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است. خاموش: ارسال پیامک گروهی غیرفعال است.',
profit_gold_api_enabled:
- 'روشن: محاسبه سود طلا از API خارجی فعال است. در اکثر نصبها خاموش میماند.',
+ 'روشن: محاسبه سود طلا از API خارجی فعال است. خاموش: محاسبه سود از API خارجی انجام نمیشود (حالت پیشفرض اکثر نصبها).',
main_wallet_products:
'محصولاتی که موجودیشان از کیف طلای اصلی خوانده میشود. میتوانید چند محصول انتخاب کنید. اگر خالی بماند، پیشفرض محصول ۱۳ است.',
unit:
@@ -572,7 +615,7 @@ const KEY_HELP_FALLBACK: Record = {
zaarbox_price_key:
'شناسه ردیف قیمت در پاسخ سرویس تابلو.',
payping_fin_sync_enabled:
- 'روشن: پس از واریز یا برداشت، تراکنش به سامانه حسابداری مالی همگام میشود.',
+ 'روشن: پس از واریز یا برداشت، تراکنش به سامانه حسابداری مالی همگام میشود. خاموش: تراکنشها به سامانه حسابداری مالی ارسال نمیشوند.',
payping_fin_url:
'آدرس ثبت تراکنش در سامانه حسابداری مالی.',
payping_fin_token:
@@ -590,7 +633,7 @@ const KEY_HELP_FALLBACK: Record = {
payping_round_toman:
'روشن: مبلغ پرداخت به نزدیکترین تومان گرد میشود. خاموش: مبلغ دقیق ریالی.',
payping_strict_failed_withdraw:
- 'روشن: در صورت برداشت ناموفق قبلی، تسویه جدید با احتیاط بیشتر انجام میشود.',
+ 'روشن: در صورت برداشت ناموفق قبلی، تسویه جدید با احتیاط بیشتر انجام میشود. خاموش: تسویه بهصورت عادی و بدون احتیاط اضافه انجام میشود.',
send_to_tahesab:
'تنظیم قدیمی — از «سیستم حسابداری» استفاده کنید.',
accounting_provider:
@@ -641,6 +684,8 @@ const KEY_HELP_FALLBACK: Record = {
'آیدی چنل یا گروه تلگرام که قیمت برایش ارسال میشود. چند مقصد را با ویرگول جدا کنید.',
price_channel_rubika_to:
'آیدی چنل یا گروه روبیکا که قیمت برایش ارسال میشود. چند مقصد را با ویرگول جدا کنید.',
+ price_channel_message_text:
+ 'قالب پیامی که با تغییر قیمت به کانال ارسال میشود. متن را بنویسید و متغیرها را با کلیک اضافه کنید؛ پیشنمایش، پیام نهایی را با مقادیر نمونه نشان میدهد. خالی = قالب پیشفرض.',
rubika_bot_token:
'توکن ربات روبیکا برای ارسال قیمت به کانال. محرمانه نگه دارید.',
change_mesghal:
@@ -701,6 +746,7 @@ export const KEY_LABEL_FALLBACK: Record = {
price_channel_bale_to: 'مقصد ارسال بله',
price_channel_telegram_to: 'مقصد ارسال تلگرام',
price_channel_rubika_to: 'مقصد ارسال روبیکا',
+ price_channel_message_text: 'قالب متن پیام کانال',
rubika_bot_token: 'توکن بات روبیکا',
change_mesghal: 'وزن مثقال (گرم)'
}
diff --git a/src/pages/menuPages/configuration/templateFields.ts b/src/pages/menuPages/configuration/templateFields.ts
new file mode 100644
index 0000000..3819749
--- /dev/null
+++ b/src/pages/menuPages/configuration/templateFields.ts
@@ -0,0 +1,234 @@
+/**
+ * کاتالوگ فیلدهای «قالب متن با متغیر» — همهٔ تنظیماتی که placeholder با % یا ٪ دارند.
+ * هر کلید یک ویرایشگر متن با چیپهای متغیر و پیشنمایش زنده میگیرد.
+ */
+
+export interface TemplateVariableItem {
+ /** توکن بدون % (مثلاً «نام») */
+ token: string
+ /** برچسب کوتاه فارسی روی چیپ */
+ label: string
+ /** توضیح کامل (tooltip) */
+ help: string
+}
+
+export interface TemplateVariableGroup {
+ title: string
+ color: string
+ items: TemplateVariableItem[]
+}
+
+export interface TemplateFieldConfig {
+ /** گروههای متغیر برای پالت چیپها */
+ variableGroups: TemplateVariableGroup[]
+ /** مقدار نمونه هر توکن برای پیشنمایش */
+ sampleValues: Record
+ /** قالب پیشفرض (وقتی فیلد خالی است در پیشنمایش نشان داده میشود) */
+ defaultTemplate: string
+ /** عنوان بالای ویرایشگر */
+ editorTitle: string
+ /** عنوان بالای پیشنمایش */
+ previewTitle: string
+ /** placeholder داخل textarea */
+ placeholder: string
+ /** متن راهنمای زیر textarea */
+ hint: string
+ /** توضیح زیر پیشنمایش */
+ legend: string
+ /** تعداد پیشفرض ردیفهای textarea */
+ rows: number
+}
+
+function toFa(input: string): string {
+ const fa = '۰۱۲۳۴۵۶۷۸۹'
+ return input.replace(/[0-9]/g, (d) => fa[Number(d)])
+}
+
+const PRICE_CHANNEL_DEFAULT = [
+ 'سلام 👋',
+ '%عنوان',
+ '📅 %تاریخ 🕐 %ساعت',
+ '',
+ '🔴 خرید گرم: %گرم_خرید',
+ '🟢 فروش گرم: %گرم_فروش',
+ '🔴 خرید مثقال: %مثقال_خرید',
+ '🟢 فروش مثقال: %مثقال_فروش',
+ '',
+ '📞 %شماره',
+ '%آیدی',
+ '%لینک',
+].join('\n')
+
+const INVITE_DEFAULT = [
+ '⭐️الان طلا بخر؛ آخر ماه سودشو ببر!',
+ 'با خرید طلای آبشده در مقدارهای کم ولی مستمر، سود بیشتری ببر!',
+ '',
+ 'این دعوت نامه از طرف "%نام" عزیز برای شما ارسال شده تا شما هم از بازار طلای ما جا نمونید.',
+ 'روی لینک زیر کلیک کنید و ثبت نام خود را تکمیل کنید.',
+ '',
+ '%لینک',
+].join('\n')
+
+const DEPOSIT_GUIDE_DEFAULT = [
+ 'برای شارژ حساب، مبلغ دلخواه را به کارت زیر واریز کنید:',
+ '',
+ 'صاحب حساب: %نام',
+ 'شماره کارت: %کارت',
+ 'شماره شبا: %شبا',
+ '',
+ 'پس از واریز، از دکمه «پیام به پشتیبانی» فیش را ارسال کنید.',
+].join('\n')
+
+const DEPOSIT_SUPPORT_DEFAULT = 'باسلام، %نام هستم. کدپیگیری و مبلغ واریزی من:'
+
+export const TEMPLATE_FIELD_CONFIGS: Record = {
+ price_channel_message_text: {
+ editorTitle: 'متن قالب پیام',
+ previewTitle: 'پیشنمایش پیام',
+ placeholder: 'متن پیام کانال را بنویسید و متغیرها را از پایین اضافه کنید…',
+ hint: 'روی هر متغیر کلیک کنید تا در محل نشانگر متن اضافه شود. اگر متن را خالی بگذارید، قالب پیشفرض ارسال میشود.',
+ legend:
+ 'مقادیر بالا نمونهاند؛ در پیام واقعی، قیمتها زنده و نام/شماره/آیدی/لینک از تنظیمات همین بخش پر میشوند.',
+ rows: 12,
+ defaultTemplate: PRICE_CHANNEL_DEFAULT,
+ variableGroups: [
+ {
+ title: 'قیمت تابلو',
+ color: '#d4af37',
+ items: [
+ { token: 'گرم_خرید', label: 'خرید هر گرم', help: 'قیمت خرید هر گرم' },
+ { token: 'گرم_فروش', label: 'فروش هر گرم', help: 'قیمت فروش هر گرم (عدد بزرگ صفحهٔ اصلی اپ)' },
+ { token: 'مثقال_خرید', label: 'خرید هر مثقال', help: 'قیمت خرید هر مثقال' },
+ { token: 'مثقال_فروش', label: 'فروش هر مثقال', help: 'قیمت فروش هر مثقال' },
+ ],
+ },
+ {
+ title: 'زمان',
+ color: '#3b82f6',
+ items: [
+ { token: 'تاریخ', label: 'تاریخ شمسی', help: 'تاریخ شمسی امروز' },
+ { token: 'ساعت', label: 'ساعت', help: 'ساعت لحظهٔ ارسال' },
+ ],
+ },
+ {
+ title: 'نام و عنوان',
+ color: '#8b5cf6',
+ items: [
+ { token: 'نام', label: 'نام فروشگاه', help: 'فقط نام فروشگاه (از تنظیم «نام فارسی»)' },
+ { token: 'عنوان', label: 'عنوان آماده', help: 'خط آمادهٔ «📊 قیمت لحظهای + نام فروشگاه»' },
+ ],
+ },
+ {
+ title: 'تماس (از فیلدهای همین بخش)',
+ color: '#10b981',
+ items: [
+ { token: 'شماره', label: 'شماره تماس', help: 'از فیلد «شماره تماس در پیام»' },
+ { token: 'آیدی', label: 'آیدی کانال', help: 'از فیلد «آیدی کانال در پیام»' },
+ { token: 'لینک', label: 'لینک', help: 'از فیلد «لینک در پیام»' },
+ ],
+ },
+ {
+ title: 'اختیاری',
+ color: '#6b7280',
+ items: [
+ { token: 'محصولات', label: 'لیست محصولات', help: 'چند خط قیمت محصولات فعال' },
+ { token: 'فوتر', label: 'فوتر آماده', help: 'بلوک آمادهٔ پایان پیام (آیدی/شماره/لینک)' },
+ ],
+ },
+ ],
+ sampleValues: {
+ گرم_خرید: '۷۱,۲۵۰,۰۰۰',
+ گرم_فروش: '۷۱,۵۰۰,۰۰۰',
+ مثقال_خرید: '۳۰۸,۷۰۰,۰۰۰',
+ مثقال_فروش: '۳۰۹,۸۰۰,۰۰۰',
+ تاریخ: '۱۴۰۴/۴/۲۲',
+ ساعت: toFa('12:30'),
+ نام: 'طلای نمونه',
+ عنوان: '📊 قیمت لحظهای طلای نمونه',
+ شماره: '۰۹۱۲۳۴۵۶۷۸۹',
+ آیدی: '@gold_channel',
+ لینک: 'https://example.com',
+ محصولات: '• سکه تمام: ۹۲۱,۱۰۰,۰۰۰\n• نیم سکه: ۴۷۵,۰۰۰,۰۰۰',
+ فوتر: '📞 ۰۹۱۲۳۴۵۶۷۸۹\n@gold_channel',
+ },
+ },
+
+ invite_link_text: {
+ editorTitle: 'متن پیام دعوت',
+ previewTitle: 'پیشنمایش پیام دعوت',
+ placeholder: 'متن دعوت از دوستان را بنویسید و متغیرها را از پایین اضافه کنید…',
+ hint: 'روی هر متغیر کلیک کنید تا در محل نشانگر اضافه شود. اگر خالی بگذارید، متن پیشفرض اپ استفاده میشود.',
+ legend: 'مقادیر بالا نمونهاند؛ در پیام واقعی «نام» نام کاربر دعوتکننده و «لینک» آدرس ثبتنام با کد معرف اوست.',
+ rows: 10,
+ defaultTemplate: INVITE_DEFAULT,
+ variableGroups: [
+ {
+ title: 'متغیرها',
+ color: '#8b5cf6',
+ items: [
+ { token: 'نام', label: 'نام کاربر', help: 'نام کامل کاربر دعوتکننده' },
+ { token: 'لینک', label: 'لینک ثبتنام', help: 'آدرس کامل ثبتنام همراه کد معرف' },
+ ],
+ },
+ ],
+ sampleValues: {
+ نام: 'علی رضایی',
+ لینک: 'https://gold.example.com/?ref=AB12CD',
+ },
+ },
+
+ support_messenger_text: {
+ editorTitle: 'متن راهنمای واریز',
+ previewTitle: 'پیشنمایش راهنما',
+ placeholder: 'متن راهنمای واریز در مودال شارژ حساب را بنویسید…',
+ hint: 'روی هر متغیر کلیک کنید تا در محل نشانگر اضافه شود. اگر خالی بگذارید، متن پیشفرض اپ استفاده میشود.',
+ legend: 'مقادیر بالا نمونهاند؛ در اپ، نام/کارت/شبا از فیلدهای «نام صاحب حساب واریز»، «شماره کارت واریز» و «شبا واریز» پر میشوند.',
+ rows: 8,
+ defaultTemplate: DEPOSIT_GUIDE_DEFAULT,
+ variableGroups: [
+ {
+ title: 'اطلاعات حساب واریز',
+ color: '#10b981',
+ items: [
+ { token: 'نام', label: 'صاحب حساب', help: 'نام صاحب حساب (از فیلد «نام صاحب حساب واریز»)' },
+ { token: 'کارت', label: 'شماره کارت', help: 'شماره کارت (از فیلد «شماره کارت واریز»)' },
+ { token: 'شبا', label: 'شماره شبا', help: 'شماره شبا (از فیلد «شبا واریز»)' },
+ ],
+ },
+ ],
+ sampleValues: {
+ نام: 'علی رضایی',
+ کارت: '۶۰۳۷-۹۹۷۹-۱۲۳۴-۵۶۷۸',
+ شبا: 'IR۸۲۰۵۴۰۱۰۲۶۸۰۰۲۰۸۱۷۹۰۹۰۰۲',
+ },
+ },
+
+ deposit_support_message_text: {
+ editorTitle: 'متن پیام به پشتیبانی',
+ previewTitle: 'پیشنمایش پیام',
+ placeholder: 'متنی که با کلیک «پیام به پشتیبانی» برای مشتری آماده میشود…',
+ hint: 'روی متغیر کلیک کنید تا در محل نشانگر اضافه شود. اگر خالی بگذارید، متن پیشفرض اپ استفاده میشود.',
+ legend: 'مقدار بالا نمونه است؛ در اپ «نام» نام مشتری واردشده است (و اگر خالی بود، نام صاحب حساب از تنظیمات).',
+ rows: 5,
+ defaultTemplate: DEPOSIT_SUPPORT_DEFAULT,
+ variableGroups: [
+ {
+ title: 'متغیر',
+ color: '#3b82f6',
+ items: [{ token: 'نام', label: 'نام مشتری', help: 'نام مشتری واردشده (یا نام صاحب حساب)' }],
+ },
+ ],
+ sampleValues: {
+ نام: 'علی رضایی',
+ },
+ },
+}
+
+export function hasTemplateField(key: string | undefined | null): boolean {
+ return !!key && Object.prototype.hasOwnProperty.call(TEMPLATE_FIELD_CONFIGS, key)
+}
+
+export function getTemplateFieldConfig(key: string | undefined | null): TemplateFieldConfig | undefined {
+ if (!key) return undefined
+ return TEMPLATE_FIELD_CONFIGS[key]
+}
diff --git a/src/pages/menuPages/orders/components/OrderEditorFormBody.vue b/src/pages/menuPages/orders/components/OrderEditorFormBody.vue
index 2f8ac27..16a7c89 100644
--- a/src/pages/menuPages/orders/components/OrderEditorFormBody.vue
+++ b/src/pages/menuPages/orders/components/OrderEditorFormBody.vue
@@ -58,12 +58,12 @@
@@ -108,6 +108,7 @@ import type { last } from '@/model/price'
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
import { usePrice } from '@core/utils/usePrice'
import { metalLabel } from '@/utils/adminMetalLabels'
+import { getCashBalance, getGoldBalance } from '@/components/mobile/utils/wallet'
const props = defineProps<{
form: Order
@@ -131,6 +132,14 @@ const marketPriceLabel = computed(() => {
return usePrice(price)
})
+const goldBalanceDisplay = computed(
+ () => getGoldBalance(props.selectedCustomer?.wallet) || '_'
+)
+
+const cashBalanceDisplay = computed(
+ () => getCashBalance(props.selectedCustomer?.wallet) || '_'
+)
+
defineExpose({
validateFields: () => formRef.value?.validateFields(),
resetFields: () => formRef.value?.resetFields()
diff --git a/src/pages/menuPages/orders/index.vue b/src/pages/menuPages/orders/index.vue
index 258f632..6694d23 100644
--- a/src/pages/menuPages/orders/index.vue
+++ b/src/pages/menuPages/orders/index.vue
@@ -91,6 +91,7 @@ import { createOrder } from '@/api/order'
import { isMobile } from '@core/utils/is'
import { metalLabel } from '@/utils/adminMetalLabels'
import { formatAmountWithUnit } from '@/utils/weightUnit'
+import { initWalletSettingsFromApi } from '@/utils/config'
//loading
const loading = ref(false)
@@ -118,6 +119,7 @@ async function getCustomers() {
}
onMounted(() => {
+ void initWalletSettingsFromApi()
getCustomers()
})
diff --git a/src/pages/menuPages/products/archive/components/ProductArchiveInfoSheet.vue b/src/pages/menuPages/products/archive/components/ProductArchiveInfoSheet.vue
index f75b2d4..bee1c31 100644
--- a/src/pages/menuPages/products/archive/components/ProductArchiveInfoSheet.vue
+++ b/src/pages/menuPages/products/archive/components/ProductArchiveInfoSheet.vue
@@ -26,7 +26,7 @@
-
+
@@ -70,6 +70,7 @@
import type { Product } from '@/model/product'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import CustomTag from '@/components/CustomTag.vue'
+import { resolveProductTradeTypeLabel } from '@/utils/productCategories'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
diff --git a/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue b/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue
index 3cc5c3f..5aa4f9f 100644
--- a/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue
+++ b/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue
@@ -82,6 +82,7 @@