версия 2.0.7: надёжный автоапдейт (CDN) + кнопка проверки + без лимита листания

- UpdateService: проверка через публичный CDN latest.json (тот же хост, что и
  установщик — надёжнее поддомена git), fallback на Gitea, лог в %TEMP%\elfisa-update.log
- Настройки: кнопка «Проверить обновления» (ручной триггер + видимый результат)
- Прайс-лист: убран лимит показа (300 → весь прайс/все совпадения одним запросом)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Exest 2026-08-13 15:35:24 +03:00
parent 23f34cc769
commit 4e341f76c3
6 changed files with 89 additions and 8 deletions

View File

@ -3,7 +3,7 @@
#define MyAppName "Электронная Фармация" #define MyAppName "Электронная Фармация"
#define MyAppBrand "ЭльФиСА" #define MyAppBrand "ЭльФиСА"
#define MyAppVersion "2.0.6" #define MyAppVersion "2.0.7"
#define MyAppPublisher "PharmData" #define MyAppPublisher "PharmData"
#define MyAppURL "https://cdn.24pharmdata.ru" #define MyAppURL "https://cdn.24pharmdata.ru"
#define MyAppSupportURL "https://24pharmdata.ru" #define MyAppSupportURL "https://24pharmdata.ru"

View File

@ -5,9 +5,9 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<Version>2.0.6</Version> <Version>2.0.7</Version>
<AssemblyVersion>2.0.6.0</AssemblyVersion> <AssemblyVersion>2.0.7.0</AssemblyVersion>
<FileVersion>2.0.6.0</FileVersion> <FileVersion>2.0.7.0</FileVersion>
<Product>ЭльФиСА — Электронная Фармация</Product> <Product>ЭльФиСА — Электронная Фармация</Product>
<Company>PharmData</Company> <Company>PharmData</Company>
<!-- Кросс-платформенная публикация задаётся флагами CLI (RID + self-contained). --> <!-- Кросс-платформенная публикация задаётся флагами CLI (RID + self-contained). -->

View File

@ -128,7 +128,9 @@ public partial class PriceListViewModel : ViewModelBase
try try
{ {
Busy = true; Busy = true;
var resp = await _api.GetPriceSummaryAsync(Search, limit: 300, priceListId: SelectedPriceList?.PriceListId); // Без лимита показа: тянем весь прайс/все совпадения одним запросом (сервер
// при одном большом запросе прогоняет дедупликацию один раз, без COUNT).
var resp = await _api.GetPriceSummaryAsync(Search, limit: 500000, priceListId: SelectedPriceList?.PriceListId);
RebuildIndex(resp.Summary ?? new()); RebuildIndex(resp.Summary ?? new());
Total = resp.TotalDrugs; Total = resp.TotalDrugs;
} }

View File

@ -3,7 +3,7 @@
xmlns:vm="using:Elfisa.Avalonia.ViewModels" xmlns:vm="using:Elfisa.Avalonia.ViewModels"
x:Class="Elfisa.Avalonia.Views.SettingsWindow" x:Class="Elfisa.Avalonia.Views.SettingsWindow"
x:DataType="vm:SettingsViewModel" x:DataType="vm:SettingsViewModel"
Width="460" Height="360" CanResize="False" Width="460" Height="440" CanResize="False"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
Background="{DynamicResource Bg}" Background="{DynamicResource Bg}"
Title="Настройки"> Title="Настройки">
@ -26,6 +26,15 @@
<ComboBox ItemsSource="{Binding Locations}" SelectedItem="{Binding SelectedLocation}" HorizontalAlignment="Stretch"/> <ComboBox ItemsSource="{Binding Locations}" SelectedItem="{Binding SelectedLocation}" HorizontalAlignment="Stretch"/>
</StackPanel> </StackPanel>
<StackPanel Spacing="6">
<TextBlock Text="Обновления" Classes="label"/>
<StackPanel Orientation="Horizontal" Spacing="10">
<Button Content="Проверить обновления" Click="OnCheckUpdate"/>
<TextBlock x:Name="UpdateStatus" VerticalAlignment="Center" TextWrapping="Wrap"
Foreground="{DynamicResource Muted}"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10" Margin="0,8,0,0"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10" Margin="0,8,0,0">
<Button Content="Отмена" Click="OnCancel" Padding="18,9"/> <Button Content="Отмена" Click="OnCancel" Padding="18,9"/>
<Button Classes="primary" Content="Сохранить" Click="OnSave"/> <Button Classes="primary" Content="Сохранить" Click="OnSave"/>

View File

@ -1,3 +1,5 @@
using System;
using System.Reflection;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -12,6 +14,28 @@ public partial class SettingsWindow : Window
{ {
public SettingsWindow() => AvaloniaXamlLoader.Load(this); public SettingsWindow() => AvaloniaXamlLoader.Load(this);
private async void OnCheckUpdate(object? sender, RoutedEventArgs e)
{
var status = this.FindControl<TextBlock>("UpdateStatus");
var current = Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(0, 0, 0, 0);
if (status is not null) status.Text = "Проверяем…";
var info = await UpdateService.CheckAsync(current);
if (info is null)
{
if (status is not null) status.Text = "Не удалось проверить (нет сети).";
return;
}
if (!info.Available)
{
if (status is not null) status.Text = $"У вас последняя версия ({info.CurrentVersion}).";
return;
}
if (status is not null) status.Text = $"Доступна {info.LatestVersion} — открываю…";
var win = new UpdateWindow();
win.DataContext = new UpdateViewModel(info, win.Close);
await win.ShowDialog(this);
}
private void OnSave(object? sender, RoutedEventArgs e) private void OnSave(object? sender, RoutedEventArgs e)
{ {
if (DataContext is SettingsViewModel vm) if (DataContext is SettingsViewModel vm)

View File

@ -20,6 +20,9 @@ public sealed class UpdateInfo
/// </summary> /// </summary>
public static class UpdateService public static class UpdateService
{ {
// Первичный источник — публичный CDN (тот же хост, откуда качается установщик;
// надёжнее приватных API и отдельных поддоменов). Fallback — Gitea Releases.
private const string LatestJsonUrl = "https://cdn.24pharmdata.ru/latest.json";
private const string ReleasesLatest = private const string ReleasesLatest =
"https://git.24pharmdata.ru/api/v1/repos/pharmdata/elfisa-pharmacy/releases/latest"; "https://git.24pharmdata.ru/api/v1/repos/pharmdata/elfisa-pharmacy/releases/latest";
@ -28,6 +31,41 @@ public static class UpdateService
/// <summary>Проверяет наличие новой версии. Возвращает null при офлайне/ошибке (не мешаем работе).</summary> /// <summary>Проверяет наличие новой версии. Возвращает null при офлайне/ошибке (не мешаем работе).</summary>
public static async Task<UpdateInfo?> CheckAsync(Version current) public static async Task<UpdateInfo?> CheckAsync(Version current)
{
var info = await TryCdnAsync(current).ConfigureAwait(false)
?? await TryGiteaAsync(current).ConfigureAwait(false);
Log(info is null
? $"current={current}: проверка не удалась (нет ответа CDN и Gitea)"
: $"current={current} latest={info.LatestVersion} available={info.Available}");
return info;
}
// CDN latest.json: {"version":"2.0.7","setup":"https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe"}
private static async Task<UpdateInfo?> TryCdnAsync(Version current)
{
try
{
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(8) };
var json = await http.GetStringAsync(LatestJsonUrl).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
var ver = root.TryGetProperty("version", out var v) ? v.GetString() : null;
var latest = ParseVersion(ver);
if (latest is null) return null;
var setup = root.TryGetProperty("setup", out var s) ? s.GetString() : null;
return new UpdateInfo
{
Available = latest > current,
CurrentVersion = current.ToString(),
LatestVersion = latest.ToString(),
ReleaseUrl = "",
SetupUrl = string.IsNullOrWhiteSpace(setup) ? SetupUrl : setup!,
};
}
catch (Exception ex) { Log("CDN latest.json недоступен: " + ex.Message); return null; }
}
private static async Task<UpdateInfo?> TryGiteaAsync(Version current)
{ {
try try
{ {
@ -49,10 +87,18 @@ public static class UpdateService
SetupUrl = SetupUrl, SetupUrl = SetupUrl,
}; };
} }
catch catch (Exception ex) { Log("Gitea releases/latest недоступен: " + ex.Message); return null; }
}
/// <summary>Лог проверки в %TEMP%\elfisa-update.log — для диагностики на машине клиента.</summary>
private static void Log(string msg)
{
try
{ {
return null; var path = Path.Combine(Path.GetTempPath(), "elfisa-update.log");
File.AppendAllText(path, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {msg}{Environment.NewLine}");
} }
catch { /* лог не критичен */ }
} }
/// <summary>Скачивает установщик с CDN во временный файл. Возвращает путь.</summary> /// <summary>Скачивает установщик с CDN во временный файл. Возвращает путь.</summary>