295 lines
8.4 KiB
Vue
295 lines
8.4 KiB
Vue
<template>
|
|
<div class="live-preview">
|
|
<v-dialog v-model="showPreview" max-width="1200px" fullscreen>
|
|
<v-card>
|
|
<v-toolbar color="primary" dark>
|
|
<v-btn icon @click="showPreview = false">
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
<v-toolbar-title>پیشنمایش قالب</v-toolbar-title>
|
|
<v-spacer></v-spacer>
|
|
<v-btn @click="printPreview" prepend-icon="mdi-printer">چاپ</v-btn>
|
|
<v-btn @click="downloadPDF" prepend-icon="mdi-file-pdf-box">دانلود PDF</v-btn>
|
|
</v-toolbar>
|
|
|
|
<v-card-text class="preview-content">
|
|
<div class="preview-toolbar">
|
|
<v-select
|
|
v-model="selectedPaper"
|
|
label="اندازه کاغذ"
|
|
:items="paperSizes"
|
|
variant="outlined"
|
|
density="compact"
|
|
style="max-width: 200px"
|
|
/>
|
|
<v-switch
|
|
v-model="showGrid"
|
|
label="نمایش گرید"
|
|
color="primary"
|
|
/>
|
|
<v-switch
|
|
v-model="showMargins"
|
|
label="نمایش حاشیه"
|
|
color="primary"
|
|
/>
|
|
</div>
|
|
|
|
<div class="preview-container" :class="{ 'show-grid': showGrid, 'show-margins': showMargins }">
|
|
<div
|
|
class="preview-frame"
|
|
:class="selectedPaper"
|
|
v-html="renderedTemplate"
|
|
></div>
|
|
</div>
|
|
</v-card-text>
|
|
</v-card>
|
|
</v-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'LivePreview',
|
|
props: {
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
templateCode: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
},
|
|
emits: ['update:modelValue', 'print', 'download-pdf'],
|
|
data() {
|
|
return {
|
|
selectedPaper: 'A4',
|
|
showGrid: false,
|
|
showMargins: true,
|
|
paperSizes: [
|
|
{ title: 'A4', value: 'A4' },
|
|
{ title: 'A4 Landscape', value: 'A4-L' },
|
|
{ title: 'A5', value: 'A5' },
|
|
{ title: 'Letter', value: 'Letter' },
|
|
{ title: 'Legal', value: 'Legal' }
|
|
],
|
|
sampleData: {
|
|
business: {
|
|
name: 'شرکت نمونه',
|
|
tel: '021-12345678',
|
|
mobile: '09123456789',
|
|
address: 'تهران، خیابان نمونه، پلاک 123',
|
|
shenasemeli: '1234567890',
|
|
codeeghtesadi: '12345678901'
|
|
},
|
|
doc: {
|
|
code: 'INV-001',
|
|
date: '1402/12/15',
|
|
amount: 1500000,
|
|
money: { shortName: 'ریال' }
|
|
},
|
|
person: {
|
|
name: 'مشتری نمونه',
|
|
mobile: '09123456789',
|
|
tel: '021-98765432',
|
|
address: 'آدرس مشتری نمونه'
|
|
},
|
|
rows: [
|
|
{
|
|
commodity: { name: 'کالای نمونه 1', code: 'ITEM-001', unit: { name: 'عدد' } },
|
|
commodityCount: 2,
|
|
des: 'توضیحات کالای نمونه 1',
|
|
bs: 500000,
|
|
tax: 45000,
|
|
discount: 50000,
|
|
showPercentDiscount: false,
|
|
discountPercent: 10
|
|
},
|
|
{
|
|
commodity: { name: 'کالای نمونه 2', code: 'ITEM-002', unit: { name: 'عدد' } },
|
|
commodityCount: 1,
|
|
des: 'توضیحات کالای نمونه 2',
|
|
bs: 300000,
|
|
tax: 27000,
|
|
discount: 0,
|
|
showPercentDiscount: true,
|
|
discountPercent: 0
|
|
}
|
|
],
|
|
discount: 50000,
|
|
transfer: 25000,
|
|
note: 'یادداشت نمونه برای فاکتور'
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
showPreview: {
|
|
get() {
|
|
return this.modelValue
|
|
},
|
|
set(value) {
|
|
this.$emit('update:modelValue', value)
|
|
}
|
|
},
|
|
renderedTemplate() {
|
|
if (!this.templateCode) return '<div>قالب خالی است</div>'
|
|
|
|
try {
|
|
// Simple template rendering - in production, use a proper Twig engine
|
|
let rendered = this.templateCode
|
|
|
|
// Replace basic variables
|
|
rendered = rendered.replace(/\{\{\s*business\.name\s*\}\}/g, this.sampleData.business.name)
|
|
rendered = rendered.replace(/\{\{\s*business\.tel\s*\}\}/g, this.sampleData.business.tel)
|
|
rendered = rendered.replace(/\{\{\s*business\.address\s*\}\}/g, this.sampleData.business.address)
|
|
rendered = rendered.replace(/\{\{\s*doc\.code\s*\}\}/g, this.sampleData.doc.code)
|
|
rendered = rendered.replace(/\{\{\s*doc\.date\s*\}\}/g, this.sampleData.doc.date)
|
|
rendered = rendered.replace(/\{\{\s*person\.name\s*\}\}/g, this.sampleData.person.name)
|
|
rendered = rendered.replace(/\{\{\s*person\.mobile\s*\}\}/g, this.sampleData.person.mobile)
|
|
rendered = rendered.replace(/\{\{\s*discount\s*\}\}/g, this.sampleData.discount.toLocaleString('fa-IR'))
|
|
rendered = rendered.replace(/\{\{\s*transfer\s*\}\}/g, this.sampleData.transfer.toLocaleString('fa-IR'))
|
|
rendered = rendered.replace(/\{\{\s*note\s*\}\}/g, this.sampleData.note)
|
|
|
|
// Replace for loops
|
|
const forLoopRegex = /\{%\s*for\s+(\w+)\s+in\s+rows\s*%\}([\s\S]*?)\{%\s*endfor\s*%\}/g
|
|
rendered = rendered.replace(forLoopRegex, (match, variable, content) => {
|
|
let result = ''
|
|
this.sampleData.rows.forEach((row, index) => {
|
|
let rowContent = content
|
|
rowContent = rowContent.replace(/\{\{\s*loop\.index\s*\}\}/g, index + 1)
|
|
rowContent = rowContent.replace(/\{\{\s*item\.commodity\.name\s*\}\}/g, row.commodity.name)
|
|
rowContent = rowContent.replace(/\{\{\s*item\.commodityCount\s*\}\}/g, row.commodityCount)
|
|
rowContent = rowContent.replace(/\{\{\s*item\.des\s*\}\}/g, row.des)
|
|
rowContent = rowContent.replace(/\{\{\s*item\.bs\s*\}\}/g, row.bs.toLocaleString('fa-IR'))
|
|
rowContent = rowContent.replace(/\{\{\s*item\.tax\s*\}\}/g, row.tax.toLocaleString('fa-IR'))
|
|
rowContent = rowContent.replace(/\{\{\s*item\.discount\s*\}\}/g, row.discount.toLocaleString('fa-IR'))
|
|
result += rowContent
|
|
})
|
|
return result
|
|
})
|
|
|
|
// Replace if conditions
|
|
rendered = rendered.replace(/\{%\s*if\s+person\s*%\}([\s\S]*?)\{%\s*endif\s*%\}/g, '$1')
|
|
|
|
return rendered
|
|
} catch (error) {
|
|
console.error('Template rendering error:', error)
|
|
return `<div style="color: red; padding: 20px;">خطا در رندر قالب: ${error.message}</div>`
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
printPreview() {
|
|
this.$emit('print', this.renderedTemplate)
|
|
},
|
|
|
|
downloadPDF() {
|
|
this.$emit('download-pdf', this.renderedTemplate)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.live-preview {
|
|
/* Component styles */
|
|
}
|
|
|
|
.preview-content {
|
|
height: calc(100vh - 64px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0;
|
|
}
|
|
|
|
.preview-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
padding: 16px;
|
|
background: #f5f5f5;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
.preview-container {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
padding: 20px;
|
|
background: #f0f0f0;
|
|
overflow: auto;
|
|
}
|
|
|
|
.preview-frame {
|
|
background: white;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* Paper sizes */
|
|
.preview-frame.A4 {
|
|
width: 210mm;
|
|
min-height: 297mm;
|
|
padding: 20mm;
|
|
}
|
|
|
|
.preview-frame.A4-L {
|
|
width: 297mm;
|
|
min-height: 210mm;
|
|
padding: 20mm;
|
|
}
|
|
|
|
.preview-frame.A5 {
|
|
width: 148mm;
|
|
min-height: 210mm;
|
|
padding: 15mm;
|
|
}
|
|
|
|
.preview-frame.Letter {
|
|
width: 216mm;
|
|
min-height: 279mm;
|
|
padding: 20mm;
|
|
}
|
|
|
|
.preview-frame.Legal {
|
|
width: 216mm;
|
|
min-height: 356mm;
|
|
padding: 20mm;
|
|
}
|
|
|
|
/* Grid and margins */
|
|
.preview-container.show-grid .preview-frame {
|
|
background-image:
|
|
linear-gradient(rgba(0,0,0,0.1) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px);
|
|
background-size: 10mm 10mm;
|
|
}
|
|
|
|
.preview-container.show-margins .preview-frame {
|
|
border: 2px dashed #ccc;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 1200px) {
|
|
.preview-frame.A4,
|
|
.preview-frame.A4-L {
|
|
transform: scale(0.8);
|
|
transform-origin: top center;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.preview-frame.A4,
|
|
.preview-frame.A4-L {
|
|
transform: scale(0.6);
|
|
transform-origin: top center;
|
|
}
|
|
|
|
.preview-toolbar {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 10px;
|
|
}
|
|
}
|
|
</style> |