ui(product): restructure product specs tab with sections and fix pricing tab gap

- Product specs tab: group fields into bordered sections (اطلاعات پایه, وزن و واحد, تنظیمات نمایش)
- Visibility switches in a compact panel with divider lines
- Weight/unit/inventory fields in same grid row
- Pricing tab: wrap all content in flex-col gap-4
- Remove unnecessary product-editor-panel border from non-melted pricing block

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-19 12:23:52 +03:30
co-authored by Cursor
parent 6cb2488221
commit 0a0d1d8680
@@ -14,6 +14,7 @@
@update:activeKey="$emit('update:activeKey', $event)" @update:activeKey="$emit('update:activeKey', $event)"
> >
<a-tab-pane key="0" tab="قیمت‌گذاری" force-render> <a-tab-pane key="0" tab="قیمت‌گذاری" force-render>
<div class="flex flex-col gap-4">
<div <div
v-if="usesMeltedPricing" v-if="usesMeltedPricing"
class="product-editor-panel flex flex-col gap-3" class="product-editor-panel flex flex-col gap-3"
@@ -112,7 +113,7 @@
</div> </div>
</div> </div>
<div v-if="!usesMeltedPricing" class="product-editor-panel flex flex-col gap-4"> <div v-if="!usesMeltedPricing" class="flex flex-col gap-4">
<div class="product-spec-switch-row"> <div class="product-spec-switch-row">
<div class="product-spec-label"> <div class="product-spec-label">
<DashboardLabelWithHelp label="دریافت قیمت از" :help="priceSourceHelp" size="sm" /> <DashboardLabelWithHelp label="دریافت قیمت از" :help="priceSourceHelp" size="sm" />
@@ -265,10 +266,14 @@
</AButton> </AButton>
</div> </div>
</div> </div>
</div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="1" tab="مشخصات محصول" force-render> <a-tab-pane key="1" tab="مشخصات محصول" force-render>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-5">
<!-- اطلاعات پایه -->
<div class="product-section">
<div class="product-section__title">اطلاعات پایه</div>
<div class="grid gap-4 md:grid-cols-3"> <div class="grid gap-4 md:grid-cols-3">
<a-form-item name="title" :rules="inputRules()" class="!mb-0"> <a-form-item name="title" :rules="inputRules()" class="!mb-0">
<template #label> <template #label>
@@ -289,32 +294,34 @@
<BasicSelect v-model:value="form.metal_id" :items="metalItems" valueName="value" keyName="key" /> <BasicSelect v-model:value="form.metal_id" :items="metalItems" valueName="value" keyName="key" />
</a-form-item> </a-form-item>
</div> </div>
<a-form-item name="description" class="!mb-0"> <a-form-item name="description" class="!mb-0">
<template #label> <template #label>
<DashboardLabelWithHelp label="توضیح محصول" :help="productDescriptionHelp" size="sm" /> <DashboardLabelWithHelp label="توضیح محصول" :help="productDescriptionHelp" size="sm" />
</template> </template>
<a-textarea <a-textarea
v-model:value="form.description" v-model:value="form.description"
:rows="3" :rows="2"
placeholder="متن توضیح برای نمایش در اپ مشتری (اختیاری)" placeholder="متن توضیح برای نمایش در اپ مشتری (اختیاری)"
:maxlength="2000" :maxlength="2000"
show-count show-count
/> />
</a-form-item> </a-form-item>
<div v-if="!isMeltedGroup" class="product-editor-panel flex flex-col gap-4">
<div class="product-spec-switch-row">
<div class="product-spec-label">
<DashboardLabelWithHelp label="نوع معامله" :help="tradeByQuantityHelp" size="sm" />
</div> </div>
<!-- وزن و واحد -->
<div class="product-section">
<div class="product-section__title">وزن و واحد</div>
<div v-if="!isMeltedGroup" class="flex flex-col gap-4">
<div class="grid gap-4 md:grid-cols-2">
<div class="flex items-center justify-between gap-3">
<DashboardLabelWithHelp label="نوع معامله" :help="tradeByQuantityHelp" size="sm" />
<a-radio-group <a-radio-group
v-if="!editId" v-if="!editId"
v-model:value="tradeTypeOption" v-model:value="tradeTypeOption"
name="tradeTypeGroup" name="tradeTypeGroup"
> >
<a-radio value="count">معامله تعدادی</a-radio> <a-radio value="count">تعدادی</a-radio>
<a-radio value="quantity">معامله مقداری</a-radio> <a-radio value="quantity">مقداری</a-radio>
</a-radio-group> </a-radio-group>
<span <span
v-else v-else
@@ -324,43 +331,28 @@
{{ resolveProductTradeTypeLabel(form.trade_by_quantity) }} {{ resolveProductTradeTypeLabel(form.trade_by_quantity) }}
</span> </span>
</div> </div>
<div <div class="flex items-center justify-between gap-3">
v-if="isQuantityTrade"
class="rounded-lg border border-solid border-amber-200 bg-amber-50 px-3 py-2 text-xs leading-6 text-amber-800"
>
معامله مقداری: مشتری بر اساس مقدار (گرم/کیلوگرم) خرید می‌کند. قیمت‌گذاری این محصول در تب «قیمت‌گذاری» به‌صورت مقداری تنظیم می‌شود.
</div>
<div class="product-spec-switch-row">
<div class="product-spec-label">
<DashboardLabelWithHelp label="وزن محصول" :help="weightHelp" size="sm" /> <DashboardLabelWithHelp label="وزن محصول" :help="weightHelp" size="sm" />
</div>
<a-radio-group :value="weightOption" name="weightGroup" @update:value="$emit('update:weightOption', $event)"> <a-radio-group :value="weightOption" name="weightGroup" @update:value="$emit('update:weightOption', $event)">
<a-radio value="1">دارد</a-radio> <a-radio value="1">دارد</a-radio>
<a-radio value="2">ندارد</a-radio> <a-radio value="2">ندارد</a-radio>
</a-radio-group> </a-radio-group>
</div> </div>
</div>
<div v-if="weightOption === '1'" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3"> <div
<a-form-item name="amount" :rules="inputRulesNumber('وزن')" class="!mb-0"> v-if="isQuantityTrade"
class="rounded-md bg-amber-50 px-3 py-1.5 text-xs leading-5 text-amber-800"
>
مشتری بر اساس مقدار (گرم/کیلوگرم) خرید می‌کند.
</div>
</div>
<div class="grid gap-4 md:grid-cols-3">
<a-form-item v-if="weightOption === '1' && !isMeltedGroup" name="amount" :rules="inputRulesNumber('وزن')" class="!mb-0">
<template #label> <template #label>
<DashboardLabelWithHelp :label="weightFieldLabel" :help="weightAmountHelp" size="sm" /> <DashboardLabelWithHelp :label="weightFieldLabel" :help="weightAmountHelp" size="sm" />
</template> </template>
<BasicInput v-model:value="form.amount" type="number" /> <BasicInput v-model:value="form.amount" type="number" />
</a-form-item> </a-form-item>
<div
v-if="form.amount && form.amount < 1 && 0 < form.amount && tradeWeightUnit === 'گرم'"
class="flex items-end pb-1 text-sm text-gray-500"
>
معادل {{ form.amount * 1000 }} سوت
</div>
</div>
</div>
<div
v-if="showUnitField || showDirectPriceField || (isStore && !isMeltedGroup)"
class="grid gap-4 md:grid-cols-2 xl:grid-cols-3"
>
<a-form-item <a-form-item
v-if="showUnitField" v-if="showUnitField"
name="unit" name="unit"
@@ -394,29 +386,32 @@
</template> </template>
<BasicInput v-model:value="form.inventory" type="number" /> <BasicInput v-model:value="form.inventory" type="number" />
</a-form-item> </a-form-item>
<div
v-if="weightOption === '1' && !isMeltedGroup && form.amount && form.amount < 1 && 0 < form.amount && tradeWeightUnit === 'گرم'"
class="flex items-end pb-1 text-sm text-gray-500"
>
معادل {{ form.amount * 1000 }} سوت
</div>
</div>
</div> </div>
<div class="product-editor-panel flex flex-col gap-0 rounded-lg overflow-hidden"> <!-- نمایش -->
<div class="product-section">
<div class="product-section__title">تنظیمات نمایش</div>
<div class="product-visibility-panel">
<div class="product-visibility-row"> <div class="product-visibility-row">
<DashboardLabelWithHelp label="نمایش در لیست محصولات" :help="showInListHelp" size="sm" /> <span class="text-sm">نمایش در لیست محصولات</span>
<a-form-item name="show_in_list" class="!mb-0">
<a-switch v-model:checked="form.show_in_list" /> <a-switch v-model:checked="form.show_in_list" />
</a-form-item>
</div> </div>
<div class="product-visibility-row"> <div class="product-visibility-row">
<DashboardLabelWithHelp label="نمایش در دارایی‌ها" :help="showInAssetsHelp" size="sm" /> <span class="text-sm">نمایش در دارایی‌ها</span>
<a-form-item name="show_in_assets" class="!mb-0">
<a-switch v-model:checked="form.show_in_assets" /> <a-switch v-model:checked="form.show_in_assets" />
</a-form-item>
</div> </div>
<div class="product-visibility-row"> <div class="product-visibility-row">
<DashboardLabelWithHelp label="نمایش در تابلو TV" :help="showOnTvHelp" size="sm" /> <span class="text-sm">نمایش در تابلو TV</span>
<a-form-item name="show_on_tv" class="!mb-0">
<a-switch v-model:checked="form.show_on_tv" /> <a-switch v-model:checked="form.show_on_tv" />
</a-form-item>
</div> </div>
</div> </div>
<TvFormulaPriceSection <TvFormulaPriceSection
v-if="form.show_on_tv" v-if="form.show_on_tv"
:model-value="form" :model-value="form"
@@ -425,6 +420,7 @@
@update:model-value="onTvFormulaFormPatch" @update:model-value="onTvFormulaFormPatch"
/> />
</div> </div>
</div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="اختلاف خرید و فروش" force-render> <a-tab-pane key="2" tab="اختلاف خرید و فروش" force-render>
@@ -980,8 +976,23 @@ defineExpose({
@apply flex flex-col items-stretch gap-2; @apply flex flex-col items-stretch gap-2;
} }
.product-section {
@apply flex flex-col gap-4 rounded-lg p-4;
border: 1px solid var(--ant-color-border-secondary, rgba(0, 0, 0, 0.06));
}
.product-section__title {
@apply text-sm font-bold;
color: var(--ant-color-text-secondary, rgba(0, 0, 0, 0.45));
}
.product-visibility-panel {
@apply flex flex-col rounded-md overflow-hidden;
border: 1px solid var(--ant-color-border-secondary, rgba(0, 0, 0, 0.06));
}
.product-visibility-row { .product-visibility-row {
@apply flex items-center justify-between gap-3 px-3 py-2; @apply flex items-center justify-between gap-3 px-3 py-2.5;
border-bottom: 1px solid var(--ant-color-border-secondary, rgba(0, 0, 0, 0.06)); border-bottom: 1px solid var(--ant-color-border-secondary, rgba(0, 0, 0, 0.06));
} }
.product-visibility-row:last-child { .product-visibility-row:last-child {