Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
19 KiB
19 KiB
🎨 ИНТЕРФЕЙС РЕДАКТОРА: ПАНЕЛЬ ПЕРЕМЕННЫХ + 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
- ❌ Сложнее реализация
🎯 РЕКОМЕНДУЕМЫЙ ПОДХОД
Гибридный вариант:
- Использовать OnlyOffice через Nextcloud (проще)
- Для вставки переменных: Копировать в буфер обмена + уведомление
- После сохранения: Автоматически парсить 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 + '}');
}
}
✅ ИТОГОВОЕ РЕШЕНИЕ
Интерфейс:
- ✅ Двухпанельный (переменные слева, редактор справа)
- ✅ Клик по переменной → вставка в документ
- ✅ Автоматическое обнаружение переменных после сохранения
Реализация:
- Начать с копирования в буфер обмена (проще)
- Потом добавить прямую вставку через API (если нужно)
Готов реализовать такой интерфейс! 🚀