fixsome changes
This commit is contained in:
@@ -63,7 +63,35 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-row :gutter="[16, 16]" class="settings-grid">
|
||||
<template v-if="group.subsections?.length">
|
||||
<div
|
||||
v-for="block in subsectionBlocks(group)"
|
||||
:key="block.id"
|
||||
class="settings-subsection"
|
||||
>
|
||||
<h3 class="settings-subsection__title">{{ block.title }}</h3>
|
||||
<a-row :gutter="[16, 16]" class="settings-grid">
|
||||
<a-col
|
||||
v-for="row in block.rows"
|
||||
:key="row.id"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:lg="rowColSpan(row)"
|
||||
>
|
||||
<SettingCard
|
||||
:row="row"
|
||||
:model-value="draftValue(row)"
|
||||
:readonly="isReadonly(row)"
|
||||
:saving="isSaving(row.id)"
|
||||
:saved-flash="isSavedFlash(row.id)"
|
||||
@update:value="onValueChange(row, $event)"
|
||||
@blur="onFieldBlur(row)"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<a-row v-else :gutter="[16, 16]" class="settings-grid">
|
||||
<a-col
|
||||
v-for="row in groupedFiltered[group.id]"
|
||||
:key="row.id"
|
||||
@@ -178,7 +206,8 @@ import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyC
|
||||
import {
|
||||
EDITABLE_SETTING_GROUPS,
|
||||
SYSTEM_SETTING_GROUPS,
|
||||
getSettingGroupId
|
||||
getSettingGroupId,
|
||||
type SettingGroupDef
|
||||
} from './settingGroups'
|
||||
import {
|
||||
SETTING_TYPE_OPTIONS,
|
||||
@@ -318,6 +347,32 @@ const visibleSettingGroups = computed(() =>
|
||||
settingGroups.value.filter((g) => (groupedFiltered.value[g.id]?.length ?? 0) > 0)
|
||||
)
|
||||
|
||||
function subsectionBlocks(group: SettingGroupDef) {
|
||||
const rows = groupedFiltered.value[group.id] ?? []
|
||||
if (!group.subsections?.length) {
|
||||
return [{ id: 'all', title: group.title, rows }]
|
||||
}
|
||||
|
||||
const used = new Set<number>()
|
||||
const blocks = group.subsections
|
||||
.map((sub) => {
|
||||
const subRows = rows.filter((row) => {
|
||||
if (row.id == null || used.has(row.id)) return false
|
||||
if (!sub.match(row.key || '')) return false
|
||||
used.add(row.id)
|
||||
return true
|
||||
})
|
||||
return { id: sub.id, title: sub.title, rows: subRows }
|
||||
})
|
||||
.filter((b) => b.rows.length > 0)
|
||||
|
||||
const leftovers = rows.filter((row) => row.id != null && !used.has(row.id))
|
||||
if (leftovers.length) {
|
||||
blocks.push({ id: `${group.id}-other`, title: 'سایر', rows: leftovers })
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
const defaultOpenGroupId = computed(() => {
|
||||
const visibleIds = visibleSettingGroups.value.map((g) => g.id)
|
||||
const preferred = isSystemMode.value
|
||||
@@ -740,6 +795,21 @@ async function ok() {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settings-subsection {
|
||||
& + & {
|
||||
margin-top: 1.25rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--admin-border);
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--admin-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-extra) {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
export type SettingSubsectionDef = {
|
||||
id: string
|
||||
title: string
|
||||
match: (key: string) => boolean
|
||||
}
|
||||
|
||||
export type SettingGroupDef = {
|
||||
id: string
|
||||
title: string
|
||||
icon: string
|
||||
match: (key: string) => boolean
|
||||
/** زیربخشهای داخل گروه (مثلاً روشهای واریز) */
|
||||
subsections?: SettingSubsectionDef[]
|
||||
}
|
||||
|
||||
/** گروههای صفحه تنظیمات عادی (فقط can_edit=1) */
|
||||
@@ -81,6 +89,44 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [
|
||||
icon: 'vuesax-linear:shop',
|
||||
match: (k) => /^external_order/.test(k)
|
||||
},
|
||||
{
|
||||
id: 'deposit_methods',
|
||||
title: 'روشهای واریز',
|
||||
icon: 'vuesax-linear:card-send',
|
||||
match: (k) =>
|
||||
k.startsWith('deposit_method_')
|
||||
|| k === 'deposit_account_holder'
|
||||
|| k === 'deposit_card_number'
|
||||
|| k === 'deposit_account_number'
|
||||
|| k === 'deposit_sheba',
|
||||
subsections: [
|
||||
{
|
||||
id: 'card_to_card',
|
||||
title: 'کارت به کارت',
|
||||
match: (k) =>
|
||||
k === 'deposit_method_card_to_card'
|
||||
|| k === 'deposit_account_holder'
|
||||
|| k === 'deposit_card_number'
|
||||
|| k === 'deposit_account_number'
|
||||
|| k === 'deposit_sheba',
|
||||
},
|
||||
{
|
||||
id: 'gateway',
|
||||
title: 'درگاه پرداخت',
|
||||
match: (k) => k === 'deposit_method_gateway',
|
||||
},
|
||||
{
|
||||
id: 'identified',
|
||||
title: 'شناسه واریز',
|
||||
match: (k) => k === 'deposit_method_identified',
|
||||
},
|
||||
{
|
||||
id: 'shetab',
|
||||
title: 'شبکه شتابی',
|
||||
match: (k) => k === 'deposit_method_shetab',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
title: 'پشتیبانی',
|
||||
@@ -89,7 +135,7 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [
|
||||
k === 'support_messengers'
|
||||
|| k.startsWith('support_messenger_')
|
||||
|| k === 'deposit_sms_support_number'
|
||||
|| (k.startsWith('deposit_') && !k.startsWith('external_')),
|
||||
|| k === 'deposit_support_message_text',
|
||||
},
|
||||
{
|
||||
id: 'bots',
|
||||
@@ -181,12 +227,21 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
|
||||
match: (k) => /^node_/.test(k) || k === 'montify_name' || k === 'external_order_api_enabled'
|
||||
},
|
||||
{
|
||||
id: 'gateway',
|
||||
title: 'درگاه شارژ آنلاین',
|
||||
icon: 'vuesax-linear:card-pos',
|
||||
id: 'deposit_methods',
|
||||
title: 'روشهای واریز',
|
||||
icon: 'vuesax-linear:card-send',
|
||||
match: (k) =>
|
||||
k.startsWith('external_payment_')
|
||||
|| k === 'wallet_payment_provider'
|
||||
|| k === 'wallet_payment_provider',
|
||||
subsections: [
|
||||
{
|
||||
id: 'gateway',
|
||||
title: 'درگاه فروشگاه / پرداخت آنلاین',
|
||||
match: (k) =>
|
||||
k.startsWith('external_payment_')
|
||||
|| k === 'wallet_payment_provider',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'payping',
|
||||
@@ -245,3 +300,14 @@ export function getSettingGroupId(key: string, groups: SettingGroupDef[]): strin
|
||||
}
|
||||
return 'misc'
|
||||
}
|
||||
|
||||
export function getSettingSubsectionId(
|
||||
key: string,
|
||||
group: SettingGroupDef | undefined
|
||||
): string | null {
|
||||
if (!group?.subsections?.length) return null
|
||||
for (const sub of group.subsections) {
|
||||
if (sub.match(key)) return sub.id
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -368,6 +368,10 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
||||
deposit_card_number: 'text',
|
||||
deposit_account_number: 'text',
|
||||
deposit_sheba: 'text',
|
||||
deposit_method_card_to_card: 'switch',
|
||||
deposit_method_gateway: 'switch',
|
||||
deposit_method_identified: 'switch',
|
||||
deposit_method_shetab: 'switch',
|
||||
invite_link_text: 'longtext',
|
||||
support_messenger: 'select',
|
||||
deposit_sms_support_number: 'number',
|
||||
@@ -775,6 +779,14 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
'شماره حساب بانکی واریز — در مودال راهنمای شارژ کنار کارت و شبا نمایش داده میشود. در متنها با ٪حساب یا %حساب جایگزین میشود.',
|
||||
deposit_sheba:
|
||||
'شماره شبا واریز — در متنها با ٪شبا یا %شبا جایگزین میشود.',
|
||||
deposit_method_card_to_card:
|
||||
'روشن (پیشفرض): روش کارت به کارت در صفحه واریز اپ نمایش داده میشود. خاموش: این روش از لیست روشهای واریز حذف میشود.',
|
||||
deposit_method_gateway:
|
||||
'روشن: روش درگاه پرداخت در صفحه واریز نمایش داده میشود. خاموش: مخفی است. اتصال و کدهای درگاه در تنظیمات پیشرفته → روشهای واریز تنظیم میشوند.',
|
||||
deposit_method_identified:
|
||||
'روشن: روش شناسه واریز در صفحه واریز نمایش داده میشود. خاموش: مخفی است.',
|
||||
deposit_method_shetab:
|
||||
'روشن: روش شبکه شتابی در صفحه واریز نمایش داده میشود. خاموش: مخفی است.',
|
||||
deposit_sms_support_number:
|
||||
'شماره تماس پشتیبانی برای راهنمای واریز و دکمه پیامک داخل مودال شارژ. برای نمایش در منوی شناور، باید «تماس تلفنی» یا «پیامک» را جداگانه در لیست پیامرسانهای پشتیبانی اضافه کنید.',
|
||||
charge_notif:
|
||||
@@ -1030,6 +1042,10 @@ export const KEY_LABEL_FALLBACK: Record<string, string> = {
|
||||
deposit_card_number: 'شماره کارت واریز',
|
||||
deposit_account_number: 'شماره حساب واریز',
|
||||
deposit_sheba: 'شبا واریز',
|
||||
deposit_method_card_to_card: 'فعالسازی کارت به کارت',
|
||||
deposit_method_gateway: 'فعالسازی درگاه پرداخت',
|
||||
deposit_method_identified: 'فعالسازی شناسه واریز',
|
||||
deposit_method_shetab: 'فعالسازی شبکه شتابی',
|
||||
deposit_sms_support_number: 'شماره تماس پشتیبانی',
|
||||
price_channel_enabled: 'ارسال قیمت به کانال',
|
||||
price_channel_access: 'دسترسی ارسال قیمت به کانال',
|
||||
@@ -1119,6 +1135,7 @@ export const SETTING_EDITABLE_KEYS = new Set([
|
||||
'sms_notif_price_trigger', 'sms_notif_wallet_alert', 'sms_notif_support_admin',
|
||||
'deposit_sms_support_number', 'deposit_support_message_text',
|
||||
'deposit_account_holder', 'deposit_card_number', 'deposit_account_number', 'deposit_sheba',
|
||||
'deposit_method_card_to_card', 'deposit_method_gateway', 'deposit_method_identified', 'deposit_method_shetab',
|
||||
'endDay',
|
||||
'fee_free', 'fee_service_charge_percentage',
|
||||
'gift_for_referral', 'gift_for_referrer',
|
||||
@@ -1138,6 +1155,7 @@ export const SETTING_EDITABLE_KEYS = new Set([
|
||||
'support_messengers', 'support_messenger_text',
|
||||
'has_chat',
|
||||
'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_account_number', 'deposit_sheba',
|
||||
'deposit_method_card_to_card', 'deposit_method_gateway', 'deposit_method_identified', 'deposit_method_shetab',
|
||||
'text_about', 'text_rule',
|
||||
'zaarbox_price_key',
|
||||
'price_channel_enabled', 'price_channel_messengers', 'price_channel_diff',
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 rounded-lg border border-solid border-amber-200 bg-amber-50 px-3 py-2">
|
||||
<div class="formula-price-section mb-4 flex flex-col gap-2 rounded-lg border border-solid px-3 py-2">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<DashboardLabelWithHelp label="قیمت فرمولی" :help="formulaHelp" size="sm" />
|
||||
<DashboardLabelWithHelp
|
||||
label="قیمت فرمولی"
|
||||
:help="formulaHelp"
|
||||
size="sm"
|
||||
text-class="formula-price-section__label"
|
||||
/>
|
||||
</div>
|
||||
<a-switch
|
||||
:checked="!!modelValue.price_formula_enabled"
|
||||
@@ -15,7 +20,7 @@
|
||||
<template v-if="modelValue.price_formula_enabled">
|
||||
<a-form-item class="!mb-0" name="price_formula">
|
||||
<template #label>
|
||||
<span class="text-sm">فرمول قیمت محصول</span>
|
||||
<span class="formula-price-section__label text-sm">فرمول قیمت محصول</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="localFormula"
|
||||
@@ -29,9 +34,9 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<div class="flex items-center justify-between gap-2 rounded-md bg-white px-2 py-1 text-sm">
|
||||
<span class="text-gray-600">قیمت محصول (محاسبهشده)</span>
|
||||
<span class="font-bold text-amber-800">{{ usePrice(computedProductPrice) }}</span>
|
||||
<div class="formula-price-section__result flex items-center justify-between gap-2 rounded-md px-2 py-1 text-sm">
|
||||
<span class="formula-price-section__result-label">قیمت محصول (محاسبهشده)</span>
|
||||
<span class="formula-price-section__result-value font-bold">{{ usePrice(computedProductPrice) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -126,9 +131,55 @@ function onToggle(checked: boolean) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.formula-price-section {
|
||||
border-color: #fde68a;
|
||||
background: #fffbeb;
|
||||
color: #78350f;
|
||||
}
|
||||
|
||||
.formula-price-section__label {
|
||||
color: #78350f;
|
||||
}
|
||||
|
||||
.formula-price-section__result {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.formula-price-section__result-label {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.formula-price-section__result-value {
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.formula-price-input :deep(input) {
|
||||
direction: ltr !important;
|
||||
text-align: left !important;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
html.is-dark .formula-price-section {
|
||||
border-color: rgba(180, 83, 9, 0.55);
|
||||
background: rgba(120, 53, 15, 0.45);
|
||||
color: #fde68a;
|
||||
}
|
||||
|
||||
html.is-dark .formula-price-section__label {
|
||||
color: #fde68a;
|
||||
}
|
||||
|
||||
html.is-dark .formula-price-section__result {
|
||||
background: rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
html.is-dark .formula-price-section__result-label {
|
||||
color: rgba(253, 230, 138, 0.85);
|
||||
}
|
||||
|
||||
html.is-dark .formula-price-section__result-value {
|
||||
color: #fbbf24;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,18 +41,47 @@
|
||||
<template #label>
|
||||
<DashboardLabelWithHelp label="مرجع قیمت" :help="priceReferenceHelp" size="sm" />
|
||||
</template>
|
||||
<BasicSelect
|
||||
class="price-reference-select"
|
||||
allowClear
|
||||
:hasTooltip="false"
|
||||
popup-class-name="price-reference-select-dropdown"
|
||||
:dropdown-match-select-width="false"
|
||||
:dropdown-style="{ minWidth: '28rem' }"
|
||||
:items="pricesList"
|
||||
valueName="name"
|
||||
keyName="id"
|
||||
v-model:value="form.reading_method"
|
||||
/>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="flex flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-3">
|
||||
<BasicSelect
|
||||
class="price-reference-select min-w-0 flex-1"
|
||||
allowClear
|
||||
:hasTooltip="false"
|
||||
popup-class-name="price-reference-select-dropdown"
|
||||
:dropdown-match-select-width="false"
|
||||
:dropdown-style="{ minWidth: '28rem' }"
|
||||
:items="pricesList"
|
||||
valueName="name"
|
||||
keyName="id"
|
||||
v-model:value="form.reading_method"
|
||||
/>
|
||||
<span
|
||||
v-if="selectedReferenceTotal > 0"
|
||||
class="price-reference-live shrink-0 text-sm font-700"
|
||||
>
|
||||
{{ usePrice(selectedReferenceTotal) }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="isOrphanReference"
|
||||
class="price-reference-live-empty shrink-0 text-sm"
|
||||
>
|
||||
در لیست فعلی نیست
|
||||
</span>
|
||||
<span
|
||||
v-else-if="form.reading_method"
|
||||
class="price-reference-live-empty shrink-0 text-sm"
|
||||
>
|
||||
قیمت مرجع در دسترس نیست
|
||||
</span>
|
||||
</div>
|
||||
<a-alert
|
||||
v-if="isOrphanReference"
|
||||
type="warning"
|
||||
show-icon
|
||||
class="!mb-0"
|
||||
message="این مرجع قبلاً روی محصول ذخیره شده، ولی الان در لیست قیمت همکار نیست. یک مرجع معتبر انتخاب کنید یا فیلد را پاک کنید."
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item name="price_offset_percent" class="!mb-0">
|
||||
<template #label>
|
||||
@@ -115,18 +144,47 @@
|
||||
<template #label>
|
||||
<DashboardLabelWithHelp label="مرجع قیمت" :help="priceReferenceHelp" size="sm" />
|
||||
</template>
|
||||
<BasicSelect
|
||||
class="price-reference-select"
|
||||
allowClear
|
||||
:hasTooltip="false"
|
||||
popup-class-name="price-reference-select-dropdown"
|
||||
:dropdown-match-select-width="false"
|
||||
:dropdown-style="{ minWidth: '28rem' }"
|
||||
:items="pricesList"
|
||||
valueName="name"
|
||||
keyName="id"
|
||||
v-model:value="form.reading_method"
|
||||
/>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="flex flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-3">
|
||||
<BasicSelect
|
||||
class="price-reference-select min-w-0 flex-1"
|
||||
allowClear
|
||||
:hasTooltip="false"
|
||||
popup-class-name="price-reference-select-dropdown"
|
||||
:dropdown-match-select-width="false"
|
||||
:dropdown-style="{ minWidth: '28rem' }"
|
||||
:items="pricesList"
|
||||
valueName="name"
|
||||
keyName="id"
|
||||
v-model:value="form.reading_method"
|
||||
/>
|
||||
<span
|
||||
v-if="selectedReferenceTotal > 0"
|
||||
class="price-reference-live shrink-0 text-sm font-700"
|
||||
>
|
||||
{{ usePrice(selectedReferenceTotal) }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="isOrphanReference"
|
||||
class="price-reference-live-empty shrink-0 text-sm"
|
||||
>
|
||||
در لیست فعلی نیست
|
||||
</span>
|
||||
<span
|
||||
v-else-if="form.reading_method"
|
||||
class="price-reference-live-empty shrink-0 text-sm"
|
||||
>
|
||||
قیمت مرجع در دسترس نیست
|
||||
</span>
|
||||
</div>
|
||||
<a-alert
|
||||
v-if="isOrphanReference"
|
||||
type="warning"
|
||||
show-icon
|
||||
class="!mb-0"
|
||||
message="این مرجع قبلاً روی محصول ذخیره شده، ولی الان در لیست قیمت همکار نیست. یک مرجع معتبر انتخاب کنید یا فیلد را پاک کنید."
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!form.price_formula_enabled" name="price_offset_percent" class="!mb-0">
|
||||
<template #label>
|
||||
@@ -170,19 +228,30 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-solid border-blue-200 bg-blue-50 px-3 py-2">
|
||||
<div class="live-price-preview rounded-lg border border-solid px-3 py-2">
|
||||
<div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between md:gap-3">
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 md:min-w-0 md:flex-1">
|
||||
<DashboardLabelWithHelp label="قیمت لحظهای" :help="livePriceHelp" size="sm" />
|
||||
<span v-if="livePricePreview.status === 'ready'" class="text-base font-700 text-blue-700">
|
||||
<DashboardLabelWithHelp
|
||||
label="قیمت لحظهای"
|
||||
:help="livePriceHelp"
|
||||
size="sm"
|
||||
text-class="live-price-preview__label"
|
||||
/>
|
||||
<span
|
||||
v-if="livePricePreview.status === 'ready'"
|
||||
class="live-price-preview__amount text-base font-700"
|
||||
>
|
||||
{{ usePrice(livePricePreview.amount) }}
|
||||
</span>
|
||||
<span v-else-if="livePricePreview.message" class="text-sm text-gray-500">
|
||||
<span
|
||||
v-else-if="livePricePreview.message"
|
||||
class="live-price-preview__message text-sm"
|
||||
>
|
||||
{{ livePricePreview.message }}
|
||||
</span>
|
||||
<span
|
||||
v-if="livePricePreview.status === 'ready'"
|
||||
class="truncate text-xs text-gray-500"
|
||||
class="live-price-preview__source truncate text-xs"
|
||||
>
|
||||
مبنا: {{ livePricePreview.source }}
|
||||
<span v-if="!form.price_formula_enabled && Number(form.price_offset_percent) !== 0">
|
||||
@@ -508,7 +577,7 @@ import type { last } from '@/model/price'
|
||||
import type { MethodePrices } from '@/model/product'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { isMeltedTradeGroup, resolveProductTradeTypeLabel, usesMeltedPriceEditor } from '@/utils/productCategories'
|
||||
import { computeProductEditorPricePreview, findMethodPriceEntry, normalizeReferenceBase } from '@/utils/productEditorPrice'
|
||||
import { computeProductEditorPricePreview, findMethodPriceEntry, normalizeReferenceBase, referenceProviderTotal, isOrphanReadingMethod } from '@/utils/productEditorPrice'
|
||||
import { resolveTradeWeightUnit, weightFieldLabelForUnit } from '@/utils/weightUnit'
|
||||
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
|
||||
import DashboardLabelWithHelp from '@/pages/dashboard/components/DashboardLabelWithHelp.vue'
|
||||
@@ -700,8 +769,20 @@ const livePricePreview = computed(() =>
|
||||
})
|
||||
)
|
||||
|
||||
const selectedReferenceEntry = computed(() =>
|
||||
findMethodPriceEntry(props.pricesData, props.form.reading_method)
|
||||
)
|
||||
|
||||
const selectedReferenceTotal = computed(() =>
|
||||
referenceProviderTotal(selectedReferenceEntry.value)
|
||||
)
|
||||
|
||||
const isOrphanReference = computed(() =>
|
||||
isOrphanReadingMethod(props.form.reading_method, props.pricesData)
|
||||
)
|
||||
|
||||
const formulaReferenceMiddlePrice = computed(() => {
|
||||
const entry = findMethodPriceEntry(props.pricesData, props.form.reading_method)
|
||||
const entry = selectedReferenceEntry.value
|
||||
if (!entry) {
|
||||
return 0
|
||||
}
|
||||
@@ -942,4 +1023,58 @@ defineExpose({
|
||||
.product-editor-form :deep(.product-cash-fee-field .ant-input-group .ant-input) {
|
||||
padding-block: 0.5rem;
|
||||
}
|
||||
|
||||
.live-price-preview {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.live-price-preview__label {
|
||||
color: #1e3a8a;
|
||||
}
|
||||
|
||||
.live-price-preview__amount {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.live-price-preview__message,
|
||||
.live-price-preview__source {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.price-reference-live {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.price-reference-live-empty {
|
||||
color: #6b7280;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
html.is-dark .live-price-preview {
|
||||
border-color: rgba(29, 78, 216, 0.55);
|
||||
background: rgba(23, 37, 84, 0.55);
|
||||
}
|
||||
|
||||
html.is-dark .live-price-preview__label {
|
||||
color: #bfdbfe;
|
||||
}
|
||||
|
||||
html.is-dark .live-price-preview__amount {
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
html.is-dark .live-price-preview__message,
|
||||
html.is-dark .live-price-preview__source {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
html.is-dark .price-reference-live {
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
html.is-dark .price-reference-live-empty {
|
||||
color: #d1d5db;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -76,7 +76,7 @@ import ProductEditorFormBody from '@/pages/menuPages/products/base/components/Pr
|
||||
import { validateProductSubmitInput } from '@/pages/menuPages/products/base/productFormValidation'
|
||||
import tbl from './components/table.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { referenceProviderTotal } from '@/utils/productEditorPrice'
|
||||
import { referenceProviderTotal, resolveReadingMethodForSelect, canonicalMethodPriceId, isOrphanReadingMethod } from '@/utils/productEditorPrice'
|
||||
import {
|
||||
applyFileToUploadList,
|
||||
normalizeFileResource,
|
||||
@@ -151,16 +151,37 @@ const pricesData = ref<MethodePrices[]>([])
|
||||
const appLastPrice = ref<(last & { price?: number }) | null>(null)
|
||||
let pricePollTimer: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
const pricesList = computed(() =>
|
||||
pricesData.value.map((x) => ({
|
||||
id: `${x.key ?? x.id ?? x.name}`,
|
||||
const pricesList = computed(() => {
|
||||
const list = pricesData.value.map((x) => ({
|
||||
id: canonicalMethodPriceId(x),
|
||||
name: `${x.name} (${usePrice(referenceProviderTotal(x))})`,
|
||||
})),
|
||||
)
|
||||
}))
|
||||
|
||||
const raw = String(form.value.reading_method ?? '').trim()
|
||||
// مقدار یتیم از DB: Select آن را نشان میدهد حتی اگر در options نباشد — صریح علامت بزن
|
||||
if (raw && isOrphanReadingMethod(raw, pricesData.value) && !list.some((item) => item.id === raw)) {
|
||||
list.unshift({
|
||||
id: raw,
|
||||
name: `${raw} — در لیست فعلی همکار نیست`,
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
})
|
||||
|
||||
function syncReadingMethodToSelectValue() {
|
||||
if (!form.value.reading_method || !pricesData.value.length) return
|
||||
if (isOrphanReadingMethod(form.value.reading_method, pricesData.value)) return
|
||||
const resolved = resolveReadingMethodForSelect(form.value.reading_method, pricesData.value)
|
||||
if (resolved && resolved !== form.value.reading_method) {
|
||||
form.value.reading_method = resolved
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPrices() {
|
||||
try {
|
||||
pricesData.value = await methodePrices()
|
||||
syncReadingMethodToSelectValue()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
@@ -364,6 +385,7 @@ async function edit(record: Product) {
|
||||
auto_open_at: start.isValid() ? start : dayjs('00:00:00', 'HH:mm:ss'),
|
||||
auto_close_at: close.isValid() ? close : dayjs('00:00:00', 'HH:mm:ss')
|
||||
})
|
||||
syncReadingMethodToSelectValue()
|
||||
|
||||
await setProductIndexImage(productData, record.id)
|
||||
} catch (err) {
|
||||
|
||||
@@ -12,12 +12,45 @@ export function findMethodPriceEntry(
|
||||
): MethodePrices | undefined {
|
||||
if (!readingMethod) return undefined
|
||||
|
||||
return prices.find(
|
||||
(entry) =>
|
||||
entry.key === readingMethod ||
|
||||
entry.id === readingMethod ||
|
||||
entry.name === readingMethod
|
||||
)
|
||||
const needle = String(readingMethod).trim()
|
||||
if (!needle) return undefined
|
||||
|
||||
return prices.find((entry) => {
|
||||
const key = String(entry.key ?? '').trim()
|
||||
const id = String(entry.id ?? '').trim()
|
||||
const name = String(entry.name ?? '').trim()
|
||||
return key === needle || id === needle || name === needle
|
||||
})
|
||||
}
|
||||
|
||||
/** مقدار value سلکت مرجع — key خالی را با ?? نگه نمیدارد */
|
||||
export function canonicalMethodPriceId(entry: MethodePrices): string {
|
||||
return String(entry.key || entry.id || entry.name || '').trim()
|
||||
}
|
||||
|
||||
/** reading_method ذخیرهشده (name/key/id) را به id سلکت همتراز میکند */
|
||||
export function resolveReadingMethodForSelect(
|
||||
raw: string | null | undefined,
|
||||
prices: MethodePrices[]
|
||||
): string | null | undefined {
|
||||
if (raw == null) return raw
|
||||
const trimmed = String(raw).trim()
|
||||
if (!trimmed) return raw
|
||||
|
||||
const entry = findMethodPriceEntry(prices, trimmed)
|
||||
if (!entry) return trimmed
|
||||
|
||||
return canonicalMethodPriceId(entry) || trimmed
|
||||
}
|
||||
|
||||
/** مرجع روی محصول هست ولی در فید/لیست فعلی همکار پیدا نمیشود */
|
||||
export function isOrphanReadingMethod(
|
||||
raw: string | null | undefined,
|
||||
prices: MethodePrices[]
|
||||
): boolean {
|
||||
const trimmed = String(raw ?? '').trim()
|
||||
if (!trimmed || !prices.length) return false
|
||||
return !findMethodPriceEntry(prices, trimmed)
|
||||
}
|
||||
|
||||
export function normalizeReferenceBase(entry: MethodePrices): { base: number; profit: number } {
|
||||
@@ -117,7 +150,12 @@ export function computeProductEditorPricePreview(input: {
|
||||
}
|
||||
const entry = findMethodPriceEntry(input.pricesData, input.form.reading_method)
|
||||
if (!entry) {
|
||||
return { status: 'empty', message: '' }
|
||||
return {
|
||||
status: 'empty',
|
||||
message: input.pricesData.length
|
||||
? 'مرجع در لیست قیمت یافت نشد'
|
||||
: 'لیست قیمت مرجع خالی است — بروزرسانی را بزنید',
|
||||
}
|
||||
}
|
||||
const { base } = normalizeReferenceBase(entry)
|
||||
if (base <= 0) {
|
||||
@@ -177,7 +215,12 @@ export function computeProductEditorPricePreview(input: {
|
||||
}
|
||||
const entry = findMethodPriceEntry(input.pricesData, input.form.reading_method)
|
||||
if (!entry) {
|
||||
return { status: 'empty', message: '' }
|
||||
return {
|
||||
status: 'empty',
|
||||
message: input.pricesData.length
|
||||
? 'مرجع در لیست قیمت یافت نشد'
|
||||
: 'لیست قیمت مرجع خالی است — بروزرسانی را بزنید',
|
||||
}
|
||||
}
|
||||
const { base, profit } = normalizeReferenceBase(entry)
|
||||
const liveAmount = adjustedProductPrice(base, profit, offsetPercent)
|
||||
@@ -232,7 +275,12 @@ export function computeProductEditorPricePreview(input: {
|
||||
}
|
||||
const entry = findMethodPriceEntry(input.pricesData, input.form.reading_method)
|
||||
if (!entry) {
|
||||
return { status: 'empty', message: '' }
|
||||
return {
|
||||
status: 'empty',
|
||||
message: input.pricesData.length
|
||||
? 'مرجع در لیست قیمت یافت نشد'
|
||||
: 'لیست قیمت مرجع خالی است — بروزرسانی را بزنید',
|
||||
}
|
||||
}
|
||||
const { base, profit } = normalizeReferenceBase(entry)
|
||||
const liveAmount = adjustedProductPrice(base, profit, offsetPercent)
|
||||
|
||||
Reference in New Issue
Block a user