add:存储管理

This commit is contained in:
2026-05-01 01:00:12 +08:00
parent 4a1403eed3
commit efe0bb6ae2
7 changed files with 842 additions and 0 deletions
+126
View File
@@ -2658,4 +2658,130 @@ Hide</value>
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint" xml:space="preserve">
<value>When enabled, the whiteboard toolbar Booth button launches Seewo Video Showcase (must be installed). When disabled, the built-in booth is used.</value>
</data>
<data name="Storage_Title" xml:space="preserve">
<value>Storage</value>
</data>
<data name="Storage_TotalUsage" xml:space="preserve">
<value>ICC CE total usage</value>
</data>
<data name="Storage_DiskPercent" xml:space="preserve">
<value>Disk percentage</value>
</data>
<data name="Storage_Calculating" xml:space="preserve">
<value>Calculating…</value>
</data>
<data name="Storage_CalculateFailed" xml:space="preserve">
<value>Calculation failed</value>
</data>
<data name="Storage_Refresh" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="Storage_OpenAppFolder" xml:space="preserve">
<value>Open app folder</value>
</data>
<data name="Storage_CategoryDetails" xml:space="preserve">
<value>Category details</value>
</data>
<data name="Storage_Clean" xml:space="preserve">
<value>Clean</value>
</data>
<data name="Storage_Legend_Core" xml:space="preserve">
<value>Core</value>
</data>
<data name="Storage_Legend_Logs" xml:space="preserve">
<value>Logs</value>
</data>
<data name="Storage_Legend_Ink" xml:space="preserve">
<value>Ink</value>
</data>
<data name="Storage_Legend_Backups" xml:space="preserve">
<value>Backups</value>
</data>
<data name="Storage_Legend_Custom" xml:space="preserve">
<value>Custom</value>
</data>
<data name="Storage_Legend_Plugins" xml:space="preserve">
<value>Plugins</value>
</data>
<data name="Storage_Legend_Update" xml:space="preserve">
<value>Update</value>
</data>
<data name="Storage_Legend_Other" xml:space="preserve">
<value>Other</value>
</data>
<data name="Storage_Core_Header" xml:space="preserve">
<value>Core files</value>
</data>
<data name="Storage_Core_Desc" xml:space="preserve">
<value>Includes .json configs, .enc usage stats, .exe / .dll executables and .dat data files.</value>
</data>
<data name="Storage_Core_NotCleanable" xml:space="preserve">
<value>Core files cannot be cleaned to keep the application functional.</value>
</data>
<data name="Storage_Logs_Header" xml:space="preserve">
<value>Logs</value>
</data>
<data name="Storage_Logs_Desc" xml:space="preserve">
<value>.txt logs and crash reports under Logs / Crashs. Cleanable.</value>
</data>
<data name="Storage_Ink_Header" xml:space="preserve">
<value>Ink</value>
</data>
<data name="Storage_Ink_Desc" xml:space="preserve">
<value>.icstk / .xml / screenshot .png files under Saves.</value>
</data>
<data name="Storage_Backups_Header" xml:space="preserve">
<value>Backups</value>
</data>
<data name="Storage_Backups_Desc" xml:space="preserve">
<value>Settings / config backups (.json / .zip) under Backups.</value>
</data>
<data name="Storage_Custom_Header" xml:space="preserve">
<value>Custom files</value>
</data>
<data name="Storage_Custom_Desc" xml:space="preserve">
<value>Custom icons and roll-call backgrounds (icons / backgrounds, .png).</value>
</data>
<data name="Storage_Custom_Hint" xml:space="preserve">
<value>Custom files are added by the user and won't be auto-cleaned. Manage them in their dedicated UI.</value>
</data>
<data name="Storage_Plugins_Header" xml:space="preserve">
<value>Plugins</value>
</data>
<data name="Storage_Plugins_Desc" xml:space="preserve">
<value>.iccpp plugin packages under the plugins directory.</value>
</data>
<data name="Storage_Plugins_Hint" xml:space="preserve">
<value>Use the plugins page to uninstall plugins.</value>
</data>
<data name="Storage_Update_Header" xml:space="preserve">
<value>Auto update</value>
</data>
<data name="Storage_Update_Desc" xml:space="preserve">
<value>Installer packages and download cache under AutoUpdate. Refreshed each release.</value>
</data>
<data name="Storage_Other_Header" xml:space="preserve">
<value>Other</value>
</data>
<data name="Storage_Other_Desc" xml:space="preserve">
<value>Files not classified into the categories above.</value>
</data>
<data name="Storage_Confirm_Title" xml:space="preserve">
<value>Confirm cleanup</value>
</data>
<data name="Storage_Confirm_Body" xml:space="preserve">
<value>All files in the "{0}" category will be permanently deleted. Continue?</value>
</data>
<data name="Storage_Confirm_Second_Title" xml:space="preserve">
<value>Confirm again</value>
</data>
<data name="Storage_Confirm_Second_Body" xml:space="preserve">
<value>Confirm again: are you sure you want to delete "{0}"?</value>
</data>
<data name="Storage_CleanFailed" xml:space="preserve">
<value>Cleanup failed: {0}</value>
</data>
<data name="Storage_NavTooltip" xml:space="preserve">
<value>View and clean storage used by ICC CE</value>
</data>
</root>
+126
View File
@@ -2707,4 +2707,130 @@
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint" xml:space="preserve">
<value>开启后,点击白板工具栏「展台」将打开希沃视频展台(需已安装);关闭则使用内置展台。</value>
</data>
<data name="Storage_Title" xml:space="preserve">
<value>存储管理</value>
</data>
<data name="Storage_TotalUsage" xml:space="preserve">
<value>ICC CE 总占用</value>
</data>
<data name="Storage_DiskPercent" xml:space="preserve">
<value>占磁盘比例</value>
</data>
<data name="Storage_Calculating" xml:space="preserve">
<value>计算中…</value>
</data>
<data name="Storage_CalculateFailed" xml:space="preserve">
<value>计算失败</value>
</data>
<data name="Storage_Refresh" xml:space="preserve">
<value>刷新</value>
</data>
<data name="Storage_OpenAppFolder" xml:space="preserve">
<value>打开应用目录</value>
</data>
<data name="Storage_CategoryDetails" xml:space="preserve">
<value>分类详情</value>
</data>
<data name="Storage_Clean" xml:space="preserve">
<value>清理</value>
</data>
<data name="Storage_Legend_Core" xml:space="preserve">
<value>核心文件</value>
</data>
<data name="Storage_Legend_Logs" xml:space="preserve">
<value>日志</value>
</data>
<data name="Storage_Legend_Ink" xml:space="preserve">
<value>墨迹</value>
</data>
<data name="Storage_Legend_Backups" xml:space="preserve">
<value>备份</value>
</data>
<data name="Storage_Legend_Custom" xml:space="preserve">
<value>自定义</value>
</data>
<data name="Storage_Legend_Plugins" xml:space="preserve">
<value>插件</value>
</data>
<data name="Storage_Legend_Update" xml:space="preserve">
<value>自动更新</value>
</data>
<data name="Storage_Legend_Other" xml:space="preserve">
<value>其他</value>
</data>
<data name="Storage_Core_Header" xml:space="preserve">
<value>核心文件</value>
</data>
<data name="Storage_Core_Desc" xml:space="preserve">
<value>包含 .json 配置文件、.enc 使用统计、.exe 主程序、.dll 文件、.dat 数据文件等</value>
</data>
<data name="Storage_Core_NotCleanable" xml:space="preserve">
<value>核心文件不可清理,以免影响应用正常运行。</value>
</data>
<data name="Storage_Logs_Header" xml:space="preserve">
<value>日志</value>
</data>
<data name="Storage_Logs_Desc" xml:space="preserve">
<value>位于 Logs / Crashs 目录下的 .txt 日志与崩溃报告,可清理</value>
</data>
<data name="Storage_Ink_Header" xml:space="preserve">
<value>墨迹</value>
</data>
<data name="Storage_Ink_Desc" xml:space="preserve">
<value>位于 Saves 目录下的 .icstk / .xml / 截图 .png 等墨迹文件</value>
</data>
<data name="Storage_Backups_Header" xml:space="preserve">
<value>备份</value>
</data>
<data name="Storage_Backups_Desc" xml:space="preserve">
<value>位于 Backups 目录的设置 / 配置备份(.json / .zip</value>
</data>
<data name="Storage_Custom_Header" xml:space="preserve">
<value>自定义文件</value>
</data>
<data name="Storage_Custom_Desc" xml:space="preserve">
<value>自定义图标与点名背景图(icons / backgrounds 等 .png</value>
</data>
<data name="Storage_Custom_Hint" xml:space="preserve">
<value>自定义文件由用户手动添加,不会自动清理,如需删除请前往对应管理界面。</value>
</data>
<data name="Storage_Plugins_Header" xml:space="preserve">
<value>插件</value>
</data>
<data name="Storage_Plugins_Desc" xml:space="preserve">
<value>位于 plugins 目录的 .iccpp 插件包</value>
</data>
<data name="Storage_Plugins_Hint" xml:space="preserve">
<value>插件请前往插件管理页面进行卸载。</value>
</data>
<data name="Storage_Update_Header" xml:space="preserve">
<value>自动更新</value>
</data>
<data name="Storage_Update_Desc" xml:space="preserve">
<value>AutoUpdate 目录中的安装包与下载缓存,每次新版本发布或更新时会被刷新</value>
</data>
<data name="Storage_Other_Header" xml:space="preserve">
<value>其他</value>
</data>
<data name="Storage_Other_Desc" xml:space="preserve">
<value>未归类至上述项目的其他文件</value>
</data>
<data name="Storage_Confirm_Title" xml:space="preserve">
<value>清理确认</value>
</data>
<data name="Storage_Confirm_Body" xml:space="preserve">
<value>将永久删除「{0}」分类下的所有文件,操作不可恢复。\n是否继续?</value>
</data>
<data name="Storage_Confirm_Second_Title" xml:space="preserve">
<value>二次确认</value>
</data>
<data name="Storage_Confirm_Second_Body" xml:space="preserve">
<value>再次确认:确定要删除「{0}」吗?</value>
</data>
<data name="Storage_CleanFailed" xml:space="preserve">
<value>清理失败:{0}</value>
</data>
<data name="Storage_NavTooltip" xml:space="preserve">
<value>查看与清理 ICC CE 占用的存储空间</value>
</data>
</root>
@@ -132,6 +132,13 @@
</ui:SettingsCard.HeaderIcon>
</ui:SettingsCard>
<ui:SettingsCard Header="{i18n:I18n Key=Storage_Title}" Description="{i18n:I18n Key=Storage_NavTooltip}"
IsClickEnabled="True" Click="QuickNavCard_Click" Tag="StoragePage">
<ui:SettingsCard.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.HardDrive}" />
</ui:SettingsCard.HeaderIcon>
</ui:SettingsCard>
<ui:SettingsCard Header="随机点名" Description="随机点名与计时器"
IsClickEnabled="True" Click="QuickNavCard_Click" Tag="RandomDrawPage">
<ui:SettingsCard.HeaderIcon>
@@ -0,0 +1,240 @@
<ui:Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.StoragePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Ink_Canvas.Windows.SettingsViews.Pages"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
xmlns:i18n="clr-namespace:Ink_Canvas.MarkupExtensions"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="{i18n:I18n Key=Storage_Title}">
<ScrollViewer PanningMode="VerticalFirst">
<Grid Margin="59,0,59,0">
<FrameworkElement.Resources>
<sys:Double x:Key="SettingsCardSpacing">4</sys:Double>
<Style x:Key="SettingsSectionHeaderTextBlockStyle"
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
TargetType="TextBlock">
<Style.Setters>
<Setter Property="Margin" Value="1,30,0,6" />
</Style.Setters>
</Style>
</FrameworkElement.Resources>
<ikw:SimpleStackPanel MaxWidth="1000"
HorizontalAlignment="Stretch"
Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="{i18n:I18n Key=Storage_Title}" />
<!-- 概览卡片 -->
<Border Background="{DynamicResource {x:Static ui:ThemeKeys.LayerFillColorDefaultBrushKey}}"
BorderBrush="{DynamicResource {x:Static ui:ThemeKeys.CardStrokeColorDefaultBrushKey}}"
CornerRadius="4" BorderThickness="1" Padding="20,16">
<ikw:SimpleStackPanel Spacing="12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ikw:SimpleStackPanel Orientation="Vertical" Spacing="2">
<TextBlock Text="{i18n:I18n Key=Storage_TotalUsage}" FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<TextBlock x:Name="TotalSizeTextBlock" Text="—"
FontSize="28" FontWeight="SemiBold"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Grid.Column="1" Orientation="Vertical"
Spacing="2" HorizontalAlignment="Right">
<TextBlock Text="{i18n:I18n Key=Storage_DiskPercent}" FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HorizontalAlignment="Right"/>
<TextBlock x:Name="DiskPercentTextBlock" Text="—"
FontSize="20" FontWeight="SemiBold"
HorizontalAlignment="Right"/>
</ikw:SimpleStackPanel>
</Grid>
<!-- 横向占比柱状图 -->
<Border Height="14" CornerRadius="7"
Background="{DynamicResource ControlStrokeColorDefaultBrush}"
ClipToBounds="True">
<Grid x:Name="UsageStackedBar">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="BarCoreCol" Width="0*"/>
<ColumnDefinition x:Name="BarLogsCol" Width="0*"/>
<ColumnDefinition x:Name="BarInkCol" Width="0*"/>
<ColumnDefinition x:Name="BarBackupsCol" Width="0*"/>
<ColumnDefinition x:Name="BarCustomCol" Width="0*"/>
<ColumnDefinition x:Name="BarPluginsCol" Width="0*"/>
<ColumnDefinition x:Name="BarUpdateCol" Width="0*"/>
<ColumnDefinition x:Name="BarOtherCol" Width="0*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Fill="#3B82F6"/>
<Rectangle Grid.Column="1" Fill="#10B981"/>
<Rectangle Grid.Column="2" Fill="#F59E0B"/>
<Rectangle Grid.Column="3" Fill="#8B5CF6"/>
<Rectangle Grid.Column="4" Fill="#EC4899"/>
<Rectangle Grid.Column="5" Fill="#06B6D4"/>
<Rectangle Grid.Column="6" Fill="#EF4444"/>
<Rectangle Grid.Column="7" Fill="#6B7280"/>
</Grid>
</Border>
<!-- 图例 -->
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="14">
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#3B82F6"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Core}" FontSize="12"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#10B981"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Logs}" FontSize="12"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#F59E0B"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Ink}" FontSize="12"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#8B5CF6"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Backups}" FontSize="12"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#EC4899"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Custom}" FontSize="12"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#06B6D4"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Plugins}" FontSize="12"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#EF4444"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Update}" FontSize="12"/>
</ikw:SimpleStackPanel>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="6">
<Border Width="10" Height="10" CornerRadius="2" Background="#6B7280"/>
<TextBlock Text="{i18n:I18n Key=Storage_Legend_Other}" FontSize="12"/>
</ikw:SimpleStackPanel>
</ikw:SimpleStackPanel>
</ikw:SimpleStackPanel>
</Border>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="8" Margin="0,8,0,0">
<Button x:Name="BtnRefresh" Content="{i18n:I18n Key=Storage_Refresh}" Click="BtnRefresh_Click"/>
<Button x:Name="BtnOpenAppFolder" Content="{i18n:I18n Key=Storage_OpenAppFolder}" Click="BtnOpenAppFolder_Click"/>
</ikw:SimpleStackPanel>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="{i18n:I18n Key=Storage_CategoryDetails}" />
<!-- 核心文件 -->
<ui:SettingsExpander x:Name="CoreExpander"
Header="{i18n:I18n Key=Storage_Core_Header}"
Description="{i18n:I18n Key=Storage_Core_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Settings}"/>
</ui:SettingsExpander.HeaderIcon>
<TextBlock x:Name="CoreSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
<ui:SettingsExpander.Items>
<ui:SettingsCard Description="{i18n:I18n Key=Storage_Core_NotCleanable}"/>
</ui:SettingsExpander.Items>
</ui:SettingsExpander>
<!-- 日志 -->
<ui:SettingsExpander x:Name="LogsExpander"
Header="{i18n:I18n Key=Storage_Logs_Header}"
Description="{i18n:I18n Key=Storage_Logs_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.OpenFile}"/>
</ui:SettingsExpander.HeaderIcon>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
<TextBlock x:Name="LogsSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button x:Name="BtnCleanLogs" Content="{i18n:I18n Key=Storage_Clean}" Click="BtnCleanLogs_Click"/>
</ikw:SimpleStackPanel>
</ui:SettingsExpander>
<!-- 墨迹 -->
<ui:SettingsExpander x:Name="InkExpander"
Header="{i18n:I18n Key=Storage_Ink_Header}"
Description="{i18n:I18n Key=Storage_Ink_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Edit}"/>
</ui:SettingsExpander.HeaderIcon>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
<TextBlock x:Name="InkSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button x:Name="BtnCleanInk" Content="{i18n:I18n Key=Storage_Clean}" Click="BtnCleanInk_Click"/>
</ikw:SimpleStackPanel>
</ui:SettingsExpander>
<!-- 备份 -->
<ui:SettingsExpander x:Name="BackupsExpander"
Header="{i18n:I18n Key=Storage_Backups_Header}"
Description="{i18n:I18n Key=Storage_Backups_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Cloud}"/>
</ui:SettingsExpander.HeaderIcon>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
<TextBlock x:Name="BackupsSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button x:Name="BtnCleanBackups" Content="{i18n:I18n Key=Storage_Clean}" Click="BtnCleanBackups_Click"/>
</ikw:SimpleStackPanel>
</ui:SettingsExpander>
<!-- 自定义文件 -->
<ui:SettingsExpander x:Name="CustomExpander"
Header="{i18n:I18n Key=Storage_Custom_Header}"
Description="{i18n:I18n Key=Storage_Custom_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Photo}"/>
</ui:SettingsExpander.HeaderIcon>
<TextBlock x:Name="CustomSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
<ui:SettingsExpander.Items>
<ui:SettingsCard Description="{i18n:I18n Key=Storage_Custom_Hint}"/>
</ui:SettingsExpander.Items>
</ui:SettingsExpander>
<!-- 插件 -->
<ui:SettingsExpander x:Name="PluginsExpander"
Header="{i18n:I18n Key=Storage_Plugins_Header}"
Description="{i18n:I18n Key=Storage_Plugins_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Puzzle}"/>
</ui:SettingsExpander.HeaderIcon>
<TextBlock x:Name="PluginsSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
<ui:SettingsExpander.Items>
<ui:SettingsCard Description="{i18n:I18n Key=Storage_Plugins_Hint}"/>
</ui:SettingsExpander.Items>
</ui:SettingsExpander>
<!-- 自动更新 -->
<ui:SettingsExpander x:Name="UpdateExpander"
Header="{i18n:I18n Key=Storage_Update_Header}"
Description="{i18n:I18n Key=Storage_Update_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Sync}"/>
</ui:SettingsExpander.HeaderIcon>
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
<TextBlock x:Name="UpdateSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button x:Name="BtnCleanUpdate" Content="{i18n:I18n Key=Storage_Clean}" Click="BtnCleanUpdate_Click"/>
</ikw:SimpleStackPanel>
</ui:SettingsExpander>
<!-- 其他 -->
<ui:SettingsExpander x:Name="OtherExpander"
Header="{i18n:I18n Key=Storage_Other_Header}"
Description="{i18n:I18n Key=Storage_Other_Desc}">
<ui:SettingsExpander.HeaderIcon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.More}"/>
</ui:SettingsExpander.HeaderIcon>
<TextBlock x:Name="OtherSizeText" Text="—" FontWeight="SemiBold" VerticalAlignment="Center"/>
</ui:SettingsExpander>
<Rectangle Height="48" />
</ikw:SimpleStackPanel>
</Grid>
</ScrollViewer>
</ui:Page>
@@ -0,0 +1,330 @@
using Ink_Canvas.Helpers;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Page = iNKORE.UI.WPF.Modern.Controls.Page;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
namespace Ink_Canvas.Windows.SettingsViews.Pages
{
public partial class StoragePage : Page
{
// 已知子目录定义。其余顶层文件归入“核心文件”或“其他”。
private static readonly string[] LogDirs = { "Logs", "Crashs" };
private static readonly string[] InkDirs = { "Saves" };
private static readonly string[] BackupDirs = { "Backups" };
private static readonly string[] CustomDirs = { "icons", "backgrounds" };
private static readonly string[] PluginDirs = { "plugins", "Plugins" };
private static readonly string[] UpdateDirs = { "AutoUpdate" };
private static readonly string[] ConfigDirs = { "Configs" };
// 视为核心文件的扩展名(位于应用根目录下)
private static readonly string[] CoreFileExtensions =
{
".exe", ".dll", ".json", ".enc", ".dat", ".config", ".pdb",
".xml", ".manifest", ".runtimeconfig.json", ".deps.json"
};
private long _coreSize, _logsSize, _inkSize, _backupsSize,
_customSize, _pluginsSize, _updateSize, _otherSize;
public StoragePage()
{
InitializeComponent();
Loaded += StoragePage_Loaded;
}
private async void StoragePage_Loaded(object sender, RoutedEventArgs e)
{
await RefreshAsync();
}
private void BtnRefresh_Click(object sender, RoutedEventArgs e)
{
_ = RefreshAsync();
}
private void BtnOpenAppFolder_Click(object sender, RoutedEventArgs e)
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = App.RootPath,
UseShellExecute = true
});
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"打开应用目录失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private async Task RefreshAsync()
{
SetAllSizesText(LocalizationHelper.GetString("Storage_Calculating"));
try
{
await Task.Run(() => CalculateSizes());
UpdateUI();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"计算存储占用失败: {ex.Message}", LogHelper.LogType.Error);
SetAllSizesText(LocalizationHelper.GetString("Storage_CalculateFailed"));
}
}
private void CalculateSizes()
{
_coreSize = _logsSize = _inkSize = _backupsSize =
_customSize = _pluginsSize = _updateSize = _otherSize = 0;
string root = App.RootPath;
if (string.IsNullOrEmpty(root) || !Directory.Exists(root)) return;
// 顶层目录归类
foreach (var dir in SafeEnumerateDirectories(root))
{
string name = Path.GetFileName(dir);
long size = GetDirectorySize(dir);
if (LogDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
_logsSize += size;
else if (InkDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
_inkSize += size;
else if (BackupDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
_backupsSize += size;
else if (CustomDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
_customSize += size;
else if (PluginDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
_pluginsSize += size;
else if (UpdateDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
_updateSize += size;
else if (ConfigDirs.Any(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
_coreSize += size;
else
_otherSize += size;
}
// 顶层文件按扩展名归类
foreach (var file in SafeEnumerateFiles(root))
{
long size = SafeFileLength(file);
string ext = Path.GetExtension(file).ToLowerInvariant();
if (CoreFileExtensions.Contains(ext))
_coreSize += size;
else
_otherSize += size;
}
}
private void UpdateUI()
{
long total = _coreSize + _logsSize + _inkSize + _backupsSize
+ _customSize + _pluginsSize + _updateSize + _otherSize;
TotalSizeTextBlock.Text = FormatSize(total);
CoreSizeText.Text = FormatSize(_coreSize);
LogsSizeText.Text = FormatSize(_logsSize);
InkSizeText.Text = FormatSize(_inkSize);
BackupsSizeText.Text = FormatSize(_backupsSize);
CustomSizeText.Text = FormatSize(_customSize);
PluginsSizeText.Text = FormatSize(_pluginsSize);
UpdateSizeText.Text = FormatSize(_updateSize);
OtherSizeText.Text = FormatSize(_otherSize);
// 更新柱状图列宽
if (total > 0)
{
BarCoreCol.Width = new GridLength(_coreSize, GridUnitType.Star);
BarLogsCol.Width = new GridLength(_logsSize, GridUnitType.Star);
BarInkCol.Width = new GridLength(_inkSize, GridUnitType.Star);
BarBackupsCol.Width = new GridLength(_backupsSize, GridUnitType.Star);
BarCustomCol.Width = new GridLength(_customSize, GridUnitType.Star);
BarPluginsCol.Width = new GridLength(_pluginsSize, GridUnitType.Star);
BarUpdateCol.Width = new GridLength(_updateSize, GridUnitType.Star);
BarOtherCol.Width = new GridLength(_otherSize, GridUnitType.Star);
}
else
{
BarCoreCol.Width = BarLogsCol.Width = BarInkCol.Width =
BarBackupsCol.Width = BarCustomCol.Width =
BarPluginsCol.Width = BarUpdateCol.Width =
BarOtherCol.Width = new GridLength(0, GridUnitType.Star);
}
// 占磁盘比例
try
{
var driveLetter = Path.GetPathRoot(App.RootPath);
if (!string.IsNullOrEmpty(driveLetter))
{
var drive = new DriveInfo(driveLetter);
if (drive.IsReady && drive.TotalSize > 0)
{
double pct = (double)total / drive.TotalSize * 100.0;
DiskPercentTextBlock.Text = pct < 0.01 ? "<0.01 %" : $"{pct:0.##} %";
return;
}
}
}
catch { }
DiskPercentTextBlock.Text = "—";
}
private void SetAllSizesText(string text)
{
TotalSizeTextBlock.Text = text;
CoreSizeText.Text = text;
LogsSizeText.Text = text;
InkSizeText.Text = text;
BackupsSizeText.Text = text;
CustomSizeText.Text = text;
PluginsSizeText.Text = text;
UpdateSizeText.Text = text;
OtherSizeText.Text = text;
}
#region
private void BtnCleanLogs_Click(object sender, RoutedEventArgs e)
{
CleanWithConfirm(LocalizationHelper.GetString("Storage_Logs_Header"), LogDirs, keepRoot: true);
}
private void BtnCleanInk_Click(object sender, RoutedEventArgs e)
{
CleanWithConfirm(LocalizationHelper.GetString("Storage_Ink_Header"), InkDirs, keepRoot: true);
}
private void BtnCleanBackups_Click(object sender, RoutedEventArgs e)
{
CleanWithConfirm(LocalizationHelper.GetString("Storage_Backups_Header"), BackupDirs, keepRoot: true);
}
private void BtnCleanUpdate_Click(object sender, RoutedEventArgs e)
{
CleanWithConfirm(LocalizationHelper.GetString("Storage_Update_Header"), UpdateDirs, keepRoot: true);
}
private async void CleanWithConfirm(string displayName, string[] subDirs, bool keepRoot)
{
var result = MessageBox.Show(
string.Format(LocalizationHelper.GetString("Storage_Confirm_Body"), displayName).Replace("\\n", "\n"),
LocalizationHelper.GetString("Storage_Confirm_Title"),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning);
if (result != MessageBoxResult.OK) return;
var second = MessageBox.Show(
string.Format(LocalizationHelper.GetString("Storage_Confirm_Second_Body"), displayName),
LocalizationHelper.GetString("Storage_Confirm_Second_Title"),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning);
if (second != MessageBoxResult.OK) return;
try
{
await Task.Run(() =>
{
foreach (var sub in subDirs)
{
string path = Path.Combine(App.RootPath, sub);
if (!Directory.Exists(path)) continue;
if (keepRoot)
{
foreach (var file in SafeEnumerateFiles(path, recursive: true))
TryDeleteFile(file);
foreach (var dir in SafeEnumerateDirectories(path).Reverse())
TryDeleteDirectory(dir);
}
else
{
TryDeleteDirectory(path);
}
}
});
LogHelper.WriteLogToFile($"已清理「{displayName}」分类");
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理「{displayName}」失败: {ex.Message}", LogHelper.LogType.Error);
MessageBox.Show(
string.Format(LocalizationHelper.GetString("Storage_CleanFailed"), ex.Message),
LocalizationHelper.GetString("Storage_Confirm_Title"),
MessageBoxButton.OK, MessageBoxImage.Error);
}
await RefreshAsync();
}
#endregion
#region
private static long GetDirectorySize(string path)
{
long total = 0;
try
{
foreach (var file in SafeEnumerateFiles(path, recursive: true))
total += SafeFileLength(file);
}
catch { }
return total;
}
private static System.Collections.Generic.IEnumerable<string> SafeEnumerateDirectories(string path)
{
try { return Directory.EnumerateDirectories(path); }
catch { return System.Linq.Enumerable.Empty<string>(); }
}
private static System.Collections.Generic.IEnumerable<string> SafeEnumerateFiles(string path, bool recursive = false)
{
try
{
return Directory.EnumerateFiles(path, "*",
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}
catch { return System.Linq.Enumerable.Empty<string>(); }
}
private static long SafeFileLength(string path)
{
try { return new FileInfo(path).Length; }
catch { return 0; }
}
private static void TryDeleteFile(string path)
{
try { File.Delete(path); } catch { }
}
private static void TryDeleteDirectory(string path)
{
try { Directory.Delete(path, true); } catch { }
}
private static string FormatSize(long bytes)
{
if (bytes <= 0) return "0 B";
string[] units = { "B", "KB", "MB", "GB", "TB" };
double size = bytes;
int u = 0;
while (size >= 1024 && u < units.Length - 1)
{
size /= 1024;
u++;
}
return u == 0 ? $"{(long)size} {units[u]}" : $"{size:0.##} {units[u]}";
}
#endregion
}
}
@@ -6,6 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
xmlns:i18n="clr-namespace:Ink_Canvas.MarkupExtensions"
Title="InkCanvasForClass 设置"
Width="1138" Height="750"
MinWidth="270" MinHeight="220"
@@ -261,6 +262,17 @@
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<!-- 存储管理 -->
<ui:NavigationViewItem
x:Name="StoragePageItem"
Content="{i18n:I18n Key=Storage_Title}"
Tag="StoragePage"
ToolTipService.ToolTip="{i18n:I18n Key=Storage_NavTooltip}">
<ui:NavigationViewItem.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.HardDrive}"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<!-- 自动化 -->
<ui:NavigationViewItem
x:Name="AutomationPageItem"
@@ -45,6 +45,7 @@ namespace Ink_Canvas.Windows.SettingsViews
{ "UpdatePage", typeof(UpdatePage) },
{ "ExperimentalPage", typeof(ExperimentalPage) },
{ "AdvancedPage", typeof(AdvancedPage) },
{ "StoragePage", typeof(StoragePage) },
{ "AutomationPage", typeof(AutomationPage) },
{ "PowerPointPage", typeof(PowerPointPage) },
{ "RandomDrawPage", typeof(RandomDrawPage) },