Files
crm.clientright.ru/DOC_TEMPLATE_EDITOR_UI.md
Fedor 01c4fe80b5 chore: snapshot current working tree changes
Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
2026-03-26 14:19:01 +03:00

19 KiB
Raw Blame History

🎨 ИНТЕРФЕЙС РЕДАКТОРА: ПАНЕЛЬ ПЕРЕМЕННЫХ + ONLYOFFICE

🎯 КОНЦЕПЦИЯ

Двухпанельный интерфейс:

  • Слева: Список полей и переменных (как в PDFMaker)
  • Справа: OnlyOffice редактор в iframe
  • При клике: Переменная вставляется в документ

📐 МАКЕТ ИНТЕРФЕЙСА

┌─────────────────────────────────────────────────────────────┐
│  Редактирование шаблона: Претензия          [💾 Сохранить] │
├──────────────────────┬──────────────────────────────────────┤
│                      │                                      │
│  ПЕРЕМЕННЫЕ          │  ONLYOFFICE РЕДАКТОР                 │
│                      │  ┌──────────────────────────────┐  │
│  [🔍 Поиск...]       │  │                              │  │
│                      │  │   ПРЕТЕНЗИЯ                  │  │
│  Прямые поля:        │  │                              │  │
│  ├─ projectname      │  │   Кому: УК "Жилищник"       │  │
│  ├─ createdtime      │  │   От: {CLIENT_NAME}         │  │
│  ├─ cf_1885          │  │                              │  │
│  └─ ...              │  │   Дата: {DATE}              │  │
│                      │  │                              │  │
│  Связанные модули:   │  │   Текст: {CLAIM_TEXT}       │  │
│  ├─ Contacts         │  │                              │  │
│  │  ├─ lastname      │  │   Сумма: {AMOUNT} ₽         │  │
│  │  └─ firstname     │  │                              │  │
│  ├─ Accounts         │  │                              │  │
│  │  └─ accountname   │  │                              │  │
│  └─ ...              │  │                              │  │
│                      │  │                              │  │
│  Функции:            │  └──────────────────────────────┘  │
│  ├─ formatDate()     │                                      │
│  ├─ formatCurrency() │                                      │
│  └─ ...              │                                      │
│                      │                                      │
│  [📋 Копировать переменную]                                 │
│                      │                                      │
└──────────────────────┴──────────────────────────────────────┘

🔧 РЕАЛИЗАЦИЯ

1. Структура View

class DocTemplate_Edit_View extends Vtiger_Index_View {
    
    public function process(Vtiger_Request $request) {
        $templateId = $request->get('record');
        $template = DocTemplate_Template_Model::getInstanceById($templateId);
        $module = $template->getModule();
        
        // Получить список полей (как в PDFMaker)
        $fieldsModel = new PDFMaker_Fields_Model();
        $moduleFields = $fieldsModel->getSelectModuleFields($module);
        
        // Получить URL для OnlyOffice
        $onlyOfficeUrl = $this->getOnlyOfficeEditorUrl($template);
        
        $viewer = $this->getViewer($request);
        $viewer->assign('MODULE_FIELDS', $moduleFields);
        $viewer->assign('ONLYOFFICE_URL', $onlyOfficeUrl);
        $viewer->assign('TEMPLATE', $template);
        $viewer->assign('MODULE', $module);
        
        $viewer->view('Edit.tpl', 'DocTemplate');
    }
    
    /**
     * Получить URL для OnlyOffice редактора
     */
    private function getOnlyOfficeEditorUrl($template) {
        $nextcloudUrl = 'https://office.clientright.ru:8443';
        $filename = $template->getFilename();
        $path = $template->getNextcloudPath();
        
        // URL для открытия в OnlyOffice через Nextcloud
        $fileUrl = $nextcloudUrl . '/remote.php/dav/files/admin' . $path . $filename;
        
        // Получить конфигурацию OnlyOffice Document Server
        $config = [
            'document' => [
                'fileType' => 'docx',
                'key' => md5($template->getId() . time()),
                'title' => $filename,
                'url' => $fileUrl
            ],
            'documentType' => 'text',
            'editorConfig' => [
                'mode' => 'edit',
                'callbackUrl' => 'https://crm.clientright.ru/index.php?module=DocTemplate&action=OnlyOfficeCallback'
            ]
        ];
        
        // URL OnlyOffice Document Server
        $onlyOfficeServer = 'https://onlyoffice.clientright.ru'; // или где у вас
        $editorUrl = $onlyOfficeServer . '/web-apps/apps/documenteditor/main/index.html';
        
        return $editorUrl . '?config=' . base64_encode(json_encode($config));
    }
}

2. Template (Edit.tpl)

<div class="doc-template-editor">
    <div class="row">
        <!-- Левая панель: Переменные -->
        <div class="col-md-3 variables-panel">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h4>Переменные</h4>
                    <input type="text" class="form-control" id="variable-search" 
                           placeholder="🔍 Поиск...">
                </div>
                <div class="panel-body" id="variables-list">
                    {* Прямые поля модуля *}
                    <div class="variables-group">
                        <h5>Прямые поля</h5>
                        <ul class="list-unstyled">
                            {foreach from=$MODULE_FIELDS key=group item=fields}
                                {foreach from=$fields key=varName item=varLabel}
                                    <li class="variable-item" 
                                        data-variable="{$varName}"
                                        data-label="{$varLabel}">
                                        <a href="javascript:void(0)" 
                                           onclick="DocTemplate.insertVariable('{$varName}')">
                                            <strong>{$varName}</strong><br>
                                            <small>{$varLabel}</small>
                                        </a>
                                    </li>
                                {/foreach}
                            {/foreach}
                        </ul>
                    </div>
                    
                    {* Связанные модули *}
                    <div class="variables-group">
                        <h5>Связанные модули</h5>
                        {* Здесь будут связанные модули *}
                    </div>
                    
                    {* Функции *}
                    <div class="variables-group">
                        <h5>Функции</h5>
                        <ul class="list-unstyled">
                            <li class="variable-item">
                                <a href="javascript:void(0)" 
                                   onclick="DocTemplate.insertFunction('formatDate')">
                                    formatDate()
                                </a>
                            </li>
                            <li class="variable-item">
                                <a href="javascript:void(0)" 
                                   onclick="DocTemplate.insertFunction('formatCurrency')">
                                    formatCurrency()
                                </a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
        
        <!-- Правая панель: OnlyOffice редактор -->
        <div class="col-md-9 editor-panel">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h4>Редактор документа</h4>
                    <button class="btn btn-primary" onclick="DocTemplate.saveTemplate()">
                        💾 Сохранить
                    </button>
                </div>
                <div class="panel-body" style="padding: 0;">
                    <iframe id="onlyoffice-editor" 
                            src="{$ONLYOFFICE_URL}"
                            width="100%" 
                            height="800px"
                            frameborder="0">
                    </iframe>
                </div>
            </div>
        </div>
    </div>
</div>

<style>
.variables-panel {
    max-height: 800px;
    overflow-y: auto;
}
.variable-item {
    padding: 8px;
    border-bottom: 1px solid #eee;
    cursor: pointer;
}
.variable-item:hover {
    background-color: #f5f5f5;
}
.variable-item a {
    color: #333;
    text-decoration: none;
}
.variable-item strong {
    color: #007bff;
}
</style>

3. JavaScript: Интеграция с OnlyOffice

DocTemplate = {
    
    onlyOfficeEditor: null,
    onlyOfficeAPI: null,
    
    /**
     * Инициализация после загрузки iframe
     */
    init: function() {
        // Ждем загрузки OnlyOffice
        window.addEventListener('message', function(event) {
            if (event.data && event.data.type === 'onlyoffice-ready') {
                DocTemplate.onlyOfficeAPI = event.data.api;
                DocTemplate.onlyOfficeEditor = event.data.editor;
            }
        });
        
        // Поиск по переменным
        jQuery('#variable-search').on('input', function() {
            var search = jQuery(this).val().toLowerCase();
            jQuery('.variable-item').each(function() {
                var text = jQuery(this).text().toLowerCase();
                if (text.indexOf(search) !== -1) {
                    jQuery(this).show();
                } else {
                    jQuery(this).hide();
                }
            });
        });
    },
    
    /**
     * Вставка переменной в OnlyOffice
     */
    insertVariable: function(variableName) {
        // Формат переменной: {VAR_NAME} или $MODULE_FIELDNAME$
        var variableText = '{' + variableName + '}';
        
        // Попытка вставить через OnlyOffice API
        if (DocTemplate.onlyOfficeAPI) {
            try {
                var oDocument = DocTemplate.onlyOfficeAPI.GetDocument();
                var oRange = oDocument.GetRange();
                
                // Вставить переменную в текущую позицию курсора
                oRange.InsertText(variableText);
            } catch (e) {
                console.error('OnlyOffice API error:', e);
                // Fallback: копировать в буфер обмена
                DocTemplate.copyToClipboard(variableText);
            }
        } else {
            // Fallback: копировать в буфер обмена
            DocTemplate.copyToClipboard(variableText);
            alert('Переменная скопирована в буфер обмена. Вставьте в документ (Ctrl+V)');
        }
    },
    
    /**
     * Вставка функции
     */
    insertFunction: function(functionName) {
        var functionText = '[FUNCTION|' + functionName + '|FUNCTION]';
        DocTemplate.insertVariable(functionText);
    },
    
    /**
     * Копирование в буфер обмена
     */
    copyToClipboard: function(text) {
        var textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
    },
    
    /**
     * Сохранение шаблона
     */
    saveTemplate: function() {
        if (!DocTemplate.onlyOfficeAPI) {
            alert('Редактор не готов. Подождите...');
            return;
        }
        
        // Получить содержимое документа из OnlyOffice
        var oDocument = DocTemplate.onlyOfficeAPI.GetDocument();
        var content = oDocument.GetContent();
        
        // Отправить на сервер для сохранения
        AppConnector.request({
            module: 'DocTemplate',
            action: 'SaveTemplateContent',
            record: jQuery('#recordId').val(),
            content: content
        }).then(function(response) {
            if (response.success) {
                Vtiger_Helper_Js.showPnotify('Шаблон сохранен');
                
                // Обновить маппинг (найти переменные в документе)
                DocTemplate.updateMapping();
            }
        });
    },
    
    /**
     * Обновление маппинга после сохранения
     */
    updateMapping: function() {
        AppConnector.request({
            module: 'DocTemplate',
            action: 'ExtractVariables',
            record: jQuery('#recordId').val()
        }).then(function(response) {
            if (response.success && response.result.variables.length > 0) {
                // Показать диалог настройки маппинга
                DocTemplate.showMappingDialog(response.result.variables);
            }
        });
    },
    
    /**
     * Показать диалог настройки маппинга
     */
    showMappingDialog: function(variables) {
        // Показать модальное окно с настройкой маппинга
        // (как в предыдущем варианте)
    }
};

// Инициализация при загрузке страницы
jQuery(document).ready(function() {
    DocTemplate.init();
});

🔗 ИНТЕГРАЦИЯ С ONLYOFFICE DOCUMENT SERVER

Вариант 1: Через Nextcloud (проще)

// Использовать встроенный OnlyOffice в Nextcloud
$onlyOfficeUrl = 'https://office.clientright.ru:8443/apps/onlyoffice/' . 
                 urlencode($path . $filename);

Преимущества:

  • Уже настроен
  • Автоматическое сохранение в Nextcloud
  • Не нужна отдельная настройка

Недостатки:

  • Сложнее получить доступ к API
  • Ограниченный контроль

Вариант 2: Прямая интеграция с OnlyOffice Document Server

// Настроить OnlyOffice Document Server отдельно
$onlyOfficeServer = 'https://onlyoffice.clientright.ru';

$config = [
    'document' => [
        'fileType' => 'docx',
        'key' => md5($templateId . time()),
        'title' => $filename,
        'url' => $nextcloudFileUrl  // URL файла в Nextcloud
    ],
    'documentType' => 'text',
    'editorConfig' => [
        'mode' => 'edit',
        'callbackUrl' => 'https://crm.clientright.ru/index.php?module=DocTemplate&action=OnlyOfficeCallback'
    ]
];

$editorUrl = $onlyOfficeServer . '/web-apps/apps/documenteditor/main/index.html';
$fullUrl = $editorUrl . '?config=' . base64_encode(json_encode($config));

Преимущества:

  • Полный контроль над API
  • Можно вставлять переменные программно
  • Больше возможностей

Недостатки:

  • Нужна настройка OnlyOffice Document Server
  • Сложнее реализация

🎯 РЕКОМЕНДУЕМЫЙ ПОДХОД

Гибридный вариант:

  1. Использовать OnlyOffice через Nextcloud (проще)
  2. Для вставки переменных: Копировать в буфер обмена + уведомление
  3. После сохранения: Автоматически парсить DOCX и настраивать маппинг

Алгоритм:

1. Пользователь кликает на переменную слева
   ↓
2. Переменная копируется в буфер обмена
   ↓
3. Показывается уведомление: "Переменная скопирована. Вставьте в документ (Ctrl+V)"
   ↓
4. Пользователь вставляет в OnlyOffice
   ↓
5. Сохраняет документ
   ↓
6. CRM автоматически находит переменные и настраивает маппинг

🔧 АЛЬТЕРНАТИВА: ПРЯМАЯ ВСТАВКА ЧЕРЕЗ API

Если OnlyOffice Document Server настроен отдельно:

insertVariable: function(variableName) {
    if (window.AscDesktopEditor) {
        // Desktop версия OnlyOffice
        var oDocument = Api.GetDocument();
        var oRange = oDocument.GetRange();
        oRange.InsertText('{' + variableName + '}');
    } else if (DocTemplate.onlyOfficeAPI) {
        // Web версия OnlyOffice
        var oDocument = DocTemplate.onlyOfficeAPI.GetDocument();
        var oRange = oDocument.GetRange();
        oRange.InsertText('{' + variableName + '}');
    } else {
        // Fallback: копирование в буфер
        DocTemplate.copyToClipboard('{' + variableName + '}');
    }
}

ИТОГОВОЕ РЕШЕНИЕ

Интерфейс:

  • Двухпанельный (переменные слева, редактор справа)
  • Клик по переменной → вставка в документ
  • Автоматическое обнаружение переменных после сохранения

Реализация:

  1. Начать с копирования в буфер обмена (проще)
  2. Потом добавить прямую вставку через API (если нужно)

Готов реализовать такой интерфейс! 🚀