update admin panel, api, and shared-types
This commit is contained in:
@@ -75,7 +75,7 @@
|
||||
</section>
|
||||
|
||||
<!-- ── trend + channel ── -->
|
||||
<section class="row row--main">
|
||||
<section class="row" :class="isSuper ? 'row--main' : 'row--single'">
|
||||
<div class="panel">
|
||||
<div class="panel__head">
|
||||
<div>
|
||||
@@ -708,6 +708,9 @@ b {
|
||||
.row--half {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.row--single {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
/* ── KPI cards ── */
|
||||
.kpi-grid {
|
||||
|
||||
@@ -77,12 +77,17 @@
|
||||
<a-input v-model:value="form.title" placeholder="مثلاً: گردنبند طلا" :disabled="accountingLocked" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="pf-grid-2">
|
||||
<a-form-item label="کد محصول" :help="accountingLocked ? 'از نرمافزار حسابداری' : undefined">
|
||||
<!-- site fields (نوع نمایش، شرح سایت، تصویر شاخص و گالری سایت) — super admin only -->
|
||||
<div :class="{ 'pf-grid-2': users.isSuperAdmin }">
|
||||
<a-form-item
|
||||
label="کد محصول"
|
||||
:class="{ 'mb-0': !users.isSuperAdmin }"
|
||||
:help="accountingLocked ? 'از نرمافزار حسابداری' : undefined"
|
||||
>
|
||||
<a-input v-model:value="form.product_code" :disabled="accountingLocked" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="نوع نمایش">
|
||||
<a-form-item v-if="users.isSuperAdmin" label="نوع نمایش">
|
||||
<a-select v-model:value="form.show_type">
|
||||
<a-select-option :value="1">سایت</a-select-option>
|
||||
<a-select-option :value="2">گوشی</a-select-option>
|
||||
@@ -91,7 +96,7 @@
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="شرح محصول (سایت)" class="mb-0">
|
||||
<a-form-item v-if="users.isSuperAdmin" label="شرح محصول (سایت)" class="mb-0">
|
||||
<a-textarea
|
||||
v-model:value="form.body_html"
|
||||
:auto-size="{ minRows: 5, maxRows: 12 }"
|
||||
@@ -193,19 +198,21 @@
|
||||
<span class="pf-section__icon"><PictureOutlined /></span>
|
||||
<div>
|
||||
<h3>گالری تصاویر</h3>
|
||||
<p>تصاویر جداگانه برای نمایش در سایت و اپلیکیشن موبایل</p>
|
||||
<p>{{ users.isSuperAdmin ? 'تصاویر جداگانه برای نمایش در سایت و اپلیکیشن موبایل' : 'تصاویر نمایش در اپلیکیشن موبایل' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-form-item label="گالری تصاویر سایت">
|
||||
<GalleryUploadInput
|
||||
v-model="gallerySite"
|
||||
purpose="product.gallery"
|
||||
placeholder="افزودن تصاویر گالری سایت"
|
||||
/>
|
||||
</a-form-item>
|
||||
<template v-if="users.isSuperAdmin">
|
||||
<a-form-item label="گالری تصاویر سایت">
|
||||
<GalleryUploadInput
|
||||
v-model="gallerySite"
|
||||
purpose="product.gallery"
|
||||
placeholder="افزودن تصاویر گالری سایت"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<div class="pf-divider"></div>
|
||||
<div class="pf-divider"></div>
|
||||
</template>
|
||||
|
||||
<a-form-item label="گالری تصاویر موبایل" class="mb-0">
|
||||
<GalleryUploadInput
|
||||
@@ -286,7 +293,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-uploads">
|
||||
<a-form-item label="سایت" class="mb-0">
|
||||
<a-form-item v-if="users.isSuperAdmin" label="سایت" class="mb-0">
|
||||
<ImageUploadInput v-model="form.indicator_id" purpose="product.indicator" placeholder="تصویر شاخص" />
|
||||
</a-form-item>
|
||||
<a-form-item label="موبایل" class="mb-0">
|
||||
@@ -383,7 +390,7 @@
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
<p class="variant-form__hint">تصویر بارگذاریشده به گالری سایت محصول هم اضافه میشود.</p>
|
||||
<p v-if="users.isSuperAdmin" class="variant-form__hint">تصویر بارگذاریشده به گالری سایت محصول هم اضافه میشود.</p>
|
||||
<template #footer>
|
||||
<a-button @click="variantModal = false">انصراف</a-button>
|
||||
<a-button type="primary" @click="saveVariant">ثبت</a-button>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<a-descriptions-item label="دسته بندی">
|
||||
{{ tempRow.category?.title || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="نوع نمایش">{{ tempRow.show_type_str }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="users.isSuperAdmin" label="نوع نمایش">{{ tempRow.show_type_str }}</a-descriptions-item>
|
||||
<a-descriptions-item label="تعداد تنوع">
|
||||
{{ tempRow.variants_all?.length ?? tempRow.variants_count ?? 0 }}
|
||||
</a-descriptions-item>
|
||||
@@ -129,7 +129,7 @@ const router = useRouter();
|
||||
const users = useUsersStore();
|
||||
const hasPermission = (rid: string) => Boolean(users.hasPermission(rid));
|
||||
|
||||
const model = buildProductListModel();
|
||||
const model = buildProductListModel(users.isSuperAdmin);
|
||||
const tblProducts = ref<{ fetch: () => Promise<void> } | null>(null);
|
||||
const selectedRows = ref<Record<string, any>[]>([]);
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
// ColCreator block above the live XTbl definition — not revived.
|
||||
import XTbl, { Xtc } from '../../../components/x-table/model';
|
||||
|
||||
export function buildProductListModel(): XTbl {
|
||||
// `withShowType` — the display channel is a site field, shown to the super admin only.
|
||||
export function buildProductListModel(withShowType = true): XTbl {
|
||||
const tbl = new XTbl('admin/products/list', 'لیست محصولات');
|
||||
tbl.add(new Xtc('id', 'کد').filterNumber());
|
||||
tbl.add(new Xtc('ProductImage', 'تصویر محصول').renderSlot('image').noSort());
|
||||
@@ -14,15 +15,17 @@ export function buildProductListModel(): XTbl {
|
||||
// legacy key is 'category.collection.title'; the mapping layer aliases it
|
||||
tbl.add(new Xtc('category.collection.title', 'دسته بندی').filterText());
|
||||
tbl.add(new Xtc('product_code', 'کد محصول').filterText());
|
||||
tbl.add(
|
||||
new Xtc('show_type', 'نوع نمایش')
|
||||
.filterSelect({
|
||||
1: 'سایت',
|
||||
2: 'گوشی',
|
||||
3: 'سایت و گوشی',
|
||||
})
|
||||
.renderSlot('show_type'),
|
||||
);
|
||||
if (withShowType) {
|
||||
tbl.add(
|
||||
new Xtc('show_type', 'نوع نمایش')
|
||||
.filterSelect({
|
||||
1: 'سایت',
|
||||
2: 'گوشی',
|
||||
3: 'سایت و گوشی',
|
||||
})
|
||||
.renderSlot('show_type'),
|
||||
);
|
||||
}
|
||||
// D27 — stock in the accounting software, and whether the app shows it at all.
|
||||
tbl.add(new Xtc('accounting_stock', 'حسابداری / نمایش').renderSlot('accounting').noSort());
|
||||
tbl.add(new Xtc('action', 'عملیات').renderSlot('op').noSort());
|
||||
|
||||
@@ -125,13 +125,15 @@ import { CloseOutlined, DownOutlined } from '@ant-design/icons-vue';
|
||||
import XTable from '../../../components/x-table/XTable.vue';
|
||||
import { buildProductListModel } from '../../apps/product/productListModel';
|
||||
import { useCartStore, type CartItem, type CartVariant } from '../../../stores/cart';
|
||||
import { useUsersStore } from '../../../stores/users';
|
||||
import api from '../../../api/client';
|
||||
|
||||
const router = useRouter();
|
||||
const cart = useCartStore();
|
||||
const users = useUsersStore();
|
||||
|
||||
const noImage = '/img/no-items.png';
|
||||
const cols = buildProductListModel();
|
||||
const cols = buildProductListModel(users.isSuperAdmin);
|
||||
const tblProducts = ref<{ fetch: () => Promise<void> } | null>(null);
|
||||
const loading = ref(false);
|
||||
const customers = ref<{ id: string; fname: string; lname: string }[]>([]);
|
||||
|
||||
@@ -94,13 +94,8 @@
|
||||
<a-button class="mt-4" type="primary" @click="saveTags">ذخیره برچسب ها</a-button>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- اسلایدر موبایل — دسترسی جدا از بقیهی این صفحه، چون آپلود بنر عمومی
|
||||
سایت حساستر از ویرایش برند/برچسب است (custom.config.statics.slider) -->
|
||||
<a-tab-pane
|
||||
v-if="hasPermission('custom.config.statics.slider')"
|
||||
key="slider"
|
||||
tab="اسلایدر موبایل"
|
||||
>
|
||||
<!-- اسلایدر موبایل — برای هر کسی که این صفحه را میبیند (config.statics) -->
|
||||
<a-tab-pane key="slider" tab="اسلایدر موبایل">
|
||||
<div class="slider-toolbar">
|
||||
<span class="slider-count">{{ sliders.length }} اسلاید</span>
|
||||
<div class="row">
|
||||
@@ -309,11 +304,8 @@ function removeSlide(index: number) {
|
||||
sliders.value.splice(index, 1);
|
||||
}
|
||||
|
||||
// Gated server-side by its own `custom.config.statics.slider` permission (not
|
||||
// the broad `config.statics` that guards the rest of this screen) — see
|
||||
// admin-misc.controller.ts. Uses the dedicated `const/slider` route rather
|
||||
// than the generic `const/:key`, which the server only accepts under that
|
||||
// specific permission.
|
||||
// Uses the dedicated `const/slider` route: the generic `const/:key` refuses the
|
||||
// slider's key (admin-misc.controller.ts).
|
||||
async function saveSliders() {
|
||||
try {
|
||||
await api.put('const/slider', { val: sliders.value });
|
||||
@@ -371,12 +363,9 @@ onMounted(async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await Promise.all([loadBrands(), users.isSuperAdmin ? loadCollections() : Promise.resolve()]);
|
||||
const [t, s] = await Promise.all([
|
||||
api.get('const/__list__tags'),
|
||||
hasPermission('custom.config.statics.slider') ? api.get('const/slider') : Promise.resolve(null),
|
||||
]);
|
||||
const [t, s] = await Promise.all([api.get('const/__list__tags'), api.get('const/slider')]);
|
||||
tags.value = Array.isArray(t.data) ? t.data : [];
|
||||
sliders.value = Array.isArray(s?.data) ? s.data : [];
|
||||
sliders.value = Array.isArray(s.data) ? s.data : [];
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
|
||||
@@ -216,7 +216,7 @@ const cart = useCartStore();
|
||||
const hasPermission = (rid: string) => Boolean(users.hasPermission(rid));
|
||||
|
||||
const noImage = '/img/no-items.png';
|
||||
const cols = buildProductListModel();
|
||||
const cols = buildProductListModel(users.isSuperAdmin);
|
||||
const tblProducts = ref<{ fetch: () => Promise<void> } | null>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user