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)"
>
<a-tab-pane key="0" tab="قیمت‌گذاری" force-render>
<div class="flex flex-col gap-4">
<div
v-if="usesMeltedPricing"
class="product-editor-panel flex flex-col gap-3"
@@ -112,7 +113,7 @@
</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-label">
<DashboardLabelWithHelp label="دریافت قیمت از" :help="priceSourceHelp" size="sm" />
@@ -265,91 +266,128 @@
</AButton>
</div>
</div>
</div>
</a-tab-pane>
<a-tab-pane key="1" tab="مشخصات محصول" force-render>
<div class="flex flex-col gap-4">
<div class="grid gap-4 md:grid-cols-3">
<a-form-item name="title" :rules="inputRules()" class="!mb-0">
<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">
<a-form-item name="title" :rules="inputRules()" class="!mb-0">
<template #label>
<DashboardLabelWithHelp label="عنوان" :help="titleHelp" size="sm" />
</template>
<BasicInput v-model:value="form.title" />
</a-form-item>
<a-form-item name="group_type" :rules="inputRules()" class="!mb-0">
<template #label>
<DashboardLabelWithHelp label="دسته‌بندی" :help="categoryHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.group_type" :items="categoryItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item name="metal_id" :rules="inputRules()" class="!mb-0">
<template #label>
<DashboardLabelWithHelp label="جنس فلز" :help="metalHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.metal_id" :items="metalItems" valueName="value" keyName="key" />
</a-form-item>
</div>
<a-form-item name="description" class="!mb-0">
<template #label>
<DashboardLabelWithHelp label="عنوان" :help="titleHelp" size="sm" />
<DashboardLabelWithHelp label="توضیح محصول" :help="productDescriptionHelp" size="sm" />
</template>
<BasicInput v-model:value="form.title" />
</a-form-item>
<a-form-item name="group_type" :rules="inputRules()" class="!mb-0">
<template #label>
<DashboardLabelWithHelp label="دسته‌بندی" :help="categoryHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.group_type" :items="categoryItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item name="metal_id" :rules="inputRules()" class="!mb-0">
<template #label>
<DashboardLabelWithHelp label="جنس فلز" :help="metalHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.metal_id" :items="metalItems" valueName="value" keyName="key" />
<a-textarea
v-model:value="form.description"
:rows="2"
placeholder="متن توضیح برای نمایش در اپ مشتری (اختیاری)"
:maxlength="2000"
show-count
/>
</a-form-item>
</div>
<a-form-item name="description" class="!mb-0">
<template #label>
<DashboardLabelWithHelp label="توضیح محصول" :help="productDescriptionHelp" size="sm" />
</template>
<a-textarea
v-model:value="form.description"
:rows="3"
placeholder="متن توضیح برای نمایش در اپ مشتری (اختیاری)"
:maxlength="2000"
show-count
/>
</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 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
v-if="!editId"
v-model:value="tradeTypeOption"
name="tradeTypeGroup"
>
<a-radio value="count">تعدادی</a-radio>
<a-radio value="quantity">مقداری</a-radio>
</a-radio-group>
<span
v-else
class="inline-flex items-center rounded-full px-3 py-1 text-sm font-600"
:class="form.trade_by_quantity ? 'bg-emerald-100 text-emerald-800' : 'bg-sky-100 text-sky-800'"
>
{{ resolveProductTradeTypeLabel(form.trade_by_quantity) }}
</span>
</div>
<div class="flex items-center justify-between gap-3">
<DashboardLabelWithHelp label="وزن محصول" :help="weightHelp" size="sm" />
<a-radio-group :value="weightOption" name="weightGroup" @update:value="$emit('update:weightOption', $event)">
<a-radio value="1">دارد</a-radio>
<a-radio value="2">ندارد</a-radio>
</a-radio-group>
</div>
</div>
<a-radio-group
v-if="!editId"
v-model:value="tradeTypeOption"
name="tradeTypeGroup"
<div
v-if="isQuantityTrade"
class="rounded-md bg-amber-50 px-3 py-1.5 text-xs leading-5 text-amber-800"
>
<a-radio value="count">معامله تعدادی</a-radio>
<a-radio value="quantity">معامله مقداری</a-radio>
</a-radio-group>
<span
v-else
class="inline-flex items-center rounded-full px-3 py-1 text-sm font-600"
:class="form.trade_by_quantity ? 'bg-emerald-100 text-emerald-800' : 'bg-sky-100 text-sky-800'"
>
{{ resolveProductTradeTypeLabel(form.trade_by_quantity) }}
</span>
</div>
<div
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" />
مشتری بر اساس مقدار (گرم/کیلوگرم) خرید می‌کند.
</div>
<a-radio-group :value="weightOption" name="weightGroup" @update:value="$emit('update:weightOption', $event)">
<a-radio value="1">دارد</a-radio>
<a-radio value="2">ندارد</a-radio>
</a-radio-group>
</div>
<div v-if="weightOption === '1'" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<a-form-item name="amount" :rules="inputRulesNumber('وزن')" class="!mb-0">
<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>
<DashboardLabelWithHelp :label="weightFieldLabel" :help="weightAmountHelp" size="sm" />
</template>
<BasicInput v-model:value="form.amount" type="number" />
</a-form-item>
<a-form-item
v-if="showUnitField"
name="unit"
:rules="inputRules()"
class="!mb-0"
>
<template #label>
<DashboardLabelWithHelp label="واحد" :help="unitHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.unit" :items="unitItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item
v-if="showDirectPriceField"
name="direct_price"
:rules="inputRules()"
class="!mb-0"
>
<template #label>
<DashboardLabelWithHelp label="نوع قیمت" :help="directPriceHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.direct_price" :items="priceItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item
v-if="isStore && !isMeltedGroup"
name="inventory"
:rules="inputRules()"
class="!mb-0"
>
<template #label>
<DashboardLabelWithHelp label="تعداد موجودی" :help="inventoryHelp" size="sm" />
</template>
<BasicInput v-model:value="form.inventory" type="number" />
</a-form-item>
<div
v-if="form.amount && form.amount < 1 && 0 < form.amount && tradeWeightUnit === 'گرم'"
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 }} سوت
@@ -357,73 +395,31 @@
</div>
</div>
<div
v-if="showUnitField || showDirectPriceField || (isStore && !isMeltedGroup)"
class="grid gap-4 md:grid-cols-2 xl:grid-cols-3"
>
<a-form-item
v-if="showUnitField"
name="unit"
:rules="inputRules()"
class="!mb-0"
>
<template #label>
<DashboardLabelWithHelp label="واحد" :help="unitHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.unit" :items="unitItems" valueName="value" keyName="value" />
</a-form-item>
<a-form-item
v-if="showDirectPriceField"
name="direct_price"
:rules="inputRules()"
class="!mb-0"
>
<template #label>
<DashboardLabelWithHelp label="نوع قیمت" :help="directPriceHelp" size="sm" />
</template>
<BasicSelect v-model:value="form.direct_price" :items="priceItems" valueName="value" keyName="key" />
</a-form-item>
<a-form-item
v-if="isStore && !isMeltedGroup"
name="inventory"
:rules="inputRules()"
class="!mb-0"
>
<template #label>
<DashboardLabelWithHelp label="تعداد موجودی" :help="inventoryHelp" size="sm" />
</template>
<BasicInput v-model:value="form.inventory" type="number" />
</a-form-item>
</div>
<div class="product-editor-panel flex flex-col gap-0 rounded-lg overflow-hidden">
<div class="product-visibility-row">
<DashboardLabelWithHelp label="نمایش در لیست محصولات" :help="showInListHelp" size="sm" />
<a-form-item name="show_in_list" class="!mb-0">
<!-- نمایش -->
<div class="product-section">
<div class="product-section__title">تنظیمات نمایش</div>
<div class="product-visibility-panel">
<div class="product-visibility-row">
<span class="text-sm">نمایش در لیست محصولات</span>
<a-switch v-model:checked="form.show_in_list" />
</a-form-item>
</div>
<div class="product-visibility-row">
<DashboardLabelWithHelp label="نمایش در دارایی‌ها" :help="showInAssetsHelp" size="sm" />
<a-form-item name="show_in_assets" class="!mb-0">
</div>
<div class="product-visibility-row">
<span class="text-sm">نمایش در دارایی‌ها</span>
<a-switch v-model:checked="form.show_in_assets" />
</a-form-item>
</div>
<div class="product-visibility-row">
<DashboardLabelWithHelp label="نمایش در تابلو TV" :help="showOnTvHelp" size="sm" />
<a-form-item name="show_on_tv" class="!mb-0">
</div>
<div class="product-visibility-row">
<span class="text-sm">نمایش در تابلو TV</span>
<a-switch v-model:checked="form.show_on_tv" />
</a-form-item>
</div>
</div>
<TvFormulaPriceSection
v-if="form.show_on_tv"
:model-value="form"
:reference-middle-price="formulaReferenceMiddlePrice"
:product-amount="Number(form.amount) || 0"
@update:model-value="onTvFormulaFormPatch"
/>
</div>
<TvFormulaPriceSection
v-if="form.show_on_tv"
:model-value="form"
:reference-middle-price="formulaReferenceMiddlePrice"
:product-amount="Number(form.amount) || 0"
@update:model-value="onTvFormulaFormPatch"
/>
</div>
</a-tab-pane>
@@ -980,8 +976,23 @@ defineExpose({
@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 {
@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));
}
.product-visibility-row:last-child {