added some feature for plugin

This commit is contained in:
2026-09-09 17:13:40 +03:30
parent c2f2b23afe
commit ba09c1d442
+68 -16
View File
@@ -8,6 +8,7 @@ import MoneyInput from '@/components/MoneyInput.vue'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { SUPERADMIN_SYSTEM_SETTING_KEYS } from '@/config/superAdminSettingKeys' import { SUPERADMIN_SYSTEM_SETTING_KEYS } from '@/config/superAdminSettingKeys'
import { rewriteLegacyUploadUrl } from '@/utils/rewriteLegacyUploadUrl' import { rewriteLegacyUploadUrl } from '@/utils/rewriteLegacyUploadUrl'
import { formatNumber, rialToToman } from '@/utils/money'
import { import {
resolveSettingDescription, resolveSettingDescription,
resolveSettingTitle, resolveSettingTitle,
@@ -250,13 +251,19 @@ async function save(item: CatalogItem) {
return return
} }
} }
const prices = item.prices.map((price) => ({ const prices = item.prices.map((price) => {
durationDays: price.durationDays, const finalPriceRials = Math.max(0, Math.trunc(Number(price.finalPriceRials) || 0))
enabled: price.enabled, const originalPriceRials =
originalPriceRials: price.enabled && price.hasDiscount
price.enabled && price.hasDiscount ? price.originalPriceRials : price.finalPriceRials, ? Math.max(0, Math.trunc(Number(price.originalPriceRials) || 0))
finalPriceRials: price.finalPriceRials, : finalPriceRials
})) return {
durationDays: price.durationDays,
enabled: Boolean(price.enabled),
originalPriceRials,
finalPriceRials,
}
})
savingId.value = item.settingKey savingId.value = item.settingKey
try { try {
const id = await ensureCatalogId(item) const id = await ensureCatalogId(item)
@@ -268,6 +275,7 @@ async function save(item: CatalogItem) {
prices, prices,
}) })
message.success('ذخیره شد') message.success('ذخیره شد')
closeDetail()
await load() await load()
} catch (e: any) { } catch (e: any) {
const msg = e?.response?.data?.message const msg = e?.response?.data?.message
@@ -339,6 +347,17 @@ function slotUploader(item: CatalogItem) {
}) => uploadSlot(item, options) }) => uploadSlot(item, options)
} }
function priceSummary(item: CatalogItem): string[] {
return item.prices
.filter((row) => row.enabled)
.map((row) => {
const toman = rialToToman(row.finalPriceRials)
const label = durationLabel[row.durationDays]
if (toman <= 0) return `${label} رایگان`
return `${label} ${formatNumber(toman)} تومان`
})
}
onMounted(load) onMounted(load)
</script> </script>
@@ -398,6 +417,10 @@ onMounted(load)
> >
<strong>{{ item.titleFa || 'بدون عنوان' }}</strong> <strong>{{ item.titleFa || 'بدون عنوان' }}</strong>
<span class="catalog-key" dir="ltr">{{ item.settingKey }}</span> <span class="catalog-key" dir="ltr">{{ item.settingKey }}</span>
<span v-if="priceSummary(item).length" class="catalog-prices">
{{ priceSummary(item).join(' · ') }}
</span>
<span v-else class="catalog-prices muted">قیمتی روشن نیست</span>
</button> </button>
<div v-if="!filteredItems.length" class="empty-hint"> <div v-if="!filteredItems.length" class="empty-hint">
{{ items.length ? 'نتیجه‌ای برای این جستجو نیست' : 'کلید دسترسی‌ای در قالب نیست' }} {{ items.length ? 'نتیجه‌ای برای این جستجو نیست' : 'کلید دسترسی‌ای در قالب نیست' }}
@@ -408,12 +431,13 @@ onMounted(load)
<a-modal <a-modal
v-model:open="showDetail" v-model:open="showDetail"
:title="selectedItem?.titleFa || 'جزئیات افزونه'" :title="selectedItem?.titleFa || 'جزئیات افزونه'"
width="40rem" width="64rem"
wrap-class-name="sa-modal-wrap" wrap-class-name="sa-modal-wrap catalog-detail-modal"
:footer="null" :footer="null"
@cancel="closeDetail" @cancel="closeDetail"
> >
<div v-if="selectedItem" class="catalog-detail"> <div v-if="selectedItem" class="catalog-detail">
<div class="catalog-detail-grid">
<a-form layout="vertical" class="catalog-form"> <a-form layout="vertical" class="catalog-form">
<a-form-item label="عنوان" required> <a-form-item label="عنوان" required>
<a-input v-model:value="selectedItem.titleFa" :disabled="!canWrite" placeholder="مثلاً ماژول جدید" /> <a-input v-model:value="selectedItem.titleFa" :disabled="!canWrite" placeholder="مثلاً ماژول جدید" />
@@ -422,7 +446,7 @@ onMounted(load)
<a-input :value="selectedItem.settingKey" disabled dir="ltr" /> <a-input :value="selectedItem.settingKey" disabled dir="ltr" />
</a-form-item> </a-form-item>
<a-form-item label="توضیح"> <a-form-item label="توضیح">
<a-textarea v-model:value="selectedItem.description" :rows="4" :maxlength="4000" :disabled="!canWrite" /> <a-textarea v-model:value="selectedItem.description" :rows="2" :maxlength="4000" :disabled="!canWrite" />
</a-form-item> </a-form-item>
<a-form-item label="عکس‌ها"> <a-form-item label="عکس‌ها">
<p class="img-hint">حداکثر سه عکس. در ادمین ماژول عکس اول روی کارت است.</p> <p class="img-hint">حداکثر سه عکس. در ادمین ماژول عکس اول روی کارت است.</p>
@@ -493,7 +517,7 @@ onMounted(load)
<span>اصلی</span> <span>اصلی</span>
<MoneyInput <MoneyInput
v-model:value="price.originalPriceRials" v-model:value="price.originalPriceRials"
size="middle" size="small"
addon-after="تومان" addon-after="تومان"
:disabled="!canWrite || !price.enabled" :disabled="!canWrite || !price.enabled"
/> />
@@ -502,7 +526,7 @@ onMounted(load)
<span>{{ price.hasDiscount ? 'نهایی' : 'قیمت' }}</span> <span>{{ price.hasDiscount ? 'نهایی' : 'قیمت' }}</span>
<MoneyInput <MoneyInput
v-model:value="price.finalPriceRials" v-model:value="price.finalPriceRials"
size="middle" size="small"
addon-after="تومان" addon-after="تومان"
:disabled="!canWrite || !price.enabled" :disabled="!canWrite || !price.enabled"
/> />
@@ -511,6 +535,7 @@ onMounted(load)
</article> </article>
</div> </div>
</section> </section>
</div>
<a-button <a-button
v-if="canWrite" v-if="canWrite"
type="primary" type="primary"
@@ -633,9 +658,31 @@ onMounted(load)
direction: ltr; direction: ltr;
unicode-bidi: isolate; unicode-bidi: isolate;
} }
.catalog-prices {
font-size: 0.75rem;
color: #17324d;
font-weight: 700;
line-height: 1.45;
}
.catalog-prices.muted {
font-weight: 500;
color: #94a3b8;
}
.catalog-detail { .catalog-detail {
min-width: 0; min-width: 0;
} }
.catalog-detail-grid {
display: grid;
gap: 12px;
min-width: 0;
}
@media (min-width: 900px) {
.catalog-detail-grid {
grid-template-columns: minmax(16rem, 0.9fr) minmax(20rem, 1.2fr);
align-items: start;
gap: 14px;
}
}
.catalog-detail > .ant-btn { .catalog-detail > .ant-btn {
margin-top: 12px; margin-top: 12px;
} }
@@ -679,6 +726,11 @@ onMounted(load)
display: grid; display: grid;
gap: 8px; gap: 8px;
} }
@media (min-width: 640px) {
.price-list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.price-item { .price-item {
border: 1px solid #e8eef5; border: 1px solid #e8eef5;
border-radius: 12px; border-radius: 12px;
@@ -738,8 +790,9 @@ onMounted(load)
} }
.img-slots { .img-slots {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, 4.75rem);
gap: 6px; gap: 6px;
justify-content: start;
} }
.img-slot { .img-slot {
position: relative; position: relative;
@@ -768,9 +821,8 @@ onMounted(load)
font-size: 0.75rem; font-size: 0.75rem;
} }
.img-slots :deep(.ant-upload.ant-upload-select-picture-card) { .img-slots :deep(.ant-upload.ant-upload-select-picture-card) {
width: 100%; width: 4.75rem;
height: auto; height: 4.75rem;
aspect-ratio: 1;
margin: 0; margin: 0;
} }
</style> </style>