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

484 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🎨 ИНТЕРФЕЙС РЕДАКТОРА: ПАНЕЛЬ ПЕРЕМЕННЫХ + ONLYOFFICE
## 🎯 КОНЦЕПЦИЯ
**Двухпанельный интерфейс:**
- **Слева:** Список полей и переменных (как в PDFMaker)
- **Справа:** OnlyOffice редактор в iframe
- **При клике:** Переменная вставляется в документ
---
## 📐 МАКЕТ ИНТЕРФЕЙСА
```
┌─────────────────────────────────────────────────────────────┐
│ Редактирование шаблона: Претензия [💾 Сохранить] │
├──────────────────────┬──────────────────────────────────────┤
│ │ │
│ ПЕРЕМЕННЫЕ │ ONLYOFFICE РЕДАКТОР │
│ │ ┌──────────────────────────────┐ │
│ [🔍 Поиск...] │ │ │ │
│ │ │ ПРЕТЕНЗИЯ │ │
│ Прямые поля: │ │ │ │
│ ├─ projectname │ │ Кому: УК "Жилищник" │ │
│ ├─ createdtime │ │ От: {CLIENT_NAME} │ │
│ ├─ cf_1885 │ │ │ │
│ └─ ... │ │ Дата: {DATE} │ │
│ │ │ │ │
│ Связанные модули: │ │ Текст: {CLAIM_TEXT} │ │
│ ├─ Contacts │ │ │ │
│ │ ├─ lastname │ │ Сумма: {AMOUNT} ₽ │ │
│ │ └─ firstname │ │ │ │
│ ├─ Accounts │ │ │ │
│ │ └─ accountname │ │ │ │
│ └─ ... │ │ │ │
│ │ │ │ │
│ Функции: │ └──────────────────────────────┘ │
│ ├─ formatDate() │ │
│ ├─ formatCurrency() │ │
│ └─ ... │ │
│ │ │
│ [📋 Копировать переменную] │
│ │ │
└──────────────────────┴──────────────────────────────────────┘
```
---
## 🔧 РЕАЛИЗАЦИЯ
### **1. Структура View**
```php
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)**
```html
<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**
```javascript
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 (проще)**
```php
// Использовать встроенный OnlyOffice в Nextcloud
$onlyOfficeUrl = 'https://office.clientright.ru:8443/apps/onlyoffice/' .
urlencode($path . $filename);
```
**Преимущества:**
- ✅ Уже настроен
- ✅ Автоматическое сохранение в Nextcloud
-Не нужна отдельная настройка
**Недостатки:**
- ❌ Сложнее получить доступ к API
- ❌ Ограниченный контроль
---
### **Вариант 2: Прямая интеграция с OnlyOffice Document Server**
```php
// Настроить 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 настроен отдельно:
```javascript
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 (если нужно)
**Готов реализовать такой интерфейс!** 🚀