bug fix in delete docs and start working on custome invoice design

This commit is contained in:
Hesabix 2025-08-09 08:21:31 +00:00
parent 8ead39e274
commit 5a12f2cbc5
17 changed files with 309197 additions and 2 deletions

View file

@ -299,6 +299,11 @@ class DirectHesabdariDoc extends AbstractController
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به سند'], 403);
}
$logs = $entityManager->getRepository(Log::class)->findBy(['doc' => $hesabdariDoc]);
foreach ($logs as $log) {
$log->setDoc(null);
$entityManager->persist($log);
}
$entityManager->remove($hesabdariDoc);
$entityManager->flush();
$log->insert('حسابداری', 'حذف سند حسابداری شماره ' . $hesabdariDoc->getCode(), $this->getUser(), $acc['bid'], $hesabdariDoc);

View file

@ -452,6 +452,15 @@ class PluginController extends AbstractController
'icon' => 'repservice.jpg',
'defaultOn' => null,
],
[
'name' => 'افزونه طراحی فاکتور اختصاصی',
'code' => 'custominvoice',
'timestamp' => '32104000',
'timelabel' => 'یک سال',
'price' => '200000',
'icon' => 'custominvoice.png',
'defaultOn' => null,
],
[
'name' => 'افزونه فروش اقساطی',
'code' => 'ghesta',

View file

@ -0,0 +1,30 @@
<?php
namespace App\Controller\Plugins;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\PlugGhestaDoc;
use App\Entity\PlugGhestaItem;
use App\Entity\HesabdariDoc;
use App\Entity\Person;
use App\Service\Access;
use App\Service\Provider;
use App\Service\Printers;
use App\Entity\PrintOptions;
use App\Service\Log;
use App\Entity\Business;
class PlugCustomInvoice extends AbstractController
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}

View file

@ -13,6 +13,7 @@
"@chenfengyuan/vue-countdown": "^2.1.3",
"@date-io/date-fns-jalali": "^3.2.0",
"@mdi/font": "^7.4.47",
"@monaco-editor/loader": "^1.5.0",
"@syncfusion/ej2-vue-dropdowns": "^29.1.38",
"@tiptap/extension-text-align": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7",
@ -38,6 +39,7 @@
"marked": "^16.1.0",
"maska": "^3.1.1",
"maz-ui": "^3.50.1",
"monaco-editor": "^0.52.2",
"pinia": "^3.0.2",
"sweetalert2": "^11.4.8",
"v-money3": "^3.24.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SimpleWorkerServer } from '../base/common/worker/simpleWorker.js';
import { EditorSimpleWorker } from './common/services/editorSimpleWorker.js';
import { EditorWorkerHost } from './common/services/editorWorkerHost.js';
let initialized = false;
export function initialize(foreignModule) {
if (initialized) {
return;
}
initialized = true;
const simpleWorker = new SimpleWorkerServer((msg) => {
globalThis.postMessage(msg);
}, (workerServer) => new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), foreignModule));
globalThis.onmessage = (e) => {
simpleWorker.onmessage(e.data);
};
}
globalThis.onmessage = (e) => {
// Ignore first message in this case and initialize if not yet initialized
if (!initialized) {
initialize(null);
}
};

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,282 @@
<template>
<div ref="editorContainer" class="monaco-editor-container" :style="{ height: height }"></div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as monaco from 'monaco-editor'
// تنظیمات MonacoEnvironment برای web workers
if (typeof self !== 'undefined' && !self.MonacoEnvironment) {
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
try {
// استفاده از web workers موجود در پوشه public
if (label === 'json') {
return '/monaco-editor/min/vs/language/json/json.worker.js'
}
if (label === 'css' || label === 'scss' || label === 'less') {
return '/monaco-editor/min/vs/language/css/css.worker.js'
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return '/monaco-editor/min/vs/language/html/html.worker.js'
}
if (label === 'typescript' || label === 'javascript') {
return '/monaco-editor/min/vs/language/typescript/ts.worker.js'
}
return '/monaco-editor/min/vs/editor/editor.worker.js'
} catch (error) {
console.warn('Monaco Editor worker not found, falling back to main thread:', error)
// Fallback to main thread if workers fail
return null
}
}
}
}
export default {
name: 'MonacoEditor',
props: {
modelValue: {
type: String,
default: ''
},
language: {
type: String,
default: 'html'
},
theme: {
type: String,
default: 'vs-dark'
},
height: {
type: String,
default: '400px'
},
readOnly: {
type: Boolean,
default: false
},
options: {
type: Object,
default: () => ({})
}
},
emits: ['update:modelValue', 'change'],
setup(props, { emit }) {
const editorContainer = ref(null)
let editor = null
const createEditor = () => {
if (!editorContainer.value) return
try {
// تنظیمات پیشفرض
const defaultOptions = {
value: props.modelValue,
language: props.language,
theme: props.theme,
readOnly: props.readOnly,
automaticLayout: true,
minimap: {
enabled: false
},
scrollBeyondLastLine: false,
fontSize: 14,
fontFamily: 'Consolas, "Courier New", monospace',
lineNumbers: 'on',
roundedSelection: false,
scrollbar: {
vertical: 'visible',
horizontal: 'visible'
},
folding: true,
wordWrap: 'on',
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: 'on',
tabCompletion: 'on',
wordBasedSuggestions: 'on',
parameterHints: {
enabled: true
},
autoIndent: 'full',
formatOnPaste: true,
formatOnType: false,
quickSuggestions: true,
hover: {
enabled: true
}
}
// ترکیب تنظیمات پیشفرض با تنظیمات سفارشی
const editorOptions = { ...defaultOptions, ...props.options }
editor = monaco.editor.create(editorContainer.value, editorOptions)
// اضافه کردن event listener برای تغییرات
editor.onDidChangeModelContent(() => {
const value = editor.getValue()
emit('update:modelValue', value)
emit('change', value)
})
// تنظیم زبان فارسی برای RTL
if (props.language === 'html') {
monaco.editor.setModelLanguage(editor.getModel(), 'html')
}
} catch (error) {
console.error('Error creating Monaco Editor:', error)
// Fallback to a simple textarea if Monaco fails
const textarea = document.createElement('textarea')
textarea.value = props.modelValue
textarea.style.width = '100%'
textarea.style.height = '100%'
textarea.style.fontFamily = 'Consolas, "Courier New", monospace'
textarea.style.fontSize = '14px'
textarea.style.border = 'none'
textarea.style.outline = 'none'
textarea.style.resize = 'none'
textarea.addEventListener('input', (e) => {
emit('update:modelValue', e.target.value)
emit('change', e.target.value)
})
editorContainer.value.appendChild(textarea)
}
}
const destroyEditor = () => {
if (editor) {
try {
editor.dispose()
} catch (error) {
console.warn('Error disposing Monaco Editor:', error)
}
editor = null
}
// Clean up fallback textarea if it exists
if (editorContainer.value) {
const textarea = editorContainer.value.querySelector('textarea')
if (textarea) {
textarea.remove()
}
}
}
// تماشای تغییرات props
watch(() => props.modelValue, (newValue) => {
if (editor && newValue !== editor.getValue()) {
editor.setValue(newValue)
}
})
watch(() => props.language, (newLanguage) => {
if (editor) {
monaco.editor.setModelLanguage(editor.getModel(), newLanguage)
}
})
watch(() => props.theme, (newTheme) => {
if (editor) {
monaco.editor.setTheme(newTheme)
}
})
watch(() => props.readOnly, (newReadOnly) => {
if (editor) {
editor.updateOptions({ readOnly: newReadOnly })
}
})
onMounted(() => {
createEditor()
})
onBeforeUnmount(() => {
destroyEditor()
})
// متدهای عمومی برای دسترسی از خارج
const getValue = () => {
if (editor) {
return editor.getValue()
}
// Fallback for textarea
if (editorContainer.value) {
const textarea = editorContainer.value.querySelector('textarea')
return textarea ? textarea.value : ''
}
return ''
}
const setValue = (value) => {
if (editor) {
editor.setValue(value)
} else if (editorContainer.value) {
// Fallback for textarea
const textarea = editorContainer.value.querySelector('textarea')
if (textarea) {
textarea.value = value
}
}
}
const focus = () => {
if (editor) {
editor.focus()
}
}
const getEditor = () => {
return editor
}
return {
editorContainer,
getValue,
setValue,
focus,
getEditor
}
}
}
</script>
<style scoped>
.monaco-editor-container {
width: 100%;
border: 1px solid #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
/* تنظیمات برای RTL */
.monaco-editor-container :deep(.monaco-editor) {
direction: ltr;
}
.monaco-editor-container :deep(.monaco-editor .margin) {
direction: ltr;
}
.monaco-editor-container :deep(.monaco-editor .monaco-editor-background) {
direction: ltr;
}
/* تنظیمات برای تم تاریک */
.monaco-editor-container :deep(.vs-dark) {
background-color: #1e1e1e;
}
/* تنظیمات برای تم روشن */
.monaco-editor-container :deep(.vs) {
background-color: #ffffff;
}
/* تنظیمات برای تم HC */
.monaco-editor-container :deep(.hc-black) {
background-color: #000000;
}
</style>

View file

@ -68,7 +68,7 @@ async function fetchAndCache(url, localStorageKey, defaultValue) {
export async function getSiteName() {
return fetchAndCache(
`${getApiUrl()}system/getname`,
`${getApiUrl()}/system/getname`,
KEYS.SITE_NAME,
DEFAULTS.SITE_NAME
);
@ -76,7 +76,7 @@ export async function getSiteName() {
export async function getSiteSlogan() {
return fetchAndCache(
`${getApiUrl()}system/getslogon`,
`${getApiUrl()}/system/getslogon`,
KEYS.SITE_SLOGON,
DEFAULTS.SITE_SLOGON
);

View file

@ -542,6 +542,24 @@ const router = createRouter({
component: () =>
import('../views/acc/plugins/taxsettings/doc.vue'),
},
{
path: 'plugins/custominvoice/intro',
name: 'custominvoice_intro',
component: () =>
import('../views/acc/plugins/custominvoice/intro.vue'),
},
{
path: 'plugins/custominvoice/templates',
name: 'custominvoice_templates',
component: () =>
import('../views/acc/plugins/custominvoice/templates.vue'),
},
{
path: 'plugins/custominvoice/template/mod/:id?',
name: 'custominvoice_template_form',
component: () =>
import('../views/acc/plugins/custominvoice/template-form.vue'),
},
{
path: 'business/logs',
name: 'business_logs',

View file

@ -197,6 +197,7 @@ export default {
{ path: '/acc/plugins/warranty', key: 'W', label: this.$t('drawer.warranty_serials'), ctrl: true, shift: true, permission: () => this.isPluginActive('warranty') && this.permissions.plugWarranty },
{ path: '/acc/plugins/tax/invoices/list', key: 'L', label: this.$t('drawer.tax_invoices'), ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('taxsettings') },
{ path: '/acc/plugins/tax/settings', key: 'T', label: this.$t('drawer.tax_settings'), ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('taxsettings') },
{ path: '/acc/plugins/custominvoice/templates', key: 'I', label: 'قالب‌های فاکتور', ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('custominvoice') },
];
},
restorePermissions(shortcuts) {
@ -899,6 +900,13 @@ export default {
</v-list-item-title>
</v-list-item>
</v-list-group>
<v-list-item v-show="isPluginActive('custominvoice') && permissions.settings" to="/acc/plugins/custominvoice/templates">
<template v-slot:prepend><v-icon icon="mdi-palette" color="primary"></v-icon></template>
<v-list-item-title>
قالبهای فاکتور
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/plugins/custominvoice/templates') }}</span>
</v-list-item-title>
</v-list-item>
<v-list-item class="text-dark" v-if="permissions.owner" to="/acc/sms/panel">
<template v-slot:prepend><v-icon icon="mdi-message-cog" color="primary"></v-icon></template>
<v-list-item-title>

View file

@ -0,0 +1,752 @@
<template>
<div class="plugin-intro">
<div class="intro-header">
<div class="plugin-icon">
<i class="fas fa-palette"></i>
</div>
<div class="plugin-info">
<h1>افزونه طراحی فاکتور اختصاصی</h1>
<p class="plugin-description">
طراحی و شخصیسازی فاکتورهای فروش با قالبهای اختصاصی و زیبا
</p>
<div class="plugin-version">
<span class="version-badge">نسخه 1.0.0</span>
<span v-if="isPluginActive('custominvoice')" class="status-badge active">فعال</span>
<RouterLink to="/acc/plugin-center/view-end/custominvoice" v-if="!isPluginActive('custominvoice')">
<span class="status-badge active text-white d-flex align-items-center">
<i class="fa fa-shopping-cart me-1"></i>
خرید
</span>
</RouterLink>
</div>
</div>
</div>
<div class="intro-content">
<div class="features-section">
<h2>امکانات افزونه</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-paint-brush"></i>
</div>
<h3>طراحی قالب اختصاصی</h3>
<p>ایجاد قالبهای فاکتور شخصیسازی شده با ابزارهای طراحی پیشرفته</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-image"></i>
</div>
<h3>افزودن لوگو و برندینگ</h3>
<p>قابلیت افزودن لوگو، رنگبندی و عناصر برندینگ به فاکتورها</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-font"></i>
</div>
<h3>تنظیم فونت و استایل</h3>
<p>انتخاب فونت، اندازه و رنگ متنها برای زیبایی بیشتر فاکتور</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-columns"></i>
</div>
<h3>چیدمان انعطافپذیر</h3>
<p>تنظیم چیدمان عناصر فاکتور با سیستم گرید پیشرفته</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-eye"></i>
</div>
<h3>پیشنمایش زنده</h3>
<p>مشاهده تغییرات فاکتور به صورت زنده در حین طراحی</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-download"></i>
</div>
<h3>خروجی چندگانه</h3>
<p>صدور فاکتور در فرمتهای PDF، HTML و تصویر با کیفیت بالا</p>
</div>
</div>
</div>
<div class="invoice-types-section">
<h2>انواع قالبهای پشتیبانی شده</h2>
<div class="invoice-types-grid">
<div class="invoice-type-card">
<div class="type-icon primary">
<i class="fas fa-file-invoice"></i>
</div>
<h4>قالب استاندارد</h4>
<p>قالب پیشفرض با طراحی مدرن و حرفهای</p>
</div>
<div class="invoice-type-card">
<div class="type-icon warning">
<i class="fas fa-star"></i>
</div>
<h4>قالب لوکس</h4>
<p>قالب ویژه با طراحی لوکس و گرانقیمت</p>
</div>
<div class="invoice-type-card">
<div class="type-icon info">
<i class="fas fa-briefcase"></i>
</div>
<h4>قالب تجاری</h4>
<p>قالب مناسب کسبوکارهای تجاری و شرکتی</p>
</div>
<div class="invoice-type-card">
<div class="type-icon danger">
<i class="fas fa-magic"></i>
</div>
<h4>قالب سفارشی</h4>
<p>طراحی کاملاً سفارشی از صفر تا صد</p>
</div>
</div>
</div>
<div class="validation-section">
<h2>ویژگیهای طراحی</h2>
<div class="validation-list">
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>پشتیبانی از فونتهای فارسی و انگلیسی</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>رنگبندی اختصاصی و پالت رنگی</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>افزودن تصاویر و لوگو با کیفیت بالا</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>تنظیم حاشیه و فاصلهگذاری</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>قابلیت چاپ با کیفیت بالا</span>
</div>
<div class="validation-item">
<i class="fas fa-check text-success"></i>
<span>پشتیبانی از RTL و LTR</span>
</div>
</div>
</div>
<div class="setup-section">
<h2>مراحل راهاندازی</h2>
<div class="setup-steps">
<div class="setup-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>انتخاب قالب پایه</h4>
<p>انتخاب یکی از قالبهای پیشفرض یا شروع از صفر</p>
</div>
</div>
<div class="setup-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>تنظیم اطلاعات شرکت</h4>
<p>وارد کردن لوگو، نام شرکت و اطلاعات تماس</p>
</div>
</div>
<div class="setup-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>شخصیسازی طراحی</h4>
<p>تنظیم رنگها، فونتها و چیدمان عناصر</p>
</div>
</div>
<div class="setup-step">
<div class="step-number">4</div>
<div class="step-content">
<h4>تست و انتشار</h4>
<p>تست قالب و فعالسازی برای استفاده</p>
</div>
</div>
</div>
</div>
<div class="compliance-section">
<h2>مزایای استفاده</h2>
<div class="compliance-grid">
<div class="compliance-item">
<i class="fas fa-rocket"></i>
<h4>سرعت بالا</h4>
<p>طراحی سریع و آسان قالبهای فاکتور</p>
</div>
<div class="compliance-item">
<i class="fas fa-mobile-alt"></i>
<h4>سازگار با موبایل</h4>
<p>نمایش مناسب در تمام دستگاهها</p>
</div>
<div class="compliance-item">
<i class="fas fa-save"></i>
<h4>ذخیره قالبها</h4>
<p>ذخیره و استفاده مجدد از قالبهای طراحی شده</p>
</div>
<div class="compliance-item">
<i class="fas fa-share-alt"></i>
<h4>اشتراکگذاری</h4>
<p>امکان اشتراکگذاری قالبها با سایر کاربران</p>
</div>
</div>
</div>
</div>
<div class="intro-footer">
<div v-if="isPluginActive('custominvoice')" class="action-buttons">
<router-link to="/acc/plugins/custominvoice/doc" class="btn btn-info">
<i class="fas fa-book"></i>
راهنمای کامل
</router-link>
<router-link to="/acc/plugins/custominvoice/settings" class="btn btn-primary">
<i class="fas fa-cog"></i>
تنظیمات افزونه
</router-link>
<router-link to="/acc/plugins/custominvoice/templates" class="btn btn-success">
<i class="fas fa-palette"></i>
مدیریت قالبها
</router-link>
</div>
<div style="margin-top: 20px; font-size: 0.75rem; color: #888; text-align: center;">
Developed by <a href="https://pirouz.xyz" target="_blank" style="color: #667eea; text-decoration: none;">Mohammad Rezai</a> 2025
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'CustomInvoiceIntro',
data() {
return {
pluginInfo: {
name: 'طراحی فاکتور اختصاصی',
version: '1.0.0',
description: 'طراحی و شخصی‌سازی فاکتورهای فروش با قالب‌های اختصاصی و زیبا'
},
plugins: {}
}
},
methods: {
isPluginActive(plugName) {
return this.plugins && this.plugins[plugName] !== undefined;
},
getSiteName() {
return localStorage.getItem('hesabix_site_name') || '{{ getSiteName() }}';
},
},
mounted() {
axios.post('/api/plugin/get/actives').then((response) => {
this.plugins = response.data;
});
if (this.$store) {
this.$store.commit('setPageTitle', 'طراحی فاکتور اختصاصی - معرفی افزونه')
}
}
}
</script>
<style scoped>
.plugin-intro {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.intro-header {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 40px;
padding: 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
color: white;
}
.plugin-icon {
font-size: 3rem;
background: rgba(255, 255, 255, 0.2);
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
flex-shrink: 0;
}
.plugin-info h1 {
margin: 0 0 10px 0;
font-size: 2.5rem;
font-weight: 700;
}
.plugin-description {
font-size: 1.1rem;
margin: 0 0 15px 0;
opacity: 0.9;
}
.plugin-version {
display: flex;
gap: 10px;
}
.version-badge, .status-badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
}
.version-badge {
background: rgba(255, 255, 255, 0.2);
}
.status-badge.active {
background: #28a745;
}
.intro-content {
display: flex;
flex-direction: column;
gap: 40px;
}
.features-section, .invoice-types-section, .validation-section, .setup-section, .compliance-section {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.features-section h2, .invoice-types-section h2, .validation-section h2, .setup-section h2, .compliance-section h2 {
margin: 0 0 25px 0;
font-size: 1.8rem;
color: #333;
border-bottom: 3px solid #667eea;
padding-bottom: 10px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 25px;
}
.feature-card {
padding: 25px;
border-radius: 12px;
background: #f8f9fa;
border-left: 4px solid #667eea;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.feature-icon {
font-size: 2rem;
color: #667eea;
margin-bottom: 15px;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(102, 126, 234, 0.1);
border-radius: 50%;
}
.feature-card h3 {
margin: 0 0 10px 0;
font-size: 1.3rem;
color: #333;
}
.feature-card p {
margin: 0;
color: #666;
line-height: 1.6;
}
.invoice-types-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.invoice-type-card {
text-align: center;
padding: 25px;
border-radius: 12px;
background: #f8f9fa;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.invoice-type-card:hover {
border-color: #667eea;
transform: translateY(-3px);
}
.type-icon {
font-size: 2.5rem;
margin-bottom: 15px;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
margin: 0 auto 15px auto;
}
.type-icon.primary { background: #e3f2fd; color: #1976d2; }
.type-icon.warning { background: #fff3e0; color: #f57c00; }
.type-icon.info { background: #e8f5e8; color: #388e3c; }
.type-icon.danger { background: #ffebee; color: #d32f2f; }
.invoice-type-card h4 {
margin: 0 0 10px 0;
font-size: 1.2rem;
color: #333;
}
.invoice-type-card p {
margin: 0;
color: #666;
font-size: 0.9rem;
}
.validation-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 15px;
}
.validation-item {
display: flex;
align-items: center;
gap: 12px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #28a745;
}
.validation-item i {
font-size: 1.2rem;
}
.setup-steps {
display: flex;
flex-direction: column;
gap: 20px;
}
.setup-step {
display: flex;
align-items: flex-start;
gap: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: 12px;
border-left: 4px solid #667eea;
}
.step-number {
background: #667eea;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 1.2rem;
flex-shrink: 0;
}
.step-content h4 {
margin: 0 0 8px 0;
color: #333;
font-size: 1.2rem;
}
.step-content p {
margin: 0;
color: #666;
line-height: 1.5;
}
.compliance-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.compliance-item {
text-align: center;
padding: 25px;
background: #f8f9fa;
border-radius: 12px;
border-top: 4px solid #667eea;
}
.compliance-item i {
font-size: 2.5rem;
color: #667eea;
margin-bottom: 15px;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(102, 126, 234, 0.1);
border-radius: 50%;
margin: 0 auto 15px auto;
}
.compliance-item h4 {
margin: 0 0 10px 0;
color: #333;
font-size: 1.1rem;
}
.compliance-item p {
margin: 0;
color: #666;
font-size: 0.9rem;
line-height: 1.5;
}
.intro-footer {
text-align: center;
margin-top: 40px;
padding: 30px;
background: white;
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.action-buttons {
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
transform: translateY(-2px);
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #218838;
transform: translateY(-2px);
}
.btn-info {
background: #17a2b8;
color: white;
}
.btn-info:hover {
background: #138496;
transform: translateY(-2px);
}
@media (max-width: 768px) {
.plugin-intro {
padding: 15px;
}
.intro-header {
flex-direction: column;
text-align: center;
padding: 20px;
gap: 15px;
}
.plugin-icon {
width: 60px;
height: 60px;
font-size: 2rem;
}
.plugin-info h1 {
font-size: 1.8rem;
}
.plugin-description {
font-size: 1rem;
}
.features-section, .invoice-types-section, .validation-section, .setup-section, .compliance-section {
padding: 20px;
}
.features-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.feature-card {
padding: 20px;
}
.feature-icon {
width: 50px;
height: 50px;
font-size: 1.5rem;
}
.invoice-types-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.invoice-type-card {
padding: 20px;
}
.type-icon {
width: 60px;
height: 60px;
font-size: 2rem;
}
.validation-list {
grid-template-columns: 1fr;
gap: 10px;
}
.validation-item {
padding: 12px;
}
.setup-step {
padding: 15px;
gap: 15px;
}
.step-number {
width: 35px;
height: 35px;
font-size: 1rem;
}
.compliance-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.compliance-item {
padding: 20px;
}
.compliance-item i {
width: 60px;
height: 60px;
font-size: 2rem;
}
.action-buttons {
flex-direction: column;
gap: 15px;
}
.btn {
justify-content: center;
padding: 15px 20px;
font-size: 1rem;
}
}
@media (max-width: 480px) {
.plugin-intro {
padding: 10px;
}
.intro-header {
padding: 15px;
}
.plugin-info h1 {
font-size: 1.5rem;
}
.plugin-description {
font-size: 0.9rem;
}
.features-section h2, .invoice-types-section h2, .validation-section h2, .setup-section h2, .compliance-section h2 {
font-size: 1.4rem;
}
.feature-card h3 {
font-size: 1.1rem;
}
.invoice-type-card h4 {
font-size: 1rem;
}
.step-content h4 {
font-size: 1rem;
}
.compliance-item h4 {
font-size: 1rem;
}
}
</style>

View file

@ -0,0 +1,895 @@
<template>
<v-toolbar color="toolbar" :title="isEditMode ? 'ویرایش قالب فاکتور' : 'ایجاد قالب جدید'">
<template v-slot:prepend>
<v-tooltip :text="$t('dialog.back')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="goBack()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
</template>
</v-tooltip>
</template>
<v-spacer></v-spacer>
<v-tooltip :text="isEditMode ? 'بروزرسانی قالب' : 'ذخیره قالب'" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="mdi-content-save" color="primary" @click="saveTemplate" :loading="saving"></v-btn>
</template>
</v-tooltip>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon color="green">
<v-icon>mdi-eye</v-icon>
<v-tooltip activator="parent" text="پیش‌نمایش قالب" location="bottom" />
</v-btn>
</template>
<v-list>
<v-list-subheader color="primary">پیشنمایش قالب</v-list-subheader>
<v-list-item class="text-dark" title="پیش‌نمایش در مرورگر" @click="previewTemplate">
<template v-slot:prepend>
<v-icon color="blue-darken-4" icon="mdi-eye"></v-icon>
</template>
</v-list-item>
<v-list-item class="text-dark" title="پیش‌نمایش چاپ" @click="previewPrint">
<template v-slot:prepend>
<v-icon color="orange-darken-4" icon="mdi-printer"></v-icon>
</template>
</v-list-item>
</v-list>
</v-menu>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon color="purple">
<v-icon>mdi-palette</v-icon>
<v-tooltip activator="parent" text="قالب‌های آماده" location="bottom" />
</v-btn>
</template>
<v-list>
<v-list-subheader color="primary">قالبهای آماده</v-list-subheader>
<v-list-item class="text-dark" title="قالب استاندارد" @click="loadTemplate('standard')">
<template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-file-document"></v-icon>
</template>
</v-list-item>
<v-list-item class="text-dark" title="قالب لوکس" @click="loadTemplate('luxury')">
<template v-slot:prepend>
<v-icon color="amber-darken-4" icon="mdi-star"></v-icon>
</template>
</v-list-item>
<v-list-item class="text-dark" title="قالب تجاری" @click="loadTemplate('business')">
<template v-slot:prepend>
<v-icon color="blue-darken-4" icon="mdi-briefcase"></v-icon>
</template>
</v-list-item>
</v-list>
</v-menu>
<v-tooltip text="فرمت کردن کد" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="mdi-format-align-left" color="info" @click="formatCode" />
</template>
</v-tooltip>
<v-tooltip text="تنظیمات قالب" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="mdi-cog" color="primary" @click="showSettings = true" />
</template>
</v-tooltip>
</v-toolbar>
<v-container>
<v-tabs v-model="activeTab" color="primary" class="template-tabs">
<v-tab value="form">فرم قالب</v-tab>
<v-tab value="help">راهنما و آموزش</v-tab>
<v-tab value="settings">تنظیمات ویرایشگر</v-tab>
</v-tabs>
<v-window v-model="activeTab" class="template-window">
<!-- فرم قالب -->
<v-window-item value="form">
<div class="template-form-fields">
<v-row>
<v-col cols="12" md="6">
<v-text-field v-model="templateData.name" label="نام قالب" placeholder="مثال: قالب استاندارد شرکت"
variant="outlined" required :rules="[v => !!v || 'نام قالب الزامی است']" />
</v-col>
<v-col cols="12" md="6">
<v-select v-model="templateData.isPublic" label="وضعیت عمومی" :items="publicOptions" variant="outlined"
required />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-label class="text-subtitle-2 mb-2 d-block">کد قالب</v-label>
<MonacoEditor
v-model="templateData.code"
:language="editorSettings.language"
:theme="editorSettings.theme"
height="500px"
:options="monacoOptions"
@change="onCodeChange"
ref="monacoEditor"
/>
<div v-if="codeError" class="text-error text-caption mt-1">
{{ codeError }}
</div>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<div class="form-actions">
<v-btn color="primary" size="large" @click="saveTemplate" :loading="saving">
<v-icon left>mdi-content-save</v-icon>
{{ isEditMode ? 'بروزرسانی قالب' : 'ذخیره قالب' }}
</v-btn>
<v-btn color="secondary" size="large" @click="goBack" class="ml-3">
<v-icon left>mdi-arrow-right</v-icon>
بازگشت
</v-btn>
</div>
</v-col>
</v-row>
</div>
</v-window-item>
<!-- راهنما و آموزش -->
<v-window-item value="help">
<v-container class="help-content">
<v-card class="help-section mb-6" variant="outlined">
<v-card-title class="d-flex align-center">
<v-icon color="primary" class="mr-3">mdi-information</v-icon>
راهنمای ایجاد قالب
</v-card-title>
<v-card-text>
<v-list class="help-steps">
<v-list-item class="help-step mb-4">
<template v-slot:prepend>
<v-avatar color="primary" size="30" class="step-number">
<span class="text-white font-weight-bold">1</span>
</v-avatar>
</template>
<v-list-item-title class="text-h6 mb-2">نام قالب</v-list-item-title>
<v-list-item-subtitle>
نام مناسبی برای قالب خود انتخاب کنید که نشاندهنده نوع و کاربرد آن باشد.
</v-list-item-subtitle>
</v-list-item>
<v-list-item class="help-step mb-4">
<template v-slot:prepend>
<v-avatar color="primary" size="30" class="step-number">
<span class="text-white font-weight-bold">2</span>
</v-avatar>
</template>
<v-list-item-title class="text-h6 mb-2">وضعیت عمومی</v-list-item-title>
<v-list-item-subtitle>
اگر قالب را عمومی انتخاب کنید، سایر کاربران نیز میتوانند از آن استفاده کنند.
</v-list-item-subtitle>
</v-list-item>
<v-list-item class="help-step mb-4">
<template v-slot:prepend>
<v-avatar color="primary" size="30" class="step-number">
<span class="text-white font-weight-bold">3</span>
</v-avatar>
</template>
<v-list-item-title class="text-h6 mb-2">کد قالب</v-list-item-title>
<v-list-item-subtitle>
کد HTML و CSS قالب خود را در این قسمت وارد کنید. میتوانید از متغیرهای زیر استفاده کنید:
</v-list-item-subtitle>
<v-card class="code-variables mt-3" variant="outlined">
<v-card-text class="font-family-monospace">
<v-chip color="error" size="small" class="mr-2 mb-1" v-text="'{{ company_name }}'"></v-chip> - نام شرکت<br>
<v-chip color="error" size="small" class="mr-2 mb-1" v-text="'{{ invoice_number }}'"></v-chip> - شماره
فاکتور<br>
<v-chip color="error" size="small" class="mr-2 mb-1" v-text="'{{ invoice_date }}'"></v-chip> - تاریخ
فاکتور<br>
<v-chip color="error" size="small" class="mr-2 mb-1" v-text="'{{ customer_name }}'"></v-chip> - نام
مشتری<br>
<v-chip color="error" size="small" class="mr-2 mb-1" v-text="'{{ total_amount }}'"></v-chip> - مبلغ کل<br>
<v-chip color="error" size="small" class="mr-2 mb-1" v-text="'{{ items_list }}'"></v-chip> - لیست اقلام
</v-card-text>
</v-card>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
<v-card class="help-section mb-6" variant="outlined">
<v-card-title class="d-flex align-center">
<v-icon color="primary" class="mr-3">mdi-code-tags</v-icon>
نمونه کد قالب
</v-card-title>
<v-card-text>
<v-card class="code-example" variant="outlined">
<v-card-text class="font-family-monospace">
<pre class="text-body-2"><code v-text="codeExample"></code></pre>
</v-card-text>
</v-card>
</v-card-text>
</v-card>
<v-card class="help-section" variant="outlined">
<v-card-title class="d-flex align-center">
<v-icon color="primary" class="mr-3">mdi-lightbulb</v-icon>
نکات مهم
</v-card-title>
<v-card-text>
<v-list class="tips-list">
<v-list-item class="tips-item">
<template v-slot:prepend>
<v-icon color="success" class="mr-3">mdi-check</v-icon>
</template>
<v-list-item-title>از CSS برای زیبایی قالب استفاده کنید</v-list-item-title>
</v-list-item>
<v-list-item class="tips-item">
<template v-slot:prepend>
<v-icon color="success" class="mr-3">mdi-check</v-icon>
</template>
<v-list-item-title>مطمئن شوید که قالب در چاپ به خوبی نمایش داده میشود</v-list-item-title>
</v-list-item>
<v-list-item class="tips-item">
<template v-slot:prepend>
<v-icon color="success" class="mr-3">mdi-check</v-icon>
</template>
<v-list-item-title>از فونتهای فارسی استفاده کنید</v-list-item-title>
</v-list-item>
<v-list-item class="tips-item">
<template v-slot:prepend>
<v-icon color="success" class="mr-3">mdi-check</v-icon>
</template>
<v-list-item-title>رنگبندی مناسب برای خوانایی انتخاب کنید</v-list-item-title>
</v-list-item>
<v-list-item class="tips-item">
<template v-slot:prepend>
<v-icon color="success" class="mr-3">mdi-check</v-icon>
</template>
<v-list-item-title>قبل از ذخیره، قالب را تست کنید</v-list-item-title>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-container>
</v-window-item>
<!-- تنظیمات ویرایشگر -->
<v-window-item value="settings">
<v-container class="settings-content">
<v-card class="settings-section mb-6" variant="outlined">
<v-card-title class="d-flex align-center">
<v-icon color="primary" class="mr-3">mdi-cog</v-icon>
تنظیمات ویرایشگر کد
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-slider v-model="editorSettings.fontSize" label="اندازه فونت" min="10" max="20" step="1" thumb-label
color="primary" @update:model-value="applySettings" />
</v-col>
<v-col cols="12" md="6">
<v-switch v-model="editorSettings.wordWrap" label="شکستن خودکار خط" color="primary" @update:model-value="applySettings" />
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-switch v-model="editorSettings.minimap" label="نمایش مینی‌مپ" color="primary" @update:model-value="applySettings" />
</v-col>
<v-col cols="12" md="6">
<v-switch v-model="editorSettings.autoComplete" label="تکمیل خودکار کد" color="primary" @update:model-value="applySettings" />
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-select v-model="editorSettings.theme" label="تم ویرایشگر" :items="themeOptions"
variant="outlined" @update:model-value="applySettings" />
</v-col>
<v-col cols="12" md="6">
<v-select v-model="editorSettings.language" label="زبان کد" :items="languageOptions"
variant="outlined" @update:model-value="applySettings" />
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-card class="settings-section" variant="outlined">
<v-card-title class="d-flex align-center">
<v-icon color="primary" class="mr-3">mdi-palette</v-icon>
تنظیمات ظاهری
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-color-picker v-model="appearanceSettings.primaryColor" label="رنگ اصلی" hide-inputs flat />
</v-col>
<v-col cols="12" md="6">
<v-color-picker v-model="appearanceSettings.secondaryColor" label="رنگ ثانویه" hide-inputs flat />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-btn color="primary" @click="applySettings" :loading="applyingSettings">
<v-icon left>mdi-check</v-icon>
اعمال تنظیمات
</v-btn>
<v-btn color="secondary" @click="resetSettings" class="ml-3">
<v-icon left>mdi-refresh</v-icon>
بازنشانی
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-container>
</v-window-item>
</v-window>
</v-container>
</template>
<script>
import MonacoEditor from '@/components/MonacoEditor.vue'
export default {
name: 'CustomInvoiceTemplateForm',
components: {
MonacoEditor
},
data() {
return {
isEditMode: false,
templateId: null,
activeTab: 'form',
saving: false,
showSettings: false,
templateData: {
name: '',
isPublic: false,
code: ''
},
codeError: '',
publicOptions: [
{ title: 'خصوصی', value: false },
{ title: 'عمومی', value: true }
],
readyTemplates: {
standard: '<div class="invoice-template">\n <div class="header">\n <h1>{{company_name}}</h1>\n <p>فاکتور شماره: {{invoice_number}}</p>\n </div>\n <div class="content">\n <p>مشتری: {{customer_name}}</p>\n <p>تاریخ: {{invoice_date}}</p>\n <div class="items">\n {{items_list}}\n </div>\n <div class="total">\n مبلغ کل: {{total_amount}}\n </div>\n </div>\n</div>',
luxury: '<div class="invoice-template luxury">\n <div class="header">\n <h1>{{company_name}}</h1>\n <p>فاکتور شماره: {{invoice_number}}</p>\n </div>\n <div class="content">\n <p>مشتری: {{customer_name}}</p>\n <p>تاریخ: {{invoice_date}}</p>\n <div class="items">\n {{items_list}}\n </div>\n <div class="total">\n مبلغ کل: {{total_amount}}\n </div>\n </div>\n</div>',
business: '<div class="invoice-template business">\n <div class="header">\n <h1>{{company_name}}</h1>\n <p>فاکتور شماره: {{invoice_number}}</p>\n </div>\n <div class="content">\n <p>مشتری: {{customer_name}}</p>\n <p>تاریخ: {{invoice_date}}</p>\n <div class="items">\n {{items_list}}\n </div>\n <div class="total">\n مبلغ کل: {{total_amount}}\n </div>\n </div>\n</div>'
},
editorSettings: {
fontSize: 14,
wordWrap: true,
minimap: false,
autoComplete: true,
theme: 'vs-dark',
language: 'html'
},
appearanceSettings: {
primaryColor: '#667eea',
secondaryColor: '#42b883'
},
applyingSettings: false,
themeOptions: [
{ title: 'تاریک', value: 'vs-dark' },
{ title: 'روشن', value: 'vs-light' },
{ title: 'تاریک بالا', value: 'vs-dark-hc' },
{ title: 'روشن بالا', value: 'vs-light-hc' }
],
languageOptions: [
{ title: 'HTML', value: 'html' },
{ title: 'CSS', value: 'css' },
{ title: 'JavaScript', value: 'javascript' },
{ title: 'TypeScript', value: 'typescript' }
]
}
},
computed: {
monacoOptions() {
return {
fontSize: this.editorSettings.fontSize,
wordWrap: this.editorSettings.wordWrap ? 'on' : 'off',
minimap: { enabled: this.editorSettings.minimap },
scrollBeyondLastLine: false,
automaticLayout: true,
suggestOnTriggerCharacters: this.editorSettings.autoComplete,
quickSuggestions: this.editorSettings.autoComplete,
hover: { enabled: this.editorSettings.autoComplete },
parameterHints: { enabled: this.editorSettings.autoComplete },
wordBasedSuggestions: this.editorSettings.autoComplete ? 'on' : 'off',
acceptSuggestionOnEnter: this.editorSettings.autoComplete ? 'on' : 'off',
tabCompletion: this.editorSettings.autoComplete ? 'on' : 'off'
}
},
codeExample() {
return `<div class="invoice-template">
<div class="header">
<h1>{{company_name}}</h1>
<p>فاکتور شماره: {{invoice_number}}</p>
</div>
<div class="content">
<p>مشتری: {{customer_name}}</p>
<p>تاریخ: {{invoice_date}}</p>
<div class="items">
{{items_list}}
</div>
<div class="total">
مبلغ کل: {{total_amount}}
</div>
</div>
</div>`
}
},
methods: {
saveTemplate() {
// Validation
if (!this.templateData.name || !this.templateData.code) {
// Show error message
return;
}
this.saving = true;
// Simulate API call
setTimeout(() => {
this.saving = false;
// Navigate back to templates list
this.$router.push('/acc/plugins/custominvoice/templates');
}, 1000);
},
goBack() {
this.$router.push('/acc/plugins/custominvoice/templates');
},
previewTemplate() {
// Open template preview in new window
const previewWindow = window.open('', '_blank');
const head = previewWindow.document.createElement('head');
const title = previewWindow.document.createElement('title');
title.textContent = 'پیش‌نمایش قالب';
head.appendChild(title);
const style = previewWindow.document.createElement('style');
style.textContent = 'body { font-family: Arial, sans-serif; margin: 20px; } .invoice-template { border: 1px solid #ccc; padding: 20px; } .header { text-align: center; margin-bottom: 20px; } .content { margin-top: 20px; } .items { margin: 20px 0; } .total { font-weight: bold; text-align: right; }';
head.appendChild(style);
const body = previewWindow.document.createElement('body');
body.innerHTML = this.templateData.code;
previewWindow.document.appendChild(head);
previewWindow.document.appendChild(body);
},
previewPrint() {
// Open print preview
const printWindow = window.open('', '_blank');
const head = printWindow.document.createElement('head');
const title = printWindow.document.createElement('title');
title.textContent = 'پیش‌نمایش چاپ';
head.appendChild(title);
const style = printWindow.document.createElement('style');
style.textContent = 'body { font-family: Arial, sans-serif; margin: 20px; } .invoice-template { border: 1px solid #ccc; padding: 20px; } .header { text-align: center; margin-bottom: 20px; } .content { margin-top: 20px; } .items { margin: 20px 0; } .total { font-weight: bold; text-align: right; } @media print { body { margin: 0; } .invoice-template { border: none; } }';
head.appendChild(style);
const body = printWindow.document.createElement('body');
body.innerHTML = this.templateData.code;
const script = printWindow.document.createElement('script');
script.textContent = 'window.onload = function() { window.print(); }';
body.appendChild(script);
printWindow.document.appendChild(head);
printWindow.document.appendChild(body);
},
loadTemplate(type) {
if (this.readyTemplates[type]) {
this.templateData.code = this.readyTemplates[type];
this.templateData.name = `قالب ${type === 'standard' ? 'استاندارد' : type === 'luxury' ? 'لوکس' : 'تجاری'}`;
}
},
onCodeChange(value) {
this.codeError = '';
try {
// Attempt to parse the HTML to check for errors
new DOMParser().parseFromString(value, 'text/html');
} catch (e) {
this.codeError = 'کد HTML معتبر نیست. لطفاً خطاهای آن را برطرف کنید.';
}
},
formatCode() {
try {
// Simple HTML formatting
let formatted = this.templateData.code
.replace(/>\s*</g, '>\n<')
.replace(/\n\s*\n/g, '\n')
.replace(/^\s+|\s+$/gm, '');
// Add proper indentation
const lines = formatted.split('\n');
let indentLevel = 0;
const indentSize = 2;
formatted = lines.map(line => {
const trimmed = line.trim();
if (trimmed.startsWith('</')) {
indentLevel = Math.max(0, indentLevel - 1);
}
const indented = ' '.repeat(indentLevel * indentSize) + trimmed;
if (trimmed.startsWith('<') && !trimmed.startsWith('</') && !trimmed.endsWith('/>')) {
indentLevel++;
}
return indented;
}).join('\n');
this.templateData.code = formatted;
} catch (e) {
console.error('خطا در فرمت کردن کد:', e);
}
},
applySettings() {
// جلوگیری از اعمال مکرر تنظیمات
if (this.applyingSettings) return;
this.applyingSettings = true;
// اعمال تنظیمات روی Monaco Editor
if (this.$refs.monacoEditor && this.$refs.monacoEditor.editor) {
const editor = this.$refs.monacoEditor.editor;
// اعمال تنظیمات جدید
editor.updateOptions({
fontSize: this.editorSettings.fontSize,
wordWrap: this.editorSettings.wordWrap ? 'on' : 'off',
minimap: { enabled: this.editorSettings.minimap },
suggestOnTriggerCharacters: this.editorSettings.autoComplete,
quickSuggestions: this.editorSettings.autoComplete,
hover: { enabled: this.editorSettings.autoComplete },
parameterHints: { enabled: this.editorSettings.autoComplete },
wordBasedSuggestions: this.editorSettings.autoComplete ? 'on' : 'off',
acceptSuggestionOnEnter: this.editorSettings.autoComplete ? 'on' : 'off',
tabCompletion: this.editorSettings.autoComplete ? 'on' : 'off'
});
// تغییر تم
if (window.monaco) {
window.monaco.editor.setTheme(this.editorSettings.theme);
}
}
// ذخیره تنظیمات در localStorage
localStorage.setItem('monacoEditorSettings', JSON.stringify(this.editorSettings));
localStorage.setItem('appearanceSettings', JSON.stringify(this.appearanceSettings));
setTimeout(() => {
this.applyingSettings = false;
// نمایش پیام موفقیت فقط در صورت نیاز
if (this.$toast) {
this.$toast.success('تنظیمات با موفقیت اعمال شد.');
}
}, 300);
},
resetSettings() {
// بازنشانی تنظیمات به مقادیر پیشفرض
this.editorSettings = {
fontSize: 14,
wordWrap: true,
minimap: false,
autoComplete: true,
theme: 'vs-dark',
language: 'html'
};
this.appearanceSettings = {
primaryColor: '#667eea',
secondaryColor: '#42b883'
};
// اعمال تنظیمات بازنشانی شده روی Monaco Editor
if (this.$refs.monacoEditor && this.$refs.monacoEditor.editor) {
const editor = this.$refs.monacoEditor.editor;
editor.updateOptions({
fontSize: this.editorSettings.fontSize,
wordWrap: this.editorSettings.wordWrap ? 'on' : 'off',
minimap: { enabled: this.editorSettings.minimap },
suggestOnTriggerCharacters: this.editorSettings.autoComplete,
quickSuggestions: this.editorSettings.autoComplete,
hover: { enabled: this.editorSettings.autoComplete },
parameterHints: { enabled: this.editorSettings.autoComplete },
wordBasedSuggestions: this.editorSettings.autoComplete ? 'on' : 'off',
acceptSuggestionOnEnter: this.editorSettings.autoComplete ? 'on' : 'off',
tabCompletion: this.editorSettings.autoComplete ? 'on' : 'off'
});
// تغییر تم
if (window.monaco) {
window.monaco.editor.setTheme(this.editorSettings.theme);
}
}
// پاک کردن تنظیمات از localStorage
localStorage.removeItem('monacoEditorSettings');
localStorage.removeItem('appearanceSettings');
this.$toast?.success('تنظیمات با موفقیت بازنشانی شد.');
},
loadSavedSettings() {
try {
// بارگذاری تنظیمات Monaco Editor
const savedEditorSettings = localStorage.getItem('monacoEditorSettings');
if (savedEditorSettings) {
this.editorSettings = { ...this.editorSettings, ...JSON.parse(savedEditorSettings) };
}
// بارگذاری تنظیمات ظاهری
const savedAppearanceSettings = localStorage.getItem('appearanceSettings');
if (savedAppearanceSettings) {
this.appearanceSettings = { ...this.appearanceSettings, ...JSON.parse(savedAppearanceSettings) };
}
} catch (error) {
console.error('خطا در بارگذاری تنظیمات:', error);
}
}
},
mounted() {
// Check if we're in edit mode by checking the route parameter
this.templateId = this.$route.params.id;
this.isEditMode = !!this.templateId;
// بارگذاری تنظیمات ذخیره شده از localStorage
this.loadSavedSettings();
if (this.$store) {
this.$store.commit('setPageTitle', this.isEditMode ? 'ویرایش قالب فاکتور' : 'ایجاد قالب جدید')
}
}
}
</script>
<style scoped>
.template-form-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.template-form-content {
background: white;
border-radius: 15px;
padding: 40px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
min-height: 400px;
}
.template-tabs {
margin-bottom: 30px;
}
.template-window {
min-height: 400px;
}
.template-form-fields {
padding: 20px 0;
}
.form-actions {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 30px;
}
.help-content {
padding: 20px 0;
}
.settings-content {
padding: 20px 0;
}
.help-section {
margin-bottom: 40px;
}
.help-steps {
display: flex;
flex-direction: column;
gap: 20px;
}
.help-step {
display: flex;
align-items: flex-start;
gap: 15px;
padding: 20px;
background: #f8f9fa;
border-radius: 12px;
border-left: 4px solid #667eea;
}
.step-number {
background: #667eea;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 1rem;
flex-shrink: 0;
}
.code-variables {
background: #f1f3f4;
padding: 15px;
border-radius: 8px;
margin-top: 10px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.code-example {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-top: 15px;
}
.code-example pre {
margin: 0;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.4;
color: #333;
}
.code-example code {
color: #d93025;
}
.tips-list {
list-style: none;
padding: 0;
margin: 0;
}
.tips-item {
padding: 10px 0;
border-bottom: 1px solid #e0e0e0;
position: relative;
padding-left: 25px;
}
.tips-item:last-child {
border-bottom: none;
}
.template-form-placeholder {
text-align: center;
padding: 60px 20px;
}
.placeholder-icon {
font-size: 4rem;
color: #667eea;
margin-bottom: 20px;
}
.template-form-placeholder h3 {
font-size: 1.8rem;
color: #333;
margin: 0 0 15px 0;
}
.template-form-placeholder p {
font-size: 1.1rem;
color: #666;
margin: 0 0 40px 0;
line-height: 1.6;
}
.placeholder-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
max-width: 800px;
margin: 0 auto;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #667eea;
transition: all 0.3s ease;
}
.feature-item:hover {
background: #e9ecef;
transform: translateY(-2px);
}
.feature-item i {
font-size: 1.2rem;
color: #667eea;
}
.feature-item span {
font-weight: 600;
color: #333;
}
@media (max-width: 768px) {
.template-form-container {
padding: 15px;
}
.template-form-header {
padding: 20px;
}
.template-form-header h1 {
font-size: 1.8rem;
}
.template-form-description {
font-size: 1rem;
}
.template-form-content {
padding: 20px;
}
.template-form-placeholder {
padding: 40px 15px;
}
.placeholder-icon {
font-size: 3rem;
}
.template-form-placeholder h3 {
font-size: 1.4rem;
}
.template-form-placeholder p {
font-size: 1rem;
}
.placeholder-features {
grid-template-columns: 1fr;
gap: 15px;
}
}
@media (max-width: 480px) {
.template-form-header h1 {
font-size: 1.5rem;
}
.template-form-description {
font-size: 0.9rem;
}
.template-form-placeholder h3 {
font-size: 1.2rem;
}
.template-form-placeholder p {
font-size: 0.9rem;
}
}
</style>

View file

@ -0,0 +1,224 @@
<template>
<div class="templates-container">
<div class="templates-header">
<h1>مدیریت قالبهای فاکتور</h1>
<p class="templates-description">
طراحی و مدیریت قالبهای اختصاصی فاکتورهای فروش
</p>
</div>
<div class="templates-content">
<div class="templates-placeholder">
<div class="placeholder-icon">
<i class="fas fa-palette"></i>
</div>
<h3>بخش قالبهای فاکتور</h3>
<p>این بخش در حال توسعه است و به زودی در دسترس خواهد بود.</p>
<div class="placeholder-features">
<router-link to="/acc/plugins/custominvoice/template/mod/" class="feature-item-link">
<div class="feature-item">
<i class="fas fa-plus"></i>
<span>ایجاد قالب جدید</span>
</div>
</router-link>
<div class="feature-item">
<i class="fas fa-edit"></i>
<span>ویرایش قالبها</span>
</div>
<div class="feature-item">
<i class="fas fa-eye"></i>
<span>پیشنمایش قالبها</span>
</div>
<div class="feature-item">
<i class="fas fa-download"></i>
<span>صدور قالبها</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CustomInvoiceTemplates',
data() {
return {
// Data will be added here
}
},
methods: {
// Methods will be added here
},
mounted() {
if (this.$store) {
this.$store.commit('setPageTitle', 'مدیریت قالب‌های فاکتور')
}
}
}
</script>
<style scoped>
.templates-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.templates-header {
text-align: center;
margin-bottom: 40px;
padding: 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
color: white;
}
.templates-header h1 {
margin: 0 0 15px 0;
font-size: 2.5rem;
font-weight: 700;
}
.templates-description {
font-size: 1.1rem;
margin: 0;
opacity: 0.9;
}
.templates-content {
background: white;
border-radius: 15px;
padding: 40px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
min-height: 400px;
}
.templates-placeholder {
text-align: center;
padding: 60px 20px;
}
.placeholder-icon {
font-size: 4rem;
color: #667eea;
margin-bottom: 20px;
}
.templates-placeholder h3 {
font-size: 1.8rem;
color: #333;
margin: 0 0 15px 0;
}
.templates-placeholder p {
font-size: 1.1rem;
color: #666;
margin: 0 0 40px 0;
line-height: 1.6;
}
.placeholder-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
max-width: 600px;
margin: 0 auto;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #667eea;
transition: all 0.3s ease;
}
.feature-item:hover {
background: #e9ecef;
transform: translateY(-2px);
}
.feature-item i {
font-size: 1.2rem;
color: #667eea;
}
.feature-item span {
font-weight: 600;
color: #333;
}
.feature-item-link {
text-decoration: none;
color: inherit;
}
.feature-item-link:hover {
text-decoration: none;
}
@media (max-width: 768px) {
.templates-container {
padding: 15px;
}
.templates-header {
padding: 20px;
}
.templates-header h1 {
font-size: 1.8rem;
}
.templates-description {
font-size: 1rem;
}
.templates-content {
padding: 20px;
}
.templates-placeholder {
padding: 40px 15px;
}
.placeholder-icon {
font-size: 3rem;
}
.templates-placeholder h3 {
font-size: 1.4rem;
}
.templates-placeholder p {
font-size: 1rem;
}
.placeholder-features {
grid-template-columns: 1fr;
gap: 15px;
}
}
@media (max-width: 480px) {
.templates-header h1 {
font-size: 1.5rem;
}
.templates-description {
font-size: 0.9rem;
}
.templates-placeholder h3 {
font-size: 1.2rem;
}
.templates-placeholder p {
font-size: 0.9rem;
}
}
</style>