change gift-card

This commit is contained in:
2026-09-21 11:10:34 +03:30
parent 8004dd52b3
commit 7e869902d6
7 changed files with 214 additions and 48 deletions
+17 -2
View File
@@ -1,6 +1,16 @@
export interface GiftcardCustomer {
id: number
name: string
mobile: string
}
export interface Giftcard {
id: number
customer_id: any
name?: string
mobile?: string
customer?: GiftcardCustomer
show_in_app?: number | boolean
count: number
amount: number
total: number
@@ -35,9 +45,14 @@ export interface AssignedTo {
mobile: string
}
export type GiftCardAssignMode = 'customer' | 'public'
export interface GiftcardInput {
mobile: string
name: string
assignMode: GiftCardAssignMode
customer_id?: number
show_in_app?: number
mobile?: string
name?: string
count: number
amount: number
unit: string
@@ -12,7 +12,13 @@
@close="close"
>
<div class="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div class="min-w-0">
<div class="min-w-0 space-y-1">
<p v-if="giftCardCustomer" class="m-0 text-13px font-600" style="color: var(--admin-text)">
مشتری: {{ giftCardCustomer.name }} — <span dir="ltr">{{ giftCardCustomer.mobile }}</span>
</p>
<p v-else class="m-0 text-13px font-600" style="color: var(--admin-text)">
کد عمومی — بدون مشتری مشخص
</p>
<p class="m-0 text-13px text-[var(--admin-text-secondary)]">
{{ codes.length }} کد — دانلود به صورت اکسل یا PDF
</p>
@@ -102,6 +108,7 @@ import {
downloadGiftCardCodesPdf,
type GiftCardCodesExportMeta
} from '../utils/exportGiftCardCodes'
import { resolveGiftCardCustomer } from '../utils/giftCardCustomer'
import { SELECT_DROPDOWN_Z_INDEX } from '@/constants/overlayZIndex'
const props = defineProps<{
@@ -129,20 +136,22 @@ const columns = [
{ title: 'وضعیت', dataIndex: 'sts', key: 'sts' },
{ title: 'تاریخ انقضا', dataIndex: 'expiry_date', key: 'expiry_date' },
{ title: 'تاریخ استفاده', dataIndex: 'used_at', key: 'used_at' },
{ title: 'نام کاربر اختصاص داده شده', dataIndex: 'assigned_to.name', key: 'assigned_to_name' },
{ title: 'شماره موبایل کاربر اختصاص داده شده', dataIndex: 'assigned_to.mobile', key: 'assigned_to_mobile' }
{ title: 'نام استفاده‌کننده', dataIndex: 'assigned_to.name', key: 'assigned_to_name' },
{ title: 'موبایل استفاده‌کننده', dataIndex: 'assigned_to.mobile', key: 'assigned_to_mobile' }
]
function close() {
visibleProxy.value = false
}
const giftCardCustomer = computed(() => resolveGiftCardCustomer(props.giftCard))
const exportMeta = computed<GiftCardCodesExportMeta>(() => ({
id: props.giftCard?.id,
unit: props.giftCard?.unit,
count: props.giftCard?.count,
customerName: (props.giftCard as Giftcard & { customer?: { name?: string } })?.customer?.name,
customerMobile: (props.giftCard as Giftcard & { customer?: { mobile?: string } })?.customer?.mobile,
customerName: giftCardCustomer.value?.name,
customerMobile: giftCardCustomer.value?.mobile,
createdAt: props.giftCard?.created_at
}))
@@ -1,25 +1,48 @@
<template>
<a-form :model="form" ref="formRef" autocomplete="off">
<a-form :model="props.form" ref="formRef" autocomplete="off">
<div class="space-y-3">
<a-form-item name="mobile" :rules="inputLenRules(11)">
<a-form-item name="assignMode" :rules="inputRules()">
<template #label>
<AdminLabelWithHelp label="شماره موبایل مشتری" :help="giftCardMobileHelp" />
<AdminLabelWithHelp label="نوع ثبت" :help="giftCardAssignModeHelp" />
</template>
<BasicInput v-model:value="form.mobile" type="tel" placeholder="09123456789" />
<a-radio-group v-model:value="props.form.assignMode" button-style="solid" @change="onAssignModeChange">
<a-radio-button value="public">کد عمومی (بدون مشتری)</a-radio-button>
<a-radio-button value="customer">اختصاص به مشتری</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="name" :rules="inputRules()">
<a-form-item v-if="isPublicMode" name="show_in_app" :rules="inputRules()">
<template #label>
<AdminLabelWithHelp label="نام مشتری" :help="giftCardNameHelp" />
<AdminLabelWithHelp label="نمایش در اپ کاربر" :help="giftCardPublicVisibilityHelp" />
</template>
<BasicInput v-model:value="form.name" placeholder="نام و نام خانوادگی مشتری" />
<a-radio-group v-model:value="props.form.show_in_app" button-style="solid" class="flex flex-col gap-2 sm:flex-row">
<a-radio-button :value="1">نمایش برای همه</a-radio-button>
<a-radio-button :value="0">مخفی — فقط با وارد کردن کد</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item v-if="isCustomerMode" name="customer_id" :rules="customerIdRules">
<template #label>
<AdminLabelWithHelp label="مشتری" :help="giftCardCustomerSelectHelp" />
</template>
<a-select
v-model:value="props.form.customer_id"
show-search
allow-clear
placeholder="انتخاب مشتری"
class="w-full"
:options="customerOptions"
:loading="customersLoading"
:filter-option="filterCustomer"
:dropdown-style="{ zIndex: SELECT_DROPDOWN_Z_INDEX }"
/>
</a-form-item>
<a-form-item name="unit" :rules="inputRules()">
<template #label>
<AdminLabelWithHelp label="نوع کارت هدیه" :help="giftCardUnitHelp" />
</template>
<a-radio-group v-model:value="form.unit" button-style="solid">
<a-radio-group v-model:value="props.form.unit" button-style="solid">
<a-radio-button value="1">ریالی</a-radio-button>
<a-radio-button value="2">{{ metalLabel.goldWalletShort() }}</a-radio-button>
</a-radio-group>
@@ -29,19 +52,19 @@
<template #label>
<AdminLabelWithHelp label="تعداد کدها" :help="giftCardCountHelp" />
</template>
<BasicInput v-model:value="form.count" type="tel" placeholder="تعداد کدهای کارت هدیه" />
<BasicInput v-model:value="props.form.count" type="tel" placeholder="تعداد کدهای کارت هدیه" />
</a-form-item>
<a-form-item name="amount" :rules="inputRulesNumber()">
<template #label>
<AdminLabelWithHelp
:label="`مقدار ${form.unit === '2' ? '(گرم)' : '(ریال)'}`"
:label="`مقدار ${props.form.unit === '2' ? '(گرم)' : '(ریال)'}`"
:help="giftCardAmountHelp"
/>
</template>
<BasicInput
v-model:value="form.amount"
:addon-before="form.unit === '2' ? 'گرم' : 'ریال'"
v-model:value="props.form.amount"
:addon-before="props.form.unit === '2' ? 'گرم' : 'ریال'"
type="tel"
placeholder="مقدار کارت هدیه"
/>
@@ -51,11 +74,11 @@
<template #label>
<AdminLabelWithHelp label="تاریخ انقضا" :help="giftCardExpiryHelp" />
</template>
<BaseDatePicker v-model:value="form.expiry_date" format="YYYY-MM-DD" />
<BaseDatePicker v-model:value="props.form.expiry_date" format="YYYY-MM-DD" />
</a-form-item>
<a-form-item label="توضیحات" name="description" class="mb-0">
<BasicTextArea v-model:value="form.description" place-holder="توضیحات اختیاری — فقط برای یادداشت شما در پنل" />
<BasicTextArea v-model:value="props.form.description" place-holder="توضیحات اختیاری — فقط برای یادداشت شما در پنل" />
</a-form-item>
</div>
</a-form>
@@ -64,24 +87,55 @@
<script setup lang="ts">
import type { FormInstance } from 'ant-design-vue'
import type { GiftcardInput } from '@/model/giftCard'
import { getCustomersList } from '@/api/customer'
import type { Customer } from '@/model/customer'
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
import {
giftCardAmountHelp,
giftCardAssignModeHelp,
giftCardCountHelp,
giftCardCustomerSelectHelp,
giftCardExpiryHelp,
giftCardMobileHelp,
giftCardNameHelp,
giftCardPublicVisibilityHelp,
giftCardUnitHelp,
} from '@/utils/adminHelpCopy'
import { inputLenRules, inputRules, inputRulesNumber } from '@/utils/inputRules'
import { SELECT_DROPDOWN_Z_INDEX } from '@/constants/overlayZIndex'
import { inputRules, inputRulesNumber } from '@/utils/inputRules'
import { metalLabel } from '@/utils/adminMetalLabels'
import dayjs from 'dayjs'
defineProps<{
const props = defineProps<{
form: GiftcardInput
}>()
const formRef = ref<FormInstance>()
const customers = ref<Customer[]>([])
const customersLoading = ref(false)
const isCustomerMode = computed(() => props.form.assignMode === 'customer')
const isPublicMode = computed(() => props.form.assignMode === 'public')
const customerOptions = computed(() =>
customers.value.map((customer) => ({
value: customer.id,
label: `${customer.name ?? '—'} — ${customer.mobile ?? ''}`.trim(),
}))
)
const customerIdRules = [
{
validator(_rule: unknown, value: unknown) {
if (props.form.assignMode !== 'customer') {
return Promise.resolve()
}
if (value === undefined || value === null || value === '') {
return Promise.reject('انتخاب مشتری الزامی است')
}
return Promise.resolve()
},
trigger: ['change', 'blur'],
},
]
const expiryDateRules = [
...inputRules(),
@@ -103,6 +157,39 @@ const expiryDateRules = [
},
]
function filterCustomer(input: string, option?: { label?: string }) {
return String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
function onAssignModeChange() {
if (props.form.assignMode === 'public') {
props.form.customer_id = undefined
if (props.form.show_in_app === undefined) {
props.form.show_in_app = 0
}
formRef.value?.clearValidate?.(['customer_id'])
return
}
props.form.show_in_app = 0
formRef.value?.clearValidate?.(['show_in_app'])
}
async function loadCustomers() {
customersLoading.value = true
try {
customers.value = (await getCustomersList()) ?? []
} catch (e) {
console.error(e)
} finally {
customersLoading.value = false
}
}
onMounted(() => {
void loadCustomers()
})
defineExpose({
validateFields: () => formRef.value?.validateFields(),
resetFields: () => formRef.value?.resetFields(),
@@ -3,6 +3,7 @@ import { toJalali } from '@core/utils/useDataTime'
import CustomTag from '@/components/CustomTag.vue'
import { usePrice } from '@core/utils/usePrice'
import { metalLabel } from '@/utils/adminMetalLabels'
import { giftCardCustomerLabel, giftCardCustomerMobile } from '../utils/giftCardCustomer'
/*import BudgetSelect from '@/components/BudgetSelect.vue'
import { useStaticApiListsStore } from '@/store/staticApisStore'
import type { FilterSelect } from '@/model/filterSelect'
@@ -105,18 +106,14 @@ export function getGiftCardColumns(): BaseTableColumn[] {
dataIndex: 'customer.name',
key: 'customer.name',
type: 'Text',
customRender: ({ record }) => {
return record.customer?.name ?? '-'
}
customRender: ({ record }) => giftCardCustomerLabel(record)
},
{
title: 'شماره موبایل مشتری',
dataIndex: 'customer.mobile',
key: 'customer.mobile',
type: 'Number',
customRender: ({ record }) => {
return record.customer?.mobile ?? '-'
}
customRender: ({ record }) => giftCardCustomerMobile(record)
},
{
title: 'توضیحات',
+35 -12
View File
@@ -135,6 +135,7 @@ import {
discountPercentHelp,
} from '@/utils/adminHelpCopy'
import BaseDatePicker from '@core/components/BaseDatePicker'
import { giftCardCustomerLabel, giftCardCustomerMobile } from './utils/giftCardCustomer'
//loading
const loading = ref<boolean>(false)
@@ -169,8 +170,9 @@ function closeGiftCardSheet() {
//form
const form = ref<GiftcardInput>({
mobile: '',
name: '',
assignMode: 'public',
customer_id: undefined,
show_in_app: 0,
count: undefined,
amount: undefined,
unit: '1',
@@ -187,8 +189,8 @@ const selectedGiftCard = ref<Giftcard | null>(null)
function info(val: Giftcard) {
// نمایش اطلاعات اصلی در BasicInfoModal
infoParams.title = 'اطلاعات کارت هدیه'
infoParams.name = val.customer?.name ?? '-'
infoParams.type = val.customer?.mobile ?? '-'
infoParams.name = giftCardCustomerLabel(val)
infoParams.type = giftCardCustomerMobile(val)
infoParams.date = toJalaliNoTime(val.created_at)
infoParams.time = toTime(val.created_at)
infoParams.itemsTitle = 'اطلاعات کارت هدیه'
@@ -197,6 +199,18 @@ function info(val: Giftcard) {
key: 'کد',
value: val.id ?? '-'
},
{
key: 'نوع ثبت',
value: val.customer_id ? 'اختصاصی' : Number(val.show_in_app) === 1 ? 'عمومی — نمایش در اپ' : 'عمومی — مخفی (فقط با کد)'
},
{
key: 'مشتری',
value: giftCardCustomerLabel(val)
},
{
key: 'موبایل مشتری',
value: giftCardCustomerMobile(val)
},
{
key: 'نوع کارت هدیه',
value: val.unit === 3 ? 'تخفیف' : val.unit === 2 ? metalLabel.goldWalletShort() : 'ریالی'
@@ -261,8 +275,9 @@ async function edit(record: Giftcard) {
addGiftCardVisible.value = true
editId = record.id
form.value = {
mobile: record.customer?.mobile ?? '',
name: record.customer?.name ?? '',
assignMode: record.customer_id ? 'customer' : 'public',
customer_id: record.customer_id ?? undefined,
show_in_app: record.customer_id ? 0 : Number(record.show_in_app) === 1 ? 1 : 0,
count: record.count,
amount: record.amount,
unit: record.unit.toString(),
@@ -330,8 +345,9 @@ async function addGiftOrDiscount(type: 'gift' | 'discount' = 'gift') {
const resetData = () => {
giftCardEditorRef.value?.resetFields?.()
form.value = {
mobile: '',
name: '',
assignMode: 'public',
customer_id: undefined,
show_in_app: 0,
count: undefined,
amount: undefined,
unit: '1',
@@ -353,19 +369,26 @@ async function ok() {
await giftCardEditorRef.value?.validateFields?.().then(async () => {
loading.value = true
try {
const formData = {
...form.value,
const formData: Record<string, unknown> = {
count: Number(form.value.count),
amount: Number(form.value.amount),
unit: form.value.unit,
expiry_date: form.value.expiry_date,
description: form.value.description,
}
if (form.value.assignMode === 'customer' && form.value.customer_id) {
formData.customer_id = Number(form.value.customer_id)
} else if (form.value.assignMode === 'public') {
formData.show_in_app = Number(form.value.show_in_app) === 1 ? 1 : 0
}
if (formData.expiry_date) {
// تبدیل به میلادی و تنظیم به آخر روز
formData.expiry_date = dayjs(formData.expiry_date)
formData.expiry_date = dayjs(formData.expiry_date as string)
.calendar('gregory')
.endOf('day')
.format('YYYY-MM-DD HH:mm:ss')
}
await createGiftCard(formData)
await createGiftCard(formData as GiftcardInput)
message.success('کارت هدیه با موفقیت ثبت شد')
addGiftCardVisible.value = false
resetData()
@@ -0,0 +1,32 @@
import type { Giftcard } from '@/model/giftCard'
export function resolveGiftCardCustomer(giftCard?: Giftcard | null) {
if (!giftCard) return null
const name = giftCard.customer?.name ?? giftCard.name
const mobile = giftCard.customer?.mobile ?? giftCard.mobile
if (name || mobile) {
return {
name: name || '-',
mobile: mobile || '-',
}
}
if (giftCard.customer_id) {
return {
name: `مشتری #${giftCard.customer_id}`,
mobile: '-',
}
}
return null
}
export function giftCardCustomerLabel(giftCard?: Giftcard | null) {
return resolveGiftCardCustomer(giftCard)?.name ?? '— (کد عمومی)'
}
export function giftCardCustomerMobile(giftCard?: Giftcard | null) {
return resolveGiftCardCustomer(giftCard)?.mobile ?? '-'
}
+8 -5
View File
@@ -41,17 +41,20 @@ export const settingsProfileBannersThemeHelp =
export const pricingPageIntro =
'اینجا تاریخچهٔ همگام‌سازی قیمت را می‌بینید. دکمهٔ «به‌روزرسانی قیمت» همان لحظه قیمت را از منبعی که در تنظیمات مشخص کرده‌اید می‌گیرد و روی محصولات اعمال می‌کند. اگر قیمت عوض نشده باشد پیام «تغییری نکرد» می‌آید.'
export const giftCardMobileHelp =
'شماره موبایلی که کارت هدیه برایش ساخته می‌شود. باید ۱۱ رقم و با ۰۹ شروع شود.'
export const giftCardAssignModeHelp =
'اختصاص به مشتری: کدها فقط برای همان شخص در اپ قابل استفاده است. کد عمومی: فقط کد ساخته می‌شود و بعداً به هر کسی می‌توانید بدهید.'
export const giftCardNameHelp =
'نام مشتری فقط برای شناسایی در پنل شماست؛ روی اپ مشتری تأثیر مستقیم ندارد.'
export const giftCardCustomerSelectHelp =
'مشتری را از لیست انتخاب کنید؛ نام و موبایل خودکار از پروندهٔ مشتری پر می‌شود.'
export const giftCardPublicVisibilityHelp =
'نمایش برای همه: کد(ها) در لیست اپ همه کاربران دیده می‌شود. مخفی: فقط کسی که کد را از مدیر گرفته و دستی وارد کند می‌تواند استفاده کند.'
export const giftCardUnitHelp =
'ریالی = اعتبار کیف پول (ریال). مس = وزن مس که به کیف فلزی مشتری واریز می‌شود.'
export const giftCardCountHelp =
'چند کد یکتا ساخته شود. مثلاً ۳ → سه کارت هدیه جدا، هر کدام یک کد و یک شارژ کامل با همان مبلغ.'
'تعداد کد یکتا در همان بچ. مثلاً ۳ → یک رکورد با سه کد جداگانه و همان مبلغ برای هر کد.'
export const giftCardAmountHelp =
'مقدار هر کد: اگر ریالی انتخاب کردید مبلغ به ریال؛ اگر مس انتخاب کردید وزن به واحد معامله.'