feat: complete admin panel and backend admin api migration
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<!--
|
||||
Ported from legacy henza_admin/src/views/offersIgnore/offersIgnore-add-edit.vue.
|
||||
|
||||
Pick an exclusion type, then tick the specific products / categories /
|
||||
customers / brands to exclude from the automatic discount. The ticked set is
|
||||
the complete selection for that type — legacy posts `{type, item_id: [...]}`
|
||||
and the server replaces that type's rows with it.
|
||||
|
||||
The card title in legacy is "تعریف تخفیف" (the same string the promotion form
|
||||
uses) even though this screen defines EXCLUSIONS — preserved, not corrected.
|
||||
-->
|
||||
<div id="page-user-view">
|
||||
<div id="user-data">
|
||||
<a-spin :spinning="loading">
|
||||
<a-form layout="vertical">
|
||||
<a-card title="تعریف تخفیف" class="mb-base mb-10">
|
||||
<div class="grid-4">
|
||||
<a-form-item label="نوع استثناء">
|
||||
<a-select v-model:value="type" @change="changeTable">
|
||||
<a-select-option v-for="(label, code) in types" :key="code" :value="Number(code)">
|
||||
{{ label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<template v-if="type === 1">
|
||||
<h6>تعداد {{ selectedRow.length }} محصول انتخاب شده است</h6>
|
||||
<XTable ref="tblProducts" v-model="selectedRow" :model="productCols" :has-selection="true">
|
||||
<template #image="{ record }">
|
||||
<img
|
||||
width="90px"
|
||||
height="90px"
|
||||
style="object-fit: scale-down"
|
||||
:src="record.thumb && record.thumb.full ? record.thumb.full : noImage"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
<h6>تعداد {{ selectedRow.length }} محصول انتخاب شده است</h6>
|
||||
</template>
|
||||
|
||||
<template v-if="type === 2">
|
||||
<h6>تعداد {{ selectedRow.length }} دسته بندی انتخاب شده است</h6>
|
||||
<XTable ref="tblCategory" v-model="selectedRow" :model="categoryCols" :has-selection="true" />
|
||||
<h6>تعداد {{ selectedRow.length }} دسته بندی انتخاب شده است</h6>
|
||||
</template>
|
||||
|
||||
<template v-if="type === 3">
|
||||
<h6>تعداد {{ selectedRow.length }} مشتری انتخاب شده است</h6>
|
||||
<XTable ref="tblCustomer" v-model="selectedRow" :model="customerCols" :has-selection="true" />
|
||||
<h6>تعداد {{ selectedRow.length }} مشتری انتخاب شده است</h6>
|
||||
</template>
|
||||
|
||||
<template v-if="type === 4">
|
||||
<h6>تعداد {{ selectedRow.length }} برند انتخاب شده است</h6>
|
||||
<XTable ref="tblBrand" v-model="selectedRow" :model="brandCols" :has-selection="true" />
|
||||
<h6>تعداد {{ selectedRow.length }} برند انتخاب شده است</h6>
|
||||
</template>
|
||||
|
||||
<div class="mt-5">
|
||||
<a-button type="primary" class="mr-4" @click="handleSubmit">ثبت</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
<a-spin :spinning="deleteLoading">
|
||||
<a-card title="حذف نوع استثناء">
|
||||
<div class="grid-4 items-end">
|
||||
<a-form-item label="نوع">
|
||||
<a-select v-model:value="deleteType">
|
||||
<a-select-option v-for="(label, code) in types" :key="code" :value="Number(code)">
|
||||
{{ label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-popconfirm
|
||||
title="آیا مطمئن هستید؟"
|
||||
ok-text="بله"
|
||||
cancel-text="خیر"
|
||||
@confirm="deleteIgnoreType"
|
||||
>
|
||||
<a-button danger>حذف</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import XTable from '../../components/x-table/XTable.vue';
|
||||
import XTbl, { Xtc } from '../../components/x-table/model';
|
||||
import api from '../../api/client';
|
||||
|
||||
const noImage = '/img/no-items.png';
|
||||
const loading = ref(false);
|
||||
const deleteLoading = ref(false);
|
||||
|
||||
/** Legacy's exclusion types reuse the promotion scope codes. */
|
||||
const types: Record<number, string> = {
|
||||
1: 'محصولات',
|
||||
2: 'دسته بندی',
|
||||
3: 'مشتری',
|
||||
4: 'برند',
|
||||
};
|
||||
|
||||
const type = ref(1);
|
||||
const deleteType = ref(1);
|
||||
const selectedRow = ref<Record<string, any>[]>([]);
|
||||
const ignoreList = ref<{ id: string; type: number; item_id: string }[]>([]);
|
||||
|
||||
// One picker table per type.
|
||||
const productCols = buildProducts();
|
||||
const categoryCols = buildCategories();
|
||||
const customerCols = buildCustomers();
|
||||
const brandCols = buildBrands();
|
||||
|
||||
function buildProducts(): XTbl {
|
||||
const t = new XTbl('admin/products/list', 'محصولات');
|
||||
t.add(new Xtc('id', 'کد').filterNumber());
|
||||
t.add(new Xtc('ProductImage', 'تصویر').renderSlot('image').noSort());
|
||||
t.add(new Xtc('title', 'نام محصول').filterText());
|
||||
t.add(new Xtc('product_code', 'کد محصول').filterText());
|
||||
return t;
|
||||
}
|
||||
function buildCategories(): XTbl {
|
||||
const t = new XTbl('admin/categories/list', 'دسته بندی');
|
||||
t.add(new Xtc('id', 'کد').filterNumber());
|
||||
t.add(new Xtc('title', 'عنوان دسته').filterText());
|
||||
t.add(new Xtc('type_str', 'نوع نمایش').noSort());
|
||||
return t;
|
||||
}
|
||||
function buildCustomers(): XTbl {
|
||||
const t = new XTbl('admin/customers-omde/list', 'مشتریان');
|
||||
t.add(new Xtc('id', 'کد').filterNumber());
|
||||
t.add(new Xtc('name', 'نام و نام خانوادگی').filterText());
|
||||
t.add(new Xtc('mobile', 'موبایل').filterText().noSort());
|
||||
return t;
|
||||
}
|
||||
function buildBrands(): XTbl {
|
||||
const t = new XTbl('admin/products/brands-table', 'برندها');
|
||||
t.add(new Xtc('id', 'کد').filterNumber());
|
||||
t.add(new Xtc('title', 'عنوان برند').filterText());
|
||||
return t;
|
||||
}
|
||||
|
||||
/** legacy items_id(type): the already-excluded ids for a type */
|
||||
function itemsId(t: number) {
|
||||
return ignoreList.value.filter((i) => i.type === t).map((x) => x.item_id);
|
||||
}
|
||||
|
||||
/** legacy changeTable(): reload the tick state when the type changes */
|
||||
function changeTable(t: number) {
|
||||
const ids = itemsId(t);
|
||||
selectedRow.value = ids.map((id) => ({ id }));
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
loading.value = true;
|
||||
await api.post('ignore', {
|
||||
type: type.value,
|
||||
item_id: selectedRow.value.map((r) => r.id),
|
||||
});
|
||||
message.success('عملیات با موفقیت انجام شد');
|
||||
await getData();
|
||||
changeTable(type.value);
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
message.error(e?.response?.data?.message ?? 'خطا در ثبت اطلاعات.');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteIgnoreType() {
|
||||
try {
|
||||
deleteLoading.value = true;
|
||||
await api.delete(`ignore/${deleteType.value}`);
|
||||
message.success('حذف شد');
|
||||
await getData();
|
||||
changeTable(type.value);
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message ?? 'خطا در حذف');
|
||||
} finally {
|
||||
deleteLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } = await api.get('ignore');
|
||||
ignoreList.value = data;
|
||||
// legacy seeds the selection from type 1 on load
|
||||
selectedRow.value = itemsId(1).map((id) => ({ id }));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
message.error('خطا در دریافت اطلاعات');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(getData);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.grid-4 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.items-end {
|
||||
align-items: end;
|
||||
}
|
||||
.mb-10 {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user