Здравствуйте.
Меня зовут Андрей. Работаю я в государственном вузе. И, как водится, в такого рода учреждениях люди, которые занимаются компьютерами, занимаются ими в широком смысле слова.
Первая часть этой истории находится тут. Там можно почерпнуть некоторую информацию о том, что из себя представляет описываемая мной система. Хотя бы в части устройства базы данных. А база это наше все!
Я не отношу себя к реальным программистам, так как не было опыта работы в команде со всеми вытекающими. По этому у меня своеобразный стиль изложения. Я смотрю на систему целиком. Иногда страдаю от синдрома самозванца. Но так или иначе моя система работает. Значит, что-то да умею.
Итак. Как говорится "И пришла пандемия"!!! От начальства была получено простое краткое письмо - надо чтобы все работало удаленно. Возражения не принимаются.
Причем надо было это сделать буквально в течении 2-3 недель. И, как говорится, без отрыва от производства. Все прочее никто не отменял.
На тот момент я вообще никогда не писал для веба. Тем более веб это понятие довольно растяжимое. Это и верстка и логика работы фронта И бэка. Впрочем тот, кто понимает, тот понимает :-)
Опыта в этом у меня было очень мало. Но надо было буквально ворваться в web разработку. Учитывая сроки.
Люблю иногда применять иносказания и аллегории. Правда редко кто понимает. По этому охарактеризую данный процесс так: процесс штурма веба у меня походил на быстрое восхождение по лестнице, разбивая ноги и колени, лишь бы добраться до следующего пролета и оглянуться назад. Главное добраться до пролета, промежуточного безопасного пункта :-)
Естественно я много до этого читал по теме веба, что то щупал, но читать одно, а делать другое. Это абсолютно разные вещи.
Ремарка. Пишу эту статью, как и предыдущую, по памяти. Так что код для меня, частично, генерит DeepSeek. Эх. Был бы тогда он :-)
Сначала я впал в прострацию. И, полагаю, это норма в той ситуации. Потом начал рыть как ко всему этому подступиться. Собственно нужен бэк и фронт. На слуху был PHP для бэка. Поставил PHP на комп. Набросал пару скриптов, которые получали данные с базы и отдавали на фронт. «Бросать» скрипты было очень «больно». Во первых PHP ни разу не щупал. Во-вторых, скрипты писал в Notepad++:-). В обыкновенном блокноте нет подсветки синтаксиса, а тогда она нужна была как воздух.
Дико прозвучит, но отладка PHP была устроена так - скрипт на сервере, дергаю урл, смотрю ошибки. Да, страшно звучит, но времени на полноценную настройку окружения не было от слова вообще. Может кто то скажет, что настроить все это можно без особых проблем и быстро — спорить не буду. Может оно и так.
Так что первая версия бэка для ухода в онлайн была на PHP.
Параллельно начал думать что делать с фронтом. Не знаю как, но в итоге я вышел на ExtJs от Sencha. Наверно подкупило меня тогда в ней то, что, судя по сайту, с ее помощью можно делать "корпоративные приложения". И так же то, что там есть такой объект, как хранилище. Нечто похожее на TPFibDataset в Delphi. Плюсом у меня не было времени на изучение css и верстки. Да и выбор инструмента надо было делать быстро. Кстати система Минфина Электронный Бюджет написана тоже на ExtJs!
Собственно что требовалось от фронта? Если совсем утрировать, то необходимо было отображать всякого рода журналы. И плюсом необходима была некая сущность, которую можно назвать документ. Его визуальное представление и логика получения, обработки и сохранения данных.
Сначала о журналах. Была у меня такая отдельная сущность в Win системе. Собственно что от них требуется? Получить данные с сервера и отобразить их в неком гриде. Далее приведу небольшой абстрактный пример реализации. Данные в ExtJs представляются в виде модели. Хранятся эти данные в виде моделей в сторах.
Скрытый текст
Ext.onReady(function() {
Ext.define('studentsModel', {
extend: 'Ext.data.Model',
fields: ['pk','f','i','o','sex','birthday'],
idProperty: 'pk'
});
var store = Ext.create('Ext.data.Store', {
model: 'studentsModel',
proxy: {
type: 'ajax',
url: '/vuz/data/studentsList',
reader: {
type: 'json',
root: 'data'
}
},
autoLoad: true
});
var grid = Ext.create('Ext.grid.Panel', {
store: store,
columns: [
{
text : 'Фамилия',
dataIndex: 'f'
},
{
text : 'Имя',
dataIndex: 'i'
},
{
text : 'Отчество',
dataIndex: 'o'
},
{
text : 'День рождения',
dataIndex: 'birthday'
},
],
height: 350,
width: 600,
title: 'Студенты',
renderTo: 'grid4'
});
});
Тут мы после удачной загрузки всего фреймворка создаем модель данных, хранилище и отрисовываем грид с данными в html элемент grid4. После инициализации стора (autoLoad: true) запрос уйдет на url /vuz/data/studentsList. Ожидается, что будет возвращен ответ в формате json и корневой элемент с данными будет называться data. Уникальный ключ у полученной записи должен содержаться в проперти с именем, указанным в idProperty. По умолчанию ExtJs считает уникальным ключем проперть с именем id.
Можно отрисовать грид и в панель, которая может быть, например, частью какого то другого элемента (например табличная часть формы документа). Код примерно такой
Скрытый текст
// Создаем grid
var GridPanel = Ext.create('Ext.grid.Panel', {
store: store,
columns: [{
text: 'Фамилия',
dataIndex: 'f'
},
{
text: 'Имя',
dataIndex: 'i'
},
{
text: 'Отчество',
dataIndex: 'o'
},
{
text: 'День рождения',
dataIndex: 'birthday'
}
],
height: 350,
width: 600,
title: 'Студенты'
});
// Создаем панель и добавляем в нее grid
var panel = Ext.create('Ext.panel.Panel', {
title: 'Контейнер для таблицы студентов',
width: 650,
height: 400,
layout: 'fit',
items: GridPanel,
renderTo: Ext.getBody() // рендерим в body (можно указать ID элемента)
});
Небольшое пояснение. В коде выше мы отрисовали грид в панель, а саму панель в html элемент тела страницы. У Ext.grid.Panel в columns мы указываем какие колонки будут у таблицы. Text и dataIndex это заголовок колонки и поле откуда брать данные в объекте store. У панели есть свойство items. По сути это children. Те это тот компонент, который будет отображаться в этой самой панели.
В Win системе у меня присутствовало множество различных журналов документов и много всякого рода списков. Соответственно набор колонок у каждого журнала свой. Жестко кодировать конфигурации для гридов - не вариант (но сначала ринулся в эту сторону). Очень долго и очень объемно по количеству кода, который придется тянуть на клиента. Плюсом у меня в win системе было понятие-сущность метаданных. И вся конфигурация объектов хранилась в соответствующих таблицах в базе.
Может я тогда заблуждался, хотя думаю нет, но для каждого журнала необходимо было иметь грид с конкретной конфигураций колонок, иметь задекларированный в фреймворке объект стора и задекларированный объект модели данных стора. По этому родился код вроде этого. Опять же - пишу по памяти, с некоторой помощью DeepSeek.
Скрытый текст
function createDynamicModel (modelName, dataUrl, fieldsConfig) {
// Преобразуем простую конфигурацию полей в формат ExtJS
var extFields = fieldsConfig.map(function (field) {
return {
name: field.name,
и прочие параметры
};
}, this);
// Создаем модель
return Ext.define('MyApp.model.' + modelName, {
extend: 'Ext.data.Model',
fields: extFields,
// Можно добавить proxy, если он указан в конфигурации
proxy: {
type: 'ajax',
url: dataUrl,
reader: {
type: 'json',
rootProperty: 'data'
}
}
});
}
function createDynamicStore (storeName, model, storeConfig) {
var config = Ext.apply({
storeId: 'Store.' + storeName,
model: model,
autoLoad: false,
pageSize: 25
}, storeConfig || {});
return Ext.create('Ext.data.Store', config);
}
А вот так можно этот код использовать
Скрытый текст
var serverConfig = {
fields: [
{name: 'pk', title:'', type: 'int', visible:false, width:10, dataIndex :0},
{name: 'f', title:'Фамилия', type: 'string', visible:true, width:100, dataIndex :1 },
{name: 'i', title:'Имя', type: 'string', visible: true, width:100, dataIndex :2 },
{name: 'o', title:'Отчество', type: 'string', visible: true, width:100, dataIndex :3 },
],
storeConfig: {
autoLoad: false,
proxy: {
url: '/api/students'
}
},
modelName: 'student'
};
// Создаем модель и store
var studentModel = createDynamicModel(serverConfig.modelName, serverConfig.fields)
var studentStore = createDynamicStore(serverConfig.modelName, studentModel , serverConfig.storeConfig);
// Или получить их позже по имени:
var studentModel = Ext.ModelManager.getModel('MyApp.model.'+ serverConfig.modelName);
var studentStore = Ext.getStore('Store.'+ serverConfig.modelName);
В итоге мы имеем зарегистрированную модель и стор. Все данные, необходимые для их генерации запрашиваются с сервера. Останется только на основании serverConfig сгенерировать конфигурацию грида, который отображает данные. Это можно сделать примерно так.
Скрытый текст
Ext.define('DynamicApp.view.DynamicGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.dynamicgrid',
initComponent: function () {
// Сначала загружаем конфигурацию с сервера
Ext.Ajax.request({
url: '/api/config',
method: 'GET',
scope: this,
success: function (response) {
var config = Ext.decode(response.responseText);
this.createGrid(config);
},
failure: function () {
Ext.Msg.alert('Ошибка', 'Не удалось загрузить конфигурацию');
}
});
this.callParent();
},
createGrid: function (config) {
var studentModel = createDynamicModel(config.modelName, config.fields)
var studentStore = createDynamicStore(config.modelName, studentModel, config.storeConfig);
// Обновляем модель в хранилище
store.updateModelConfig(config.fields);
// Создаем конфигурацию колонок
var columns = this.buildColumns(config.fields);
// Обновляем конфигурацию грида
Ext.apply(this, {
store: studentStore,
columns: columns,
title: config.title || 'Динамический грид'
});
// Если грид уже отрендерен, нужно обновить его
if (this.rendered) {
this.reconfigure(studentStore, columns);
}
},
buildColumns: function (columnConfigs) {
return Ext.Array.map(columnConfigs, function (col) {
return {
text: col.title,
dataIndex: col.dataIndex,
flex: col.flex || 1,
sortable: col.sortable !== false
};
});
}
});
Так что довольно быстро (естественно быстро это понятие относительное ) я решил проблему отображения всевозможных списков. Это журналы, отчеты.
Но, помимо отчетов и всякого рода списков-журналов, есть еще и документы. А данные документов можно представить в виде двух сторов. Стор шапки и стор тела. Да еще, помимо того, что данные надо получать с сервера, их надо еще и отправлять !
Для этого модель можно переписать так.
Скрытый текст
Ext.define('studentHeaderModel', {
extend: 'Ext.data.Model',
fields: ['pk', 'f', 'i', 'o', 'sex', 'birthday'],
proxy: {
type: 'ajax', // Используем AJAX вместо REST
url: '/vuz/data/studentInfo', // Базовый URL API
extraParams: { // Статичные параметры (можно переопределить в load())
pk: null // Пока не знаем pk, но задаём структуру
},
reader: {
type: 'json',
rootProperty: 'data'
},
writer: {
type: 'json',
writeAllFields: true // Отправлять все поля, даже если не изменены
}
}
});
В модели reader и writer ответственны за работу с одной единственной записью. Так как модель по сути определяет один объект-запись. Получается она полезна в контексте документа. По крайней мере для работы с данными шапки документа. Так как шапка представлена одной записью-объектом. Параметр extraParams ответственен за передачу параметров на сервер в случае, если нам с сервера надо получить какую то конкретную запись.
Далее определим store для записей шапки документа. И определимся как с ним работать в случае запроса данных с сервера
Скрытый текст
var studentStore = Ext.create('Ext.data.Store', {
model: 'studentHeaderModel',
autoLoad: true
});
const student = Ext.create('studentHeaderModel', {
pk: 42
}); // Создаем модель-объект
student.getProxy().setExtraParam('pk', student.get(' pk ')); // Устанавливаем pk в параметры
student.load(); // Отправит GET /vuz/data/ studentInfo?pk=42
//или же передаем параметр для запроса так
student.load({
params: {
pk: student.get('pk') // Передаём 'pk как параметр
},
success: function (record) {
userData = record.getData(); // Сохраняем во внешнюю переменную
}
});
Запрос уйдет на сервер в виде GET на url /vuz/data/ studentInfo?pk=42 После запроса на сервер за данными они будут содержаться в переменной модели student. Так же можно получить доступ к данным через studentStore
Ок. С получением данных вроде как разобрались. Теперь надо разобраться как быть в случае, когда нам надо отправить данные на сервер. Вернее либо обновить данные в имеющейся модели, либо отправить на сервер новую модель данных (по простому - создать новую запись в базе на бэке )
Чтобы отправить данные(создать некую сущность) на сервер необходимо сделать следующее
Скрытый текст
// Создаем нового студента
const newStudent = Ext.create('studentHeaderModel', {
f: 'Иванов',
i: 'Иван',
o: 'Иванович',
sex: 'M',
birthday: '01.01.2020'
});
// Сохраняем пользователя
newStudent.save({
success: function(record, operation) {
Ext.Msg.alert('Успех');
},
failure: function(record, operation) {
Ext.Msg.alert('Ошибка');
}
});
//Тоже самое можно сделать и через store.
//Код такой
studentStore.add(newStudent);
studentStore.sync()
На сервер уйдет POST на /vuz/data/studentInfo вида { f: 'Иванов', i: 'Иван', o: 'Иванович', sex: 'M, birthday: '01.01.2020' }
.
Чтобы данные в модели на фронте обновились (нам нужен ИД для новой записи) сервер должен вернуть такие данные -
{
"success": true,
"data": { pk:123,f: 'Иванов', i: 'Иван', o: 'Иванович', sex: 'M, birthday: '01.01.2020' }}
После обработки запроса сервером мы можем получить на фронте ИД новой записи через newStudent.getId(). А с данными работать либо через переменную модели newStudent, либо через стор таким вот образом var newStudent = studentStore.getAt(0). И далее newStudent.get('i')
В контексте документа осталось определиться как работать с данными его табличной части. В принципе работа с ней аналогична работе с данными шапки. Разница только в том, что в соответствующем store у нас будет не одна запись, а несколько. И получить мы ее можем через метод стора getAt(pos)
Ок. Вроде как разобрались как работать с данными документа. Осталось его отобразить пользователю.
Тут, думаю , не стоит публиковать код. Так как он довольно объемен. Можно спросить DeepSeek примерно так.
Приведи пример для extjs формы документа, который будет содержать панель шапки документа. и панель табличной части. В табличной части должен быть грид со строками. В шапке форма с несколькими контролами.
Собственно и все. Если кратко. Осталось только "натянуть" это на какую то основу, которая отображала бы менюшки, выборы журналов и тд.
После долгих раздумий была выбрана такая "тема".
Да, это похоже на "корпоративное приложение". И даже, вроде как, на интерфейс Windows, что должно быть понятно конечному пользователю. Для внутренних пользователей это более менее удобно.
Много текста получилось. По этому закругляюсь :-)
Отмечу , что в результате этого штурма веба у меня увеличилось на два порядка количество пользователей системы, так как появились еще и студенты. Данная система прожила некоторое время, часть документов я оставил в win системе. И начал думать что ж делать дальше :-)
Что делал дальше это уже тема другой статьи. Я резко ушел в React и NodeJs. Получилась довольно большая система. Боюсь, если писать еще дну статью и описывать ее целиком, то опять получится много текста. Может кто то посоветует как структурировать следующую статью.
Спасибо за внимание! Надеюсь, было интересно!