hesabixCore/webUI/src/components/LivePreview.vue

295 lines
8.4 KiB
Vue
Raw Normal View History

<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>