gift card export, new category, login theme fix
This commit is contained in:
@@ -11,7 +11,33 @@
|
||||
@cancel="close"
|
||||
@close="close"
|
||||
>
|
||||
<div v-if="isMobile" class="mb-3 text-base font-700">لیست کدهای کارت هدیه</div>
|
||||
<div class="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="min-w-0">
|
||||
<p class="m-0 text-13px text-[var(--admin-text-secondary)]">
|
||||
{{ codes.length }} کد — دانلود به صورت اکسل یا PDF
|
||||
</p>
|
||||
</div>
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
placement="bottomLeft"
|
||||
:overlay-style="{ zIndex: SELECT_DROPDOWN_Z_INDEX }"
|
||||
>
|
||||
<BaseButton
|
||||
type="default"
|
||||
size="small"
|
||||
icon="vuesax-linear:document-download"
|
||||
:disabled="!codes.length"
|
||||
>
|
||||
دانلود
|
||||
</BaseButton>
|
||||
<template #overlay>
|
||||
<a-menu @click="handleDownload">
|
||||
<a-menu-item key="excel">اکسل (CSV)</a-menu-item>
|
||||
<a-menu-item key="pdf">PDF</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
class="hidden md:block"
|
||||
@@ -63,19 +89,25 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Giftcardcode } from '@/model/giftCard'
|
||||
import type { Giftcard, Giftcardcode } from '@/model/giftCard'
|
||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
|
||||
import { isMobile } from '@core/utils/is'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import GiftCardCodeMobileDetailSheet from './GiftCardCodeMobileDetailSheet.vue'
|
||||
import GiftCardCodeMobileListItem from './GiftCardCodeMobileListItem.vue'
|
||||
import {
|
||||
downloadGiftCardCodesExcel,
|
||||
downloadGiftCardCodesPdf,
|
||||
type GiftCardCodesExportMeta
|
||||
} from '../utils/exportGiftCardCodes'
|
||||
import { SELECT_DROPDOWN_Z_INDEX } from '@/constants/overlayZIndex'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
codes: Giftcardcode[]
|
||||
giftCard?: Giftcard | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -104,4 +136,24 @@ const columns = [
|
||||
function close() {
|
||||
visibleProxy.value = false
|
||||
}
|
||||
|
||||
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,
|
||||
createdAt: props.giftCard?.created_at
|
||||
}))
|
||||
|
||||
function handleDownload({ key }: { key: string }) {
|
||||
if (!props.codes.length) return
|
||||
if (key === 'excel') {
|
||||
downloadGiftCardCodesExcel(props.codes, exportMeta.value)
|
||||
return
|
||||
}
|
||||
if (key === 'pdf') {
|
||||
downloadGiftCardCodesPdf(props.codes, exportMeta.value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -91,7 +91,11 @@
|
||||
:name="infoParams.name"
|
||||
:items="infoParams.items"
|
||||
/>
|
||||
<GiftCardCodesSheet v-model:visible="giftCardCodesVisible" :codes="selectedGiftCardCodes" />
|
||||
<GiftCardCodesSheet
|
||||
v-model:visible="giftCardCodesVisible"
|
||||
:codes="selectedGiftCardCodes"
|
||||
:gift-card="selectedGiftCard"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -178,6 +182,7 @@ const form = ref<GiftcardInput>({
|
||||
let infoParams = reactive<Info>({})
|
||||
const giftCardCodesVisible = ref<boolean>(false)
|
||||
const selectedGiftCardCodes = ref<Giftcardcode[]>([])
|
||||
const selectedGiftCard = ref<Giftcard | null>(null)
|
||||
|
||||
function info(val: Giftcard) {
|
||||
// نمایش اطلاعات اصلی در BasicInfoModal
|
||||
@@ -209,8 +214,10 @@ function info(val: Giftcard) {
|
||||
value: usePrice(val.total ?? 0)
|
||||
},
|
||||
{
|
||||
key: 'کد یکتا',
|
||||
value: val.unique_code ?? '-'
|
||||
key: val.giftcardcodes?.length > 1 ? 'کدهای یکتا' : 'کد یکتا',
|
||||
value: val.giftcardcodes?.length
|
||||
? val.giftcardcodes.map((c) => c.code).join('، ')
|
||||
: (val.unique_code ?? '-')
|
||||
},
|
||||
{
|
||||
key: 'تعداد استفاده شده',
|
||||
@@ -239,6 +246,7 @@ function info(val: Giftcard) {
|
||||
function showGiftCardCodes(val: Giftcard) {
|
||||
// نمایش کدهای کارت هدیه در مودال جداگانه
|
||||
if (val.giftcardcodes && val.giftcardcodes.length > 0) {
|
||||
selectedGiftCard.value = val
|
||||
selectedGiftCardCodes.value = val.giftcardcodes
|
||||
giftCardCodesVisible.value = true
|
||||
} else {
|
||||
@@ -341,11 +349,15 @@ const userInfoVisible = ref<boolean>(false)
|
||||
|
||||
//submit gift card
|
||||
async function ok() {
|
||||
if (loading.value) return
|
||||
await giftCardEditorRef.value?.validateFields?.().then(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// تنظیم تاریخ انقضا به آخر همان روز (23:59:59) و تبدیل به میلادی
|
||||
const formData = { ...form.value }
|
||||
const formData = {
|
||||
...form.value,
|
||||
count: Number(form.value.count),
|
||||
amount: Number(form.value.amount),
|
||||
}
|
||||
if (formData.expiry_date) {
|
||||
// تبدیل به میلادی و تنظیم به آخر روز
|
||||
formData.expiry_date = dayjs(formData.expiry_date)
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import type { Giftcardcode } from '@/model/giftCard'
|
||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
|
||||
export interface GiftCardCodesExportMeta {
|
||||
id?: number
|
||||
unit?: number
|
||||
count?: number
|
||||
customerName?: string
|
||||
customerMobile?: string
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
interface ExportRow {
|
||||
index: number
|
||||
code: string
|
||||
unit: string
|
||||
amount: string
|
||||
status: string
|
||||
expiryDate: string
|
||||
usedAt: string
|
||||
assignedName: string
|
||||
assignedMobile: string
|
||||
}
|
||||
|
||||
const CSV_HEADERS = [
|
||||
'ردیف',
|
||||
'کد',
|
||||
'نوع',
|
||||
'مقدار',
|
||||
'وضعیت',
|
||||
'تاریخ انقضا',
|
||||
'تاریخ استفاده',
|
||||
'نام کاربر',
|
||||
'موبایل کاربر'
|
||||
]
|
||||
|
||||
function statusLabel(sts: number): string {
|
||||
if (sts === 1) return 'فعال'
|
||||
if (sts === 2) return 'استفاده شده'
|
||||
return 'غیرفعال'
|
||||
}
|
||||
|
||||
function unitLabel(unit: number): string {
|
||||
if (unit === 2) return metalLabel.goldWalletShort()
|
||||
if (unit === 3) return 'تخفیف'
|
||||
return 'ریالی'
|
||||
}
|
||||
|
||||
function amountLabel(code: Giftcardcode): string {
|
||||
const suffix = code.unit === 2 ? 'گرم' : 'ریال'
|
||||
return `${code.amount ?? '-'} ${suffix}`
|
||||
}
|
||||
|
||||
function buildRows(codes: Giftcardcode[]): ExportRow[] {
|
||||
return codes.map((code, index) => ({
|
||||
index: index + 1,
|
||||
code: code.code,
|
||||
unit: unitLabel(code.unit),
|
||||
amount: amountLabel(code),
|
||||
status: statusLabel(code.sts),
|
||||
expiryDate: code.expiry_date ? toJalali(code.expiry_date) : '-',
|
||||
usedAt: code.used_at ? toJalali(code.used_at) : '-',
|
||||
assignedName: code.assigned_to?.name ?? '-',
|
||||
assignedMobile: code.assigned_to?.mobile ?? '-'
|
||||
}))
|
||||
}
|
||||
|
||||
function csvCell(value: unknown): string {
|
||||
const text = value == null || value === '' ? '-' : String(value)
|
||||
if (/[",\n\r]/.test(text)) {
|
||||
return `"${text.replace(/"/g, '""')}"`
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function buildFilename(meta: GiftCardCodesExportMeta, extension: string): string {
|
||||
const idPart = meta.id ? `-${meta.id}` : ''
|
||||
const datePart = new Date().toISOString().slice(0, 10)
|
||||
return `giftcard-codes${idPart}-${datePart}.${extension}`
|
||||
}
|
||||
|
||||
function downloadBlob(content: string, filename: string, mime: string) {
|
||||
const blob = new Blob([content], { type: mime })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
function buildSummary(meta: GiftCardCodesExportMeta): string[] {
|
||||
const lines: string[] = []
|
||||
if (meta.id) lines.push(`شناسه کارت هدیه: ${meta.id}`)
|
||||
if (meta.unit != null) lines.push(`نوع: ${unitLabel(meta.unit)}`)
|
||||
if (meta.count != null) lines.push(`تعداد کدها: ${meta.count}`)
|
||||
if (meta.customerName) lines.push(`مشتری: ${meta.customerName}`)
|
||||
if (meta.customerMobile) lines.push(`موبایل: ${meta.customerMobile}`)
|
||||
if (meta.createdAt) lines.push(`تاریخ ثبت: ${toJalali(meta.createdAt)}`)
|
||||
return lines
|
||||
}
|
||||
|
||||
export function downloadGiftCardCodesExcel(codes: Giftcardcode[], meta: GiftCardCodesExportMeta = {}) {
|
||||
const rows = buildRows(codes)
|
||||
const lines = [
|
||||
CSV_HEADERS.map(csvCell).join(','),
|
||||
...rows.map((row) =>
|
||||
[
|
||||
row.index,
|
||||
row.code,
|
||||
row.unit,
|
||||
row.amount,
|
||||
row.status,
|
||||
row.expiryDate,
|
||||
row.usedAt,
|
||||
row.assignedName,
|
||||
row.assignedMobile
|
||||
]
|
||||
.map(csvCell)
|
||||
.join(',')
|
||||
)
|
||||
]
|
||||
const content = `\uFEFF${lines.join('\r\n')}`
|
||||
downloadBlob(content, buildFilename(meta, 'csv'), 'text/csv;charset=utf-8')
|
||||
}
|
||||
|
||||
export function downloadGiftCardCodesPdf(codes: Giftcardcode[], meta: GiftCardCodesExportMeta = {}) {
|
||||
const rows = buildRows(codes)
|
||||
const summary = buildSummary(meta)
|
||||
const title = meta.id ? `لیست کدهای کارت هدیه #${meta.id}` : 'لیست کدهای کارت هدیه'
|
||||
|
||||
const tableRows = rows
|
||||
.map(
|
||||
(row) => `
|
||||
<tr>
|
||||
<td>${row.index}</td>
|
||||
<td dir="ltr">${row.code}</td>
|
||||
<td>${row.unit}</td>
|
||||
<td>${row.amount}</td>
|
||||
<td>${row.status}</td>
|
||||
<td>${row.expiryDate}</td>
|
||||
<td>${row.usedAt}</td>
|
||||
<td>${row.assignedName}</td>
|
||||
<td dir="ltr">${row.assignedMobile}</td>
|
||||
</tr>`
|
||||
)
|
||||
.join('')
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
body { font-family: Tahoma, Arial, sans-serif; margin: 24px; color: #111; }
|
||||
h1 { font-size: 18px; margin: 0 0 8px; }
|
||||
.summary { font-size: 12px; color: #555; margin-bottom: 16px; line-height: 1.8; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; text-align: right; }
|
||||
th { background: #f5f5f5; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${title}</h1>
|
||||
${summary.length ? `<div class="summary">${summary.map((line) => `<div>${line}</div>`).join('')}</div>` : ''}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
${CSV_HEADERS.map((header) => `<th>${header}</th>`).join('')}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${tableRows}</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
const printWindow = window.open('', '_blank')
|
||||
if (!printWindow) return
|
||||
|
||||
printWindow.document.open()
|
||||
printWindow.document.write(html)
|
||||
printWindow.document.close()
|
||||
printWindow.focus()
|
||||
window.setTimeout(() => {
|
||||
printWindow.print()
|
||||
printWindow.close()
|
||||
}, 300)
|
||||
}
|
||||
Reference in New Issue
Block a user