bug fix in delete docs and start working on custome invoice design
This commit is contained in:
parent
8ead39e274
commit
5a12f2cbc5
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
|
30
hesabixCore/src/Controller/Plugins/PlugCustomInvoice.php
Normal file
30
hesabixCore/src/Controller/Plugins/PlugCustomInvoice.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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",
|
||||
|
|
BIN
webUI/public/img/plugins/custominvoice.png
Normal file
BIN
webUI/public/img/plugins/custominvoice.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 710 KiB |
26
webUI/public/monaco-editor/min/vs/editor/editor.worker.js
Normal file
26
webUI/public/monaco-editor/min/vs/editor/editor.worker.js
Normal 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);
|
||||
}
|
||||
};
|
40031
webUI/public/monaco-editor/min/vs/language/css/css.worker.js
Normal file
40031
webUI/public/monaco-editor/min/vs/language/css/css.worker.js
Normal file
File diff suppressed because it is too large
Load diff
16207
webUI/public/monaco-editor/min/vs/language/html/html.worker.js
Normal file
16207
webUI/public/monaco-editor/min/vs/language/html/html.worker.js
Normal file
File diff suppressed because one or more lines are too long
7861
webUI/public/monaco-editor/min/vs/language/json/json.worker.js
Normal file
7861
webUI/public/monaco-editor/min/vs/language/json/json.worker.js
Normal file
File diff suppressed because it is too large
Load diff
242845
webUI/public/monaco-editor/min/vs/language/typescript/ts.worker.js
Normal file
242845
webUI/public/monaco-editor/min/vs/language/typescript/ts.worker.js
Normal file
File diff suppressed because one or more lines are too long
282
webUI/src/components/MonacoEditor.vue
Normal file
282
webUI/src/components/MonacoEditor.vue
Normal 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>
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
752
webUI/src/views/acc/plugins/custominvoice/intro.vue
Normal file
752
webUI/src/views/acc/plugins/custominvoice/intro.vue
Normal 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>
|
895
webUI/src/views/acc/plugins/custominvoice/template-form.vue
Normal file
895
webUI/src/views/acc/plugins/custominvoice/template-form.vue
Normal 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>
|
224
webUI/src/views/acc/plugins/custominvoice/templates.vue
Normal file
224
webUI/src/views/acc/plugins/custominvoice/templates.vue
Normal 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>
|
Loading…
Reference in a new issue