fix bug 22/4 2
This commit is contained in:
@@ -12,7 +12,18 @@
|
||||
<span class="setting-card__title">{{ title }}</span>
|
||||
</div>
|
||||
<div class="setting-card__key">کلید: {{ settingKey }}</div>
|
||||
<div v-if="help" class="setting-card__help">{{ help }}</div>
|
||||
<div v-if="switchHelp" class="setting-card__help setting-card__help--switch">
|
||||
<div v-if="switchHelp.note" class="setting-card__help-note">{{ switchHelp.note }}</div>
|
||||
<div v-if="switchHelp.on" class="setting-card__help-state">
|
||||
<span class="setting-card__help-badge setting-card__help-badge--on">روشن</span>
|
||||
<span class="setting-card__help-text">{{ switchHelp.on }}</span>
|
||||
</div>
|
||||
<div v-if="switchHelp.off" class="setting-card__help-state">
|
||||
<span class="setting-card__help-badge setting-card__help-badge--off">خاموش</span>
|
||||
<span class="setting-card__help-text">{{ switchHelp.off }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="help" class="setting-card__help">{{ help }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="setting-card__body"
|
||||
@@ -41,6 +52,7 @@
|
||||
import SettingValueField from './SettingValueField.vue'
|
||||
import {
|
||||
helpText,
|
||||
parseSwitchHelp,
|
||||
resolveSettingTitle,
|
||||
resolveSettingFieldType,
|
||||
type SettingFieldType
|
||||
@@ -70,6 +82,10 @@ const fieldType = computed<SettingFieldType>(() =>
|
||||
)
|
||||
|
||||
const help = computed(() => helpText(props.row))
|
||||
|
||||
const switchHelp = computed(() =>
|
||||
fieldType.value === 'switch' ? parseSwitchHelp(help.value) : null
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -136,6 +152,54 @@ const help = computed(() => helpText(props.row))
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
&__help--switch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__help-note {
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
&__help-state {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
&__help-badge {
|
||||
flex-shrink: 0;
|
||||
min-width: 42px;
|
||||
text-align: center;
|
||||
padding: 1px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1.6;
|
||||
|
||||
&--on {
|
||||
color: #047857;
|
||||
background: #d1fae5;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
&--off {
|
||||
color: #b91c1c;
|
||||
background: #fee2e2;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
}
|
||||
|
||||
&__help-text {
|
||||
color: var(--admin-text-secondary);
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
margin-top: 12px;
|
||||
flex: 1;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div
|
||||
class="setting-toggle"
|
||||
:class="{
|
||||
'setting-toggle--on': checked,
|
||||
'setting-toggle--off': !checked,
|
||||
'setting-toggle--disabled': disabled,
|
||||
}"
|
||||
role="switch"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="setting-toggle__seg"
|
||||
:class="{ 'setting-toggle__seg--active': !checked }"
|
||||
:disabled="disabled"
|
||||
@click="setChecked(false)"
|
||||
>
|
||||
<BaseIcon icon="vuesax-linear:close-circle" size="15" />
|
||||
<span>{{ labels.off }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="setting-toggle__seg"
|
||||
:class="{ 'setting-toggle__seg--active': checked }"
|
||||
:disabled="disabled"
|
||||
@click="setChecked(true)"
|
||||
>
|
||||
<BaseIcon icon="vuesax-linear:tick-circle" size="15" />
|
||||
<span>{{ labels.on }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BaseIcon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
checked: boolean
|
||||
labels: { on: string; off: string }
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
{ disabled: false },
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [checked: boolean]
|
||||
}>()
|
||||
|
||||
function setChecked(next: boolean) {
|
||||
if (props.disabled || next === props.checked) return
|
||||
emit('change', next)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting-toggle {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
background: var(--admin-bg-muted, #f1f5f9);
|
||||
border: 1px solid var(--admin-border, #e2e8f0);
|
||||
user-select: none;
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-toggle__seg {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
min-width: 84px;
|
||||
padding: 6px 14px;
|
||||
border: none;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
color: var(--admin-text-secondary, #64748b);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s ease;
|
||||
|
||||
:deep(svg) {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
/* حالت روشن → سگمنت فعال سبز */
|
||||
.setting-toggle--on .setting-toggle__seg--active {
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
|
||||
:deep(svg) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* حالت خاموش → سگمنت فعال خاکستری قرمزفام */
|
||||
.setting-toggle--off .setting-toggle__seg--active {
|
||||
background: #fff;
|
||||
color: #ef4444;
|
||||
|
||||
:deep(svg) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,12 @@
|
||||
<template>
|
||||
<div class="setting-value-field">
|
||||
<a-switch
|
||||
<SettingToggle
|
||||
v-if="fieldType === 'switch'"
|
||||
:checked="switchChecked"
|
||||
:labels="labels"
|
||||
:disabled="disabled"
|
||||
@change="onSwitchChange"
|
||||
>
|
||||
<template #checkedChildren>{{ labels.on }}</template>
|
||||
<template #unCheckedChildren>{{ labels.off }}</template>
|
||||
</a-switch>
|
||||
/>
|
||||
|
||||
<SupportMessengersField
|
||||
v-else-if="isSupportMessengers"
|
||||
@@ -38,6 +36,15 @@
|
||||
@update:value="emit('update:value', $event)"
|
||||
/>
|
||||
|
||||
<TemplateTextField
|
||||
v-else-if="isTemplateField"
|
||||
:model-value="modelValue"
|
||||
:setting-key="settingKey || ''"
|
||||
:disabled="disabled"
|
||||
@update:value="emit('update:value', $event)"
|
||||
@blur="emit('blur')"
|
||||
/>
|
||||
|
||||
<a-select
|
||||
v-else-if="fieldType === 'select'"
|
||||
:value="modelValue || undefined"
|
||||
@@ -124,8 +131,11 @@
|
||||
<script setup lang="ts">
|
||||
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({
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div class="tpl-field">
|
||||
<div class="tpl-field__grid">
|
||||
<!-- ویرایشگر قالب -->
|
||||
<div class="tpl-field__editor">
|
||||
<div class="tpl-field__section-title">
|
||||
<BaseIcon icon="vuesax-linear:edit-2" size="15" />
|
||||
<span>{{ config.editorTitle }}</span>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
class="tpl-field__textarea"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
:rows="config.rows"
|
||||
spellcheck="false"
|
||||
:placeholder="config.placeholder"
|
||||
@input="onInput"
|
||||
@blur="emit('blur')"
|
||||
/>
|
||||
|
||||
<div class="tpl-field__hint">
|
||||
<BaseIcon icon="vuesax-linear:info-circle" size="13" />
|
||||
<span>
|
||||
{{ config.hint }} متغیرها با <code>%</code> یا <code>٪</code> نوشته میشوند.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- پالت متغیرها -->
|
||||
<div class="tpl-field__vars">
|
||||
<div v-for="group in config.variableGroups" :key="group.title" class="tpl-vargroup">
|
||||
<div class="tpl-vargroup__head">
|
||||
<span class="tpl-vargroup__dot" :style="{ background: group.color }" />
|
||||
<span class="tpl-vargroup__title">{{ group.title }}</span>
|
||||
</div>
|
||||
<div class="tpl-vargroup__chips">
|
||||
<button
|
||||
v-for="v in group.items"
|
||||
:key="v.token"
|
||||
type="button"
|
||||
class="tpl-chip"
|
||||
:class="{ 'tpl-chip--disabled': disabled }"
|
||||
:disabled="disabled"
|
||||
:title="v.help"
|
||||
@click="insertToken(v.token)"
|
||||
>
|
||||
<span class="tpl-chip__token">%{{ v.token }}</span>
|
||||
<span class="tpl-chip__label">{{ v.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- پیشنمایش -->
|
||||
<div class="tpl-field__preview">
|
||||
<div class="tpl-field__section-title">
|
||||
<BaseIcon icon="vuesax-linear:eye" size="15" />
|
||||
<span>{{ config.previewTitle }}</span>
|
||||
<span class="tpl-field__preview-tag">نمونه</span>
|
||||
</div>
|
||||
|
||||
<div class="tpl-preview">
|
||||
<div class="tpl-preview__bubble" dir="auto">{{ previewText }}</div>
|
||||
</div>
|
||||
|
||||
<div class="tpl-field__legend">
|
||||
{{ config.legend }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import BaseIcon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { getTemplateFieldConfig, TEMPLATE_FIELD_CONFIGS, type TemplateFieldConfig } from './templateFields'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: string
|
||||
settingKey: string
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
{ disabled: false },
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:value': [value: string]
|
||||
blur: []
|
||||
}>()
|
||||
|
||||
const textareaRef = ref<HTMLTextAreaElement>()
|
||||
|
||||
const config = computed<TemplateFieldConfig>(
|
||||
() => getTemplateFieldConfig(props.settingKey) ?? TEMPLATE_FIELD_CONFIGS.price_channel_message_text,
|
||||
)
|
||||
|
||||
function escapeRegExp(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
const previewText = computed(() => {
|
||||
const cfg = config.value
|
||||
let out = (props.modelValue ?? '').trim() || cfg.defaultTemplate
|
||||
const keys = Object.keys(cfg.sampleValues).sort((a, b) => b.length - a.length)
|
||||
for (const key of keys) {
|
||||
const re = new RegExp('[%٪]\\s*' + escapeRegExp(key), 'gu')
|
||||
out = out.replace(re, cfg.sampleValues[key])
|
||||
}
|
||||
return out.replace(/\n{3,}/g, '\n\n').trim()
|
||||
})
|
||||
|
||||
function onInput(event: Event) {
|
||||
emit('update:value', (event.target as HTMLTextAreaElement).value)
|
||||
}
|
||||
|
||||
function insertToken(token: string) {
|
||||
if (props.disabled) return
|
||||
|
||||
const insert = `%${token}`
|
||||
const current = props.modelValue ?? ''
|
||||
const el = textareaRef.value
|
||||
|
||||
if (!el) {
|
||||
emit('update:value', current ? `${current}${insert}` : insert)
|
||||
return
|
||||
}
|
||||
|
||||
const start = el.selectionStart ?? current.length
|
||||
const end = el.selectionEnd ?? current.length
|
||||
const next = current.slice(0, start) + insert + current.slice(end)
|
||||
emit('update:value', next)
|
||||
|
||||
nextTick(() => {
|
||||
el.focus()
|
||||
const pos = start + insert.length
|
||||
el.setSelectionRange(pos, pos)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tpl-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tpl-field__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
|
||||
@media (min-width: 992px) {
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
}
|
||||
}
|
||||
|
||||
.tpl-field__section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--admin-text);
|
||||
margin-bottom: 8px;
|
||||
|
||||
:deep(svg) {
|
||||
color: #d4af37;
|
||||
}
|
||||
}
|
||||
|
||||
.tpl-field__preview-tag {
|
||||
margin-inline-start: auto;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: #b45309;
|
||||
background: #fff7ed;
|
||||
border: 1px solid #fed7aa;
|
||||
border-radius: 6px;
|
||||
padding: 1px 8px;
|
||||
}
|
||||
|
||||
.tpl-field__textarea {
|
||||
width: 100%;
|
||||
min-height: 160px;
|
||||
resize: vertical;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 10px;
|
||||
background: var(--admin-bg-elevated);
|
||||
color: var(--admin-text);
|
||||
font-size: 13px;
|
||||
line-height: 2;
|
||||
font-family: 'Vazirmatn', 'IRANSans', ui-monospace, monospace;
|
||||
direction: rtl;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #d4af37;
|
||||
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.15);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: var(--admin-bg-muted);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.tpl-field__hint {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
line-height: 1.7;
|
||||
color: var(--admin-text-secondary);
|
||||
|
||||
:deep(svg) {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--admin-bg-muted);
|
||||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tpl-field__vars {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tpl-vargroup__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.tpl-vargroup__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tpl-vargroup__title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.tpl-vargroup__chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tpl-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 8px;
|
||||
background: var(--admin-bg-elevated);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover:not(.tpl-chip--disabled) {
|
||||
border-color: #d4af37;
|
||||
background: #fffaf0;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 6px rgba(212, 175, 55, 0.15);
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__token {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #b45309;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 11px;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.tpl-preview {
|
||||
background: linear-gradient(160deg, #e8f5e9 0%, #eef7ff 100%);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
min-height: 160px;
|
||||
}
|
||||
|
||||
.tpl-preview__bubble {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px 12px 12px 4px;
|
||||
padding: 12px 14px;
|
||||
font-size: 13px;
|
||||
line-height: 2;
|
||||
color: #111827;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.tpl-field__legend {
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
line-height: 1.7;
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
</style>
|
||||
@@ -290,6 +290,7 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
||||
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<string, string> = {
|
||||
name_fa:
|
||||
@@ -442,13 +491,13 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
zaarbox_price_key:
|
||||
'شناسه ردیف قیمت در پاسخ سرویس تابلو.',
|
||||
payping_fin_sync_enabled:
|
||||
'روشن: پس از واریز یا برداشت، تراکنش به سامانه حسابداری مالی همگام میشود.',
|
||||
'روشن: پس از واریز یا برداشت، تراکنش به سامانه حسابداری مالی همگام میشود. خاموش: تراکنشها به سامانه حسابداری مالی ارسال نمیشوند.',
|
||||
payping_fin_url:
|
||||
'آدرس ثبت تراکنش در سامانه حسابداری مالی.',
|
||||
payping_fin_token:
|
||||
@@ -590,7 +633,7 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
payping_round_toman:
|
||||
'روشن: مبلغ پرداخت به نزدیکترین تومان گرد میشود. خاموش: مبلغ دقیق ریالی.',
|
||||
payping_strict_failed_withdraw:
|
||||
'روشن: در صورت برداشت ناموفق قبلی، تسویه جدید با احتیاط بیشتر انجام میشود.',
|
||||
'روشن: در صورت برداشت ناموفق قبلی، تسویه جدید با احتیاط بیشتر انجام میشود. خاموش: تسویه بهصورت عادی و بدون احتیاط اضافه انجام میشود.',
|
||||
send_to_tahesab:
|
||||
'تنظیم قدیمی — از «سیستم حسابداری» استفاده کنید.',
|
||||
accounting_provider:
|
||||
@@ -641,6 +684,8 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
'آیدی چنل یا گروه تلگرام که قیمت برایش ارسال میشود. چند مقصد را با ویرگول جدا کنید.',
|
||||
price_channel_rubika_to:
|
||||
'آیدی چنل یا گروه روبیکا که قیمت برایش ارسال میشود. چند مقصد را با ویرگول جدا کنید.',
|
||||
price_channel_message_text:
|
||||
'قالب پیامی که با تغییر قیمت به کانال ارسال میشود. متن را بنویسید و متغیرها را با کلیک اضافه کنید؛ پیشنمایش، پیام نهایی را با مقادیر نمونه نشان میدهد. خالی = قالب پیشفرض.',
|
||||
rubika_bot_token:
|
||||
'توکن ربات روبیکا برای ارسال قیمت به کانال. محرمانه نگه دارید.',
|
||||
change_mesghal:
|
||||
@@ -701,6 +746,7 @@ export const KEY_LABEL_FALLBACK: Record<string, string> = {
|
||||
price_channel_bale_to: 'مقصد ارسال بله',
|
||||
price_channel_telegram_to: 'مقصد ارسال تلگرام',
|
||||
price_channel_rubika_to: 'مقصد ارسال روبیکا',
|
||||
price_channel_message_text: 'قالب متن پیام کانال',
|
||||
rubika_bot_token: 'توکن بات روبیکا',
|
||||
change_mesghal: 'وزن مثقال (گرم)'
|
||||
}
|
||||
|
||||
@@ -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<string, string>
|
||||
/** قالب پیشفرض (وقتی فیلد خالی است در پیشنمایش نشان داده میشود) */
|
||||
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<string, TemplateFieldConfig> = {
|
||||
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]
|
||||
}
|
||||
Reference in New Issue
Block a user