Systemy ERP to serce programowania w Delphi: klienci, zamówienia, faktury, stany magazynowe, przebieg produkcji. Logika biznesowa już istnieje w Object Pascal. Baza danych jest podłączona. Raporty działają. Czego często brakuje, to dostępny przez przeglądarkę pulpit nawigacyjny, który kadra zarządzająca może otworzyć na tablecie bez instalowania czegokolwiek. Ten artykuł pokazuje, jak zbudować dokładnie to za pomocą sgcHTML, pracując na realistycznym scenariuszu: sprzedażowego systemu ERP z żywymi danymi o przychodach, siatką faktur, tablicą Kanban dla dostaw i aktualizacjami push w czasie rzeczywistym przy nowych zamówieniach.
Cały kod kompiluje się w Delphi 10.4 Sydney i nowszych wersjach. Gotowy wynik to pojedynczy plik .exe, który serwuje pulpit nawigacyjny na porcie 8080, ze stylizacją Bootstrap 5, responsywnym układem i żywymi aktualizacjami przez WebSocket, bez żadnych plików JavaScript do wdrożenia obok pliku binarnego.
Konfiguracja serwera
sgcHTML opakowuje TsgcWSHTTPServer z sgcWebSockets. Umieść TsgcHTMLEngine_Server w module danych obok serwera HTTP i połącz je ze sobą. Silnik automatycznie przechwytuje żądania zasobów statycznych (Bootstrap CSS/JS, Chart.js, htmx); wszystko inne trafia do Twojego handlera OnCommandGet.
uses
sgcWebSocket_Server, sgcHTML_Engine_Server,
sgcHTML_Template_Bootstrap, sgcHTML_Page;
type
TERP_Server = class(TDataModule)
HTTPServer: TsgcWSHTTPServer;
HTMLEngine: TsgcHTMLEngine_Server;
private
procedure HandleGet(AContext: TIdContext;
AReq: TIdHTTPRequestInfo; AResp: TIdHTTPResponseInfo);
public
procedure Start(aPort: Integer);
end;
procedure TERP_Server.Start(aPort: Integer);
begin
HTTPServer.Port := aPort;
HTMLEngine.Server := HTTPServer;
HTTPServer.OnCommandGet := HandleGet;
HTTPServer.Active := True;
end;
procedure TERP_Server.HandleGet(AContext: TIdContext;
AReq: TIdHTTPRequestInfo; AResp: TIdHTTPResponseInfo);
begin
if AReq.Document = '/' then
ServeDashboard(AResp)
else if AReq.Document = '/invoices' then
ServeInvoices(AResp)
else if AReq.Document = '/kanban' then
ServeKanban(AResp)
else
AResp.ResponseNo := 404;
end;
Karty KPI
Górny wiersz pulpitu nawigacyjnego pokazuje cztery karty KPI: łączny przychód, otwarte faktury, liczba przeterminowanych i wskaźnik terminowości dostaw. TsgcHTMLComponent_StatCard obsługuje całą warstwę wizualną: gradient tła, tytuł, duża wartość, strzałka trendu i opcjonalna etykieta stopki.
uses
sgcHTML_Component_StatCard;
procedure TERP_Server.BuildKPIRow(aPage: TsgcHTMLPage; aDB: TFDConnection);
function MakeCard(aPage: TsgcHTMLPage; const aTitle, aValue: string;
aColor: TsgcHTMLStatColor; aTrend: TsgcHTMLStatTrend;
const aTrendValue, aFooter: string; aOrder: Integer): TsgcHTMLComponent_StatCard;
begin
Result := TsgcHTMLComponent_StatCard.Create(nil);
Result.PageBuilder := aPage.PageBuilder;
Result.Section := 'kpi';
Result.SectionOrder := aOrder;
Result.ColumnWidth := cw3;
Result.Title := aTitle;
Result.Value := aValue;
Result.Color := aColor;
Result.Trend := aTrend;
Result.TrendValue := aTrendValue;
Result.FooterText := aFooter;
Result.Gradient := sgBlueViolet;
end;
var
oQ: TFDQuery;
begin
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text := 'SELECT SUM(total) FROM invoices WHERE YEAR(issued)=YEAR(NOW())';
oQ.Open;
MakeCard(aPage, 'YTD Revenue', FormatCurr('$#,##0', oQ.Fields[0].AsFloat),
scPrimary, stUp, '+14%', 'vs last year', 1);
oQ.Close;
oQ.SQL.Text := 'SELECT COUNT(*) FROM invoices WHERE status=''open''';
oQ.Open;
MakeCard(aPage, 'Open Invoices', oQ.Fields[0].AsString,
scInfo, stNeutral, '', 'awaiting payment', 2);
oQ.Close;
oQ.SQL.Text := 'SELECT COUNT(*) FROM invoices WHERE status=''overdue''';
oQ.Open;
MakeCard(aPage, 'Overdue', oQ.Fields[0].AsString,
scDanger, stDown, '-3', 'vs last month', 3);
oQ.Close;
oQ.SQL.Text := 'SELECT ROUND(100.0*SUM(on_time)/COUNT(*),1) FROM deliveries';
oQ.Open;
MakeCard(aPage, 'On-Time Delivery', oQ.Fields[0].AsString + '%',
scSuccess, stUp, '+2pp', 'last 30 days', 4);
oQ.Close;
finally
oQ.Free;
end;
end;
Wykres trendu przychodów
Wykres przychodów zajmuje drugi wiersz. TsgcHTMLComponent_Chart przyjmuje tablice etykiet i zestawy danych z kolorami obramowania i wypełnienia. LoadFromDataSet mapuje wynik zapytania na dane wykresu jednym wywołaniem: przekaż zbiór danych, pole etykiety (nazwa miesiąca) i jedno lub więcej pól wartości (przychód, koszt).
uses
sgcHTML_Component_Chart;
procedure TERP_Server.BuildRevenueChart(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oChart: TsgcHTMLComponent_Chart;
oQ: TFDQuery;
begin
oChart := TsgcHTMLComponent_Chart.Create(nil);
oChart.PageBuilder := aPage.PageBuilder;
oChart.Section := 'charts';
oChart.SectionTitle := 'Revenue';
oChart.SectionOrder := 1;
oChart.ColumnWidth := cw8;
oChart.ChartType := ctBar;
oChart.Title := 'Monthly Revenue vs Cost (last 12 months)';
oChart.ShowLegend := True;
oChart.Stacked := False;
oChart.Responsive := True;
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT DATE_FORMAT(issued,''%b'') AS month, ' +
' SUM(total) AS revenue, ' +
' SUM(cost) AS cost ' +
'FROM invoices ' +
'WHERE issued >= DATE_SUB(NOW(), INTERVAL 12 MONTH) ' +
'GROUP BY YEAR(issued), MONTH(issued) ' +
'ORDER BY YEAR(issued), MONTH(issued)';
oQ.Open;
// One call maps the query to labels + two datasets
oChart.LoadFromDataSet(oQ, 'month', ['revenue', 'cost']);
finally
oQ.Free;
end;
end;
Paginowana tabela faktur
TsgcHTMLComponent_DataTable łączy TsgcHTMLComponent_Grid i TsgcHTMLComponent_Pagination w jeden widget z wbudowanym polem wyszukiwania, etykietą liczby wierszy, opcjonalnym przyciskiem eksportu i konfigurowalną liczbą wierszy na stronie. LoadFromDataSet automatycznie odczytuje nazwy kolumn i typy; możesz następnie nadpisać etykiety nagłówków i szerokości kolumn przez Grid.Columns.
uses
sgcHTML_Component_DataTable;
procedure TERP_Server.BuildInvoiceTable(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oTable: TsgcHTMLComponent_DataTable;
oQ: TFDQuery;
begin
oTable := TsgcHTMLComponent_DataTable.Create(nil);
oTable.PageBuilder := aPage.PageBuilder;
oTable.Section := 'invoices';
oTable.SectionTitle := 'Invoices';
oTable.SectionOrder := 1;
oTable.ColumnWidth := cw12;
oTable.Title := 'Recent Invoices';
oTable.ShowSearch := True;
oTable.ShowExport := True;
oTable.ShowPageSize := True;
oTable.PageSizes := '10,25,50';
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT number, customer, issued, due, total, status ' +
'FROM invoices ' +
'ORDER BY issued DESC ' +
'LIMIT 200';
oQ.Open;
oTable.LoadFromDataSet(oQ, 20);
finally
oQ.Free;
end;
end;
Tablica Kanban dla realizacji zamówień
TsgcHTMLComponent_KanbanBoard reprezentuje wielokolumnową tablicę z obsługą przeciągania. Każda kolumna zawiera kolekcję kart; każda karta ma tytuł, opcjonalny opis, osobę przypisaną, tag i kolor. W kontekście ERP kolumny naturalnie odpowiadają stanom zamówień: Received, Picking, Packed, Shipped.
uses
sgcHTML_Component_KanbanBoard;
procedure TERP_Server.BuildKanban(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oBoard: TsgcHTMLComponent_KanbanBoard;
oCol: TsgcHTMLKanbanColumn;
oQ: TFDQuery;
vStatus: string;
begin
oBoard := TsgcHTMLComponent_KanbanBoard.Create(nil);
oBoard.PageBuilder := aPage.PageBuilder;
oBoard.Section := 'kanban';
oBoard.SectionTitle := 'Order Fulfilment';
oBoard.SectionOrder := 1;
oBoard.ColumnWidth := cw12;
// Create the four columns
for vStatus in ['Received', 'Picking', 'Packed', 'Shipped'] do
begin
oCol := oBoard.Columns.Add;
oCol.Title := vStatus;
oCol.Color := hcLight;
end;
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT order_no, customer, qty_total, assigned_to, status ' +
'FROM orders ' +
'WHERE status IN (''Received'',''Picking'',''Packed'',''Shipped'') ' +
'ORDER BY created DESC LIMIT 100';
oQ.Open;
while not oQ.Eof do
begin
// Find the column whose title matches the order status
oCol := oBoard.Columns.FindByTitle(oQ.FieldByName('status').AsString);
if Assigned(oCol) then
oCol.AddCard(
oQ.FieldByName('order_no').AsString,
oQ.FieldByName('customer').AsString + ' — ' +
oQ.FieldByName('qty_total').AsString + ' items',
hcLight,
oQ.FieldByName('assigned_to').AsString
);
oQ.Next;
end;
finally
oQ.Free;
end;
end;
Składanie pełnej strony
Cztery sekcje są składane przez jedną procedurę ServeDashboard, która tworzy stronę, wywołuje każdy budowniczy, opakowuje wynik w szablon Bootstrap i zapisuje go do odpowiedzi:
procedure TERP_Server.ServeDashboard(AResp: TIdHTTPResponseInfo);
var
oPage: TsgcHTMLPage;
oTemplate: TsgcHTMLTemplate_Bootstrap;
begin
oPage := TsgcHTMLPage.Create(nil);
oTemplate := TsgcHTMLTemplate_Bootstrap.Create(nil);
try
BuildKPIRow(oPage, FDB);
BuildRevenueChart(oPage, FDB);
BuildInvoiceTable(oPage, FDB);
BuildKanban(oPage, FDB);
oTemplate.Title := 'ERP Dashboard';
oTemplate.Page := oPage;
oTemplate.DarkMode := False;
oTemplate.BodyClass := 'bg-light';
AResp.ContentType := 'text/html; charset=utf-8';
AResp.ContentText := oTemplate.GetHTML;
AResp.ResponseNo := 200;
finally
oPage.Free;
oTemplate.Free;
end;
end;
Aktualizacje na żywo przez WebSocket
Pulpit nawigacyjny byłby statyczny bez danych na żywo. sgcHTML opiera się na sgcWebSockets, więc wysłanie aktualizacji fragmentu przy nowym zamówieniu to jedno wywołanie rozgłoszenia. Po stronie klienta karta KPI ma atrybut id, a rozszerzenie WebSocket htmx zastępuje jej zawartość po nadejściu wiadomości.
// Called from the order-processing thread when a new order is saved
procedure TERP_Server.OnNewOrderSaved(aOrder: TERPOrder);
var
oCard: TsgcHTMLComponent_StatCard;
begin
// Rebuild the "Open Invoices" KPI card with the updated count
oCard := TsgcHTMLComponent_StatCard.Create(nil);
try
oCard.CardID := 'kpi-open-invoices';
oCard.Title := 'Open Invoices';
oCard.Value := IntToStr(FDB.OpenInvoiceCount);
oCard.Color := scInfo;
oCard.Trend := stUp;
// Broadcast the new card HTML to all connected dashboard clients
HTTPServer.Broadcast(oCard.HTML, '/dashboard');
finally
oCard.Free;
end;
end;
Fragment zastępuje pasującą kartę w przeglądarce natychmiast, bez przeładowywania strony i bez konieczności klikania czegokolwiek przez użytkownika. Każdy podłączony klient widzi aktualizację w ciągu milisekund.
Co otrzymujesz na końcu
Kompletny przykład to pojedynczy unit Delphi i moduł danych. Skompiluj i uruchom: dostępny przez przeglądarkę pulpit nawigacyjny ERP pojawia się na skonfigurowanym porcie z czterema kartami KPI, 12-miesięcznym wykresem słupkowym przychód a koszt, przeszukiwalną i paginowaną tabelą faktur oraz wielokolumnową tablicą Kanban dla realizacji zamówień. Gdy składane są nowe zamówienia, karty KPI aktualizują się w czasie rzeczywistym we wszystkich podłączonych kartach przeglądarki. Łączna liczba napisanych linii kodu front-endowego: zero.
Ten sam wzorzec działa dla dowolnej aplikacji biznesowej Delphi: zarządzanie magazynem, harmonogramowanie produkcji, systemy HR, śledzenie floty. Jeśli dane są w TDataSet, mogą znaleźć się na żywym pulpicie nawigacyjnym w sieci w ciągu jednego poranka.
Pobierz bezpłatną wersję próbną sgcHTML ze strony esegece.com/products/sgchtml/download i przejrzyj dołączone przykładowe aplikacje: demonstrację ERP, konsolę administracyjną, monitor na żywo i portal klientów, aby zobaczyć kompletny działający przykład.
Pytania? Skontaktuj się z nami. Odpowiedź otrzymasz od osób, które napisały ten kod.
