This commit is contained in:
2026-07-12 22:24:26 +03:30
parent 4b59444e7d
commit 50f6d7f7e5
4 changed files with 229 additions and 25 deletions
@@ -0,0 +1,136 @@
<template>
<div class="partner-urls-field">
<div
v-for="(row, index) in rows"
:key="index"
class="partner-urls-field__row"
>
<span class="partner-urls-field__index">{{ index + 1 }}</span>
<a-input
:value="row.url"
:disabled="disabled"
allow-clear
dir="ltr"
class="partner-urls-field__url"
:placeholder="index === 0 ? 'آدرس اصلی، مثلاً https://price.zaarbox.ir/api/free' : 'آدرس جایگزین (اختیاری)'"
@update:value="(val) => updateRow(index, String(val ?? ''))"
/>
<a-button
type="text"
danger
:disabled="disabled || rows.length <= 1"
aria-label="حذف"
@click="removeRow(index)"
>
حذف
</a-button>
</div>
<a-button
v-if="rows.length < MAX"
type="dashed"
block
:disabled="disabled"
class="partner-urls-field__add"
@click="addRow"
>
افزودن آدرس سرور
</a-button>
<p class="partner-urls-field__hint">
اگر آدرس اول پاسخ ندهد، به‌ترتیب سراغ آدرس‌های بعدی می‌رود. حداکثر {{ MAX }} آدرس.
</p>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import {
formatPartnerServerUrls,
parsePartnerServerUrlsDraft,
PARTNER_SERVER_URLS_MAX,
type PartnerUrlRow,
} from './settingTypes'
const props = withDefaults(
defineProps<{
modelValue: string
disabled?: boolean
}>(),
{ disabled: false },
)
const emit = defineEmits<{ 'update:value': [value: string] }>()
const MAX = PARTNER_SERVER_URLS_MAX
const rows = ref<PartnerUrlRow[]>(parsePartnerServerUrlsDraft(props.modelValue))
let skipExternalSync = false
watch(
() => props.modelValue,
(value) => {
if (skipExternalSync) {
skipExternalSync = false
return
}
rows.value = parsePartnerServerUrlsDraft(value)
},
)
function commit(nextRows: PartnerUrlRow[]) {
rows.value = nextRows
skipExternalSync = true
emit('update:value', formatPartnerServerUrls(nextRows))
}
function updateRow(index: number, url: string) {
const next = rows.value.map((row, i) => (i === index ? { ...row, url } : row))
commit(next)
}
function addRow() {
if (rows.value.length >= MAX) return
commit([...rows.value, { url: '' }])
}
function removeRow(index: number) {
const next = rows.value.filter((_, i) => i !== index)
commit(next.length > 0 ? next : [{ url: '' }])
}
</script>
<style scoped>
.partner-urls-field {
display: flex;
flex-direction: column;
gap: 10px;
}
.partner-urls-field__row {
display: grid;
grid-template-columns: 24px 1fr auto;
gap: 8px;
align-items: center;
}
.partner-urls-field__index {
text-align: center;
font-size: 12px;
color: #6b7280;
}
.partner-urls-field__hint {
margin: 0;
font-size: 12px;
color: #6b7280;
}
@media (max-width: 640px) {
.partner-urls-field__row {
grid-template-columns: 20px 1fr auto;
}
}
</style>
@@ -17,6 +17,13 @@
@update:value="emit('update:value', $event)"
/>
<PartnerServerUrlsField
v-else-if="isPartnerServerUrls"
:model-value="modelValue"
:disabled="disabled"
@update:value="emit('update:value', $event)"
/>
<MainWalletProductsField
v-else-if="isMainWalletProducts"
:model-value="modelValue"
@@ -116,6 +123,7 @@
<script setup lang="ts">
import MainWalletProductsField from './MainWalletProductsField.vue'
import PartnerServerUrlsField from './PartnerServerUrlsField.vue'
import SupportMessengersField from './SupportMessengersField.vue'
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
import {
@@ -154,6 +162,7 @@ const fieldType = computed<SettingFieldType>(() =>
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 selectItems = computed(() =>
parseSelectOptions({
@@ -55,11 +55,11 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ref, watch } from 'vue'
import {
formatSupportMessengers,
parseSelectOptions,
parseSupportMessengers,
parseSupportMessengersDraft,
supportMessengerIdPlaceholder,
type SupportMessengerEntryDraft,
} from './settingTypes'
@@ -74,26 +74,29 @@ const props = withDefaults(
const emit = defineEmits<{ 'update:value': [value: string] }>()
const messengerOptions = computed(() =>
parseSelectOptions({
key: 'support_messenger',
description: 'bale,telegram,whatsapp,rubika,eitaa,soroush',
type: 'select',
}),
)
const rows = computed<SupportMessengerEntryDraft[]>({
get() {
const parsed = parseSupportMessengers(props.modelValue)
if (parsed.length > 0) return parsed
return [{ messenger: 'bale', id: '' }]
},
set(nextRows) {
emit('update:value', formatSupportMessengers(nextRows))
},
const messengerOptions = parseSelectOptions({
key: 'support_messenger',
description: 'bale,telegram,whatsapp,rubika,eitaa,soroush',
type: 'select',
})
const rows = ref<SupportMessengerEntryDraft[]>(parseSupportMessengersDraft(props.modelValue))
let skipExternalSync = false
watch(
() => props.modelValue,
(value) => {
if (skipExternalSync) {
skipExternalSync = false
return
}
rows.value = parseSupportMessengersDraft(value)
},
)
function commit(nextRows: SupportMessengerEntryDraft[]) {
rows.value = nextRows
skipExternalSync = true
emit('update:value', formatSupportMessengers(nextRows))
}
@@ -60,23 +60,36 @@ export function isSupportMessengerType(value: string | undefined | null): boolea
return SUPPORT_MESSENGER_TYPES.includes(type as (typeof SUPPORT_MESSENGER_TYPES)[number])
}
/** پارس setting `support_messengers` — JSON یا legacy تک‌مورد */
/** پارس setting `support_messengers` — JSON یا legacy تک‌مورد (فقط موارد کامل) */
export function parseSupportMessengers(value: string | undefined | null): SupportMessengerEntryDraft[] {
return parseSupportMessengersDraft(value).filter(
(row) => isSupportMessengerType(row.messenger) && row.id !== '',
)
}
/** پارس برای UI — ردیف‌های ناقص (بدون id) هم نگه داشته می‌شوند */
export function parseSupportMessengersDraft(
value: string | undefined | null,
): SupportMessengerEntryDraft[] {
const raw = (value ?? '').trim()
if (!raw) return []
if (!raw) return [{ messenger: 'bale', id: '' }]
try {
const decoded = JSON.parse(raw)
if (!Array.isArray(decoded)) return []
if (!Array.isArray(decoded) || decoded.length === 0) {
return [{ messenger: 'bale', id: '' }]
}
return decoded
const rows = decoded
.map((row) => ({
messenger: String(row?.messenger ?? '').trim().toLowerCase(),
id: String(row?.id ?? '').trim(),
}))
.filter((row) => isSupportMessengerType(row.messenger) && row.id !== '')
.filter((row) => isSupportMessengerType(row.messenger))
return rows.length > 0 ? rows : [{ messenger: 'bale', id: '' }]
} catch {
return []
return [{ messenger: 'bale', id: '' }]
}
}
@@ -92,6 +105,47 @@ export function formatSupportMessengers(entries: SupportMessengerEntryDraft[]):
return JSON.stringify(normalized, null, 0)
}
export type PartnerUrlRow = { url: string }
export const PARTNER_SERVER_URLS_MAX = 5
/** پارس setting `montzar_base_url` برای UI — JSON آرایه، یا مقدار تکی/legacy با کاما/خط جدید */
export function parsePartnerServerUrlsDraft(value: string | undefined | null): PartnerUrlRow[] {
const raw = (value ?? '').trim()
if (!raw) return [{ url: '' }]
let urls: string[] = []
try {
const decoded = JSON.parse(raw)
if (Array.isArray(decoded)) {
urls = decoded.map((item) =>
typeof item === 'string' ? item.trim() : String(item?.url ?? '').trim(),
)
} else {
throw new Error('not-array')
}
} catch {
urls = raw.split(/[\r\n,]+/).map((part) => part.trim())
}
urls = urls.filter((u) => u !== '')
const rows = urls.slice(0, PARTNER_SERVER_URLS_MAX).map((url) => ({ url }))
return rows.length > 0 ? rows : [{ url: '' }]
}
/** خروجی ذخیره: تکی → رشتهٔ ساده (سازگاری قبل)، چندتایی → JSON آرایه */
export function formatPartnerServerUrls(rows: PartnerUrlRow[]): string {
const urls = rows
.map((row) => (row.url ?? '').trim().replace(/\/+$/, ''))
.filter((url) => url !== '')
const unique = [...new Set(urls)].slice(0, PARTNER_SERVER_URLS_MAX)
if (unique.length === 0) return ''
if (unique.length === 1) return unique[0]
return JSON.stringify(unique, null, 0)
}
export function supportMessengerIdPlaceholder(messenger?: string): string {
switch ((messenger ?? '').trim().toLowerCase()) {
case 'whatsapp':
@@ -453,6 +507,8 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
'شناسه محصول مرجع در سرویس همکار قیمت.',
montzar_base_url:
'آدرس پایه سرور سرویس همکار (بدون اسلش انتهایی).',
zaarbox_api_url:
'آدرس سرور(های) فید قیمت (/api/free). تا ۵ آدرس می‌توانید اضافه کنید؛ اگر آدرس اول پاسخ ندهد به‌ترتیب سراغ بعدی می‌رود. آدرس فعلی در ردیف اول حفظ می‌شود.',
montzar_open_buy:
'روشن: ثبت سفارش خرید از طریق قیمت همکار مجاز است (وضعیت واقعی از سرویس خارجی خوانده می‌شود).',
montzar_open_sell: