diff --git a/.gitignore b/.gitignore index 16c6ee3..321a810 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ bld/ .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +src/TodoList.Maui/wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ @@ -363,3 +364,5 @@ MigrationBackup/ FodyWeavers.xsd /Setup/Output /TodoList/Output +/src/TodoList.Maui/Output +/src/TodoList.Host/todolist.db diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0532146 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // 使用 IntelliSense 找出 C# 调试存在哪些属性 + // 将悬停用于现有属性的说明 + // 有关详细信息,请访问 https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md。 + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // 如果已更改目标框架,请确保更新程序路径。 + "program": "${workspaceFolder}/src/TodoList.Host/bin/Debug/net10.0/TodoList.Host.dll", + "args": [], + "cwd": "${workspaceFolder}/src/TodoList.Host", + "stopAtEntry": false, + // 启用在启动 ASP.NET Core 时启动 Web 浏览器。有关详细信息: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..bcea2f5 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/TodoList.Host/TodoList.Host.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/TodoList.Host/TodoList.Host.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/src/TodoList.Host/TodoList.Host.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index c9c57a5..22738db 100644 --- a/README.md +++ b/README.md @@ -1,133 +1,170 @@ -# TodoList 待办事项管理应用 +# TodoList 跨平台待办事项管理应用 -一个基于 C# WPF 开发的轻量、高效桌面待办事项管理应用,专注于通过全局快捷键提供极致的快速记录体验。 +一个基于 MAUI + WebView 架构开发的跨平台待办事项管理应用,支持 Windows、macOS、Android、iOS 和 Linux(预览)平台。通过 HTTP API 实现前后端通信,提供轻量、高效的任务管理体验。 ## 🚀 功能特点 ### 核心功能 -- **全局快捷键快速记录**:支持系统级全局快捷键(如 `Ctrl + Alt + A`),随时唤起记录窗口 +- **跨平台支持**:基于 MAUI + WebView 架构,支持 Windows、macOS、Android、iOS 和 Linux(预览) +- **任务管理**:支持创建、编辑、删除、完成状态切换 - **优先级管理**:支持高、中、低三种优先级设置,通过颜色直观区分 -- **任务状态跟踪**:清晰标记任务完成状态,默认隐藏已完成任务 +- **任务状态跟踪**:清晰标记任务完成状态,支持过滤查看(全部/进行中/已完成) - **本地数据持久化**:使用 SQLite 数据库保存数据,支持完全离线使用 +- **HTTP API 通信**:前后端通过 RESTful API 进行数据交互 ### 技术特性 -- **响应式界面**:基于 WPF 构建的现代化用户界面 -- **MVVM 架构**:采用 CommunityToolkit.Mvvm 实现清晰的架构分层 -- **自包含发布**:支持单文件发布,无需额外依赖 -- **一键打包**:内置自动构建和打包脚本 +- **现代化架构**:MAUI + WebView + C# 后端 + Vue.js 前端 +- **分层设计**:Core(核心层)+ API(后端)+ Web(前端) +- **响应式界面**:Vue.js 3 实现的现代化用户界面 +- **统一 API 设计**:RESTful API 风格,支持跨域请求 ## 🛠️ 技术栈 -- **开发语言**:C# 10+ -- **UI 框架**:WPF (Windows Presentation Foundation) -- **目标框架**:.NET 8.0 -- **架构模式**:MVVM (Model-View-ViewModel) -- **数据存储**:SQLite (sqlite-net-pcl) -- **打包工具**:Inno Setup 6 -- **依赖管理**:NuGet +### 后端技术栈 +- **开发语言**:C# 10 +- **框架**:.NET 10 +- **UI 框架**:MAUI (Multi-platform App UI) +- **Web 服务器**:Kestrel (ASP.NET Core 内置) +- **API 框架**:ASP.NET Core Web API +- **数据访问**:Entity Framework Core +- **数据库**:SQLite (本地存储) +- **依赖注入**:Microsoft.Extensions.DependencyInjection + +### 前端技术栈 +- **开发语言**:TypeScript +- **框架**:Vue.js 3 +- **构建工具**:Vite +- **HTTP 客户端**:Axios +- **状态管理**:Pinia +- **UI 组件库**:Element Plus / Vant (移动端) +- **CSS 预处理器**:SCSS ## 📦 安装与使用 -### 直接安装 -1. 从 `Output` 目录下载最新的安装包:`TodoList_Setup_vX.X.X.exe` -2. 双击运行安装程序,按照提示完成安装 -3. 启动应用后,在系统托盘找到应用图标 - -### 使用说明 -- **快速记录**:按下预设的全局快捷键(默认为 `Ctrl + Alt + A`) -- **添加任务**:在快速记录窗口中输入任务内容,设置优先级,按 Enter 保存 -- **管理任务**:在主界面中查看、编辑和标记任务完成状态 -- **隐藏完成任务**:默认自动隐藏已完成任务,可通过界面开关显示 - -## 🔧 开发指南 - ### 环境要求 -- Visual Studio 2022 或更高版本 -- .NET 8.0 SDK -- Inno Setup 6(用于打包) +- **后端**: + - .NET 10 SDK + - Visual Studio 2022 或更高版本 +- **前端**: + - Node.js 18+ + - npm 或 yarn ### 快速开始 -1. **克隆或下载项目** - ```bash - git clone <仓库地址> - cd TodoList - ``` - -2. **打开项目** - - 使用 Visual Studio 打开 `TodoList.slnx` 解决方案 - - 或直接打开 `TodoList/TodoList.csproj` 项目文件 - -3. **安装依赖** - ```bash - dotnet restore - ``` - -4. **运行项目** - ```bash - dotnet run --project TodoList/TodoList.csproj - ``` - -### 构建与发布 - -使用内置的发布脚本进行一键构建和打包: - +#### 1. 克隆或下载项目 ```bash -cd TodoList/TodoList -powershell -ExecutionPolicy Bypass -File "BuildSetup.ps1" +git clone <仓库地址> +cd TodoList ``` -脚本功能: -- 自动递增版本号 -- 更新项目文件和安装脚本版本 -- 编译 Release 版本 -- 生成单文件可执行文件 -- 创建安装程序(输出到 `Output` 目录) +#### 2. 启动后端 API +```bash +cd src/TodoList.Api +dotnet restore +dotnet ef database update +dotnet run +``` +API 将在 `http://localhost:5173` 启动 -## 📁 项目结构 +#### 3. 启动前端 Web +```bash +cd src/TodoList.Web +npm install +npm run dev +``` +前端将在 `http://localhost:5173` 启动 +### 使用说明 +- **添加任务**:在前端界面中输入任务内容,设置优先级,点击添加按钮 +- **管理任务**:查看任务列表,支持按状态过滤(全部/进行中/已完成) +- **完成任务**:点击任务前的复选框切换完成状态 +- **删除任务**:点击删除按钮移除任务 + +## 🔧 开发指南 + +### 项目结构 ``` TodoList/ -├── TodoList/ # 主项目目录 -│ ├── Models/ # 数据模型 -│ ├── Services/ # 服务层(数据访问、快捷键等) -│ ├── ViewModels/ # 视图模型 -│ ├── Views/ # 界面视图 -│ ├── TodoList.csproj # 项目文件 -│ ├── BuildSetup.ps1 # 发布脚本 -│ └── setup.iss # Inno Setup 安装脚本 -├── TodoList.slnx # 解决方案文件 -├── PRD.md # 产品需求文档 -└── README.md # 项目说明文档 +├── docs/ # 文档目录 +│ ├── 产品需求文档.md +│ ├── 产品需求文档-1.1.0.md +│ ├── 技术设计文档.md +│ └── 代码规范文档.md +├── src/ # 源代码目录 +│ ├── TodoList.Core/ # 核心业务逻辑层 +│ │ ├── Entities/ # 实体类 +│ │ │ ├── Task.cs +│ │ │ └── TaskPriority.cs +│ │ └── Interfaces/ # 接口定义 +│ │ ├── ITaskRepository.cs +│ │ └── ITaskService.cs +│ ├── TodoList.Api/ # 后端 API 项目 +│ │ ├── Controllers/ # API 控制器 +│ │ │ └── TasksController.cs +│ │ ├── Services/ # 业务服务 +│ │ │ └── TaskService.cs +│ │ ├── Repositories/ # 数据访问层 +│ │ │ └── TaskRepository.cs +│ │ ├── Data/ # 数据库上下文 +│ │ │ ├── TodoDbContext.cs +│ │ │ └── Migrations/ # 数据库迁移 +│ │ ├── Models/ # 数据模型 +│ │ │ └── TaskModels.cs +│ │ ├── Program.cs # API 入口 +│ │ └── TodoList.Api.csproj # API 项目文件 +│ ├── TodoList.Web/ # 前端 Web 项目 (Vue.js) +│ │ ├── public/ # 静态资源 +│ │ ├── src/ +│ │ │ ├── api/ # API 调用 +│ │ │ │ ├── client.ts +│ │ │ │ └── tasks.ts +│ │ │ ├── components/ # Vue 组件 +│ │ │ │ ├── TaskList.vue +│ │ │ │ └── TaskItem.vue +│ │ │ ├── types/ # TypeScript 类型定义 +│ │ │ │ └── task.ts +│ │ │ ├── App.vue # 根组件 +│ │ │ └── main.ts # 应用入口 +│ │ ├── package.json # 依赖配置 +│ │ ├── vite.config.ts # Vite 配置 +│ │ └── tsconfig.json # TypeScript 配置 +│ └── TodoList.slnx # 解决方案文件 +├── .gitignore # Git 忽略文件 +└── README.md # 项目说明文档 ``` +### API 端点 +- `GET /api/tasks` - 获取任务列表 +- `GET /api/tasks/{id}` - 获取单个任务 +- `POST /api/tasks` - 创建任务 +- `PUT /api/tasks/{id}` - 更新任务 +- `PATCH /api/tasks/{id}/complete` - 切换完成状态 +- `DELETE /api/tasks/{id}` - 删除任务 + ## 🎯 核心模块说明 -### QuickEntryWindow -快速记录窗口,通过全局快捷键唤起,提供极简的任务输入体验。 +### TodoList.Core +核心业务逻辑层,定义领域模型和业务规则,提供核心业务接口。 -### MainWindow -主界面,展示任务列表,支持任务管理和状态切换。 +### TodoList.Api +后端 API 项目,提供 RESTful API 接口,处理业务逻辑,管理数据访问和持久化。 -### GlobalShortcutService -全局快捷键服务,负责注册和监听系统级快捷键。 - -### SqliteDataService -SQLite 数据服务,实现本地数据持久化。 +### TodoList.Web +前端 Web 项目,基于 Vue.js 3 + TypeScript,提供用户界面,通过 HTTP API 与后端通信。 ## 🔄 版本更新 ### 版本策略 - 采用语义化版本号:`MAJOR.MINOR.PATCH` -- 每次运行发布脚本自动递增 PATCH 版本 +- v1.0.0:初始 WPF 版本 +- v1.1.0:MAUI + WebView 跨平台版本 -### 更新日志 - -| 版本 | 日期 | 描述 | -|------|------|------| -| 1.0.17 | 2024-01-XX | 修复发布脚本和安装路径问题 | -| 1.0.16 | 2024-01-XX | 完善任务优先级显示 | -| 1.0.0 | 2024-01-XX | 初始版本发布 | +### v1.1.0 更新内容 +- 重构为 MAUI + WebView 架构 +- 实现跨平台支持 +- 使用 HTTP API 进行前后端通信 +- 采用 Vue.js 3 作为前端框架 +- 使用 SQLite 作为本地数据库 ## 🤝 贡献指南 @@ -144,8 +181,8 @@ SQLite 数据服务,实现本地数据持久化。 ## 📞 联系方式 - 项目作者:ShaoHua -- 项目地址: +- 项目地址:https://git.we965.cn/Tools/TodoList --- -**TodoList** - 让任务管理更高效! \ No newline at end of file +**TodoList** - 跨平台任务管理,让效率无处不在! diff --git a/TodoList.slnx b/TodoList.slnx index d463135..e6f9e0e 100644 --- a/TodoList.slnx +++ b/TodoList.slnx @@ -6,4 +6,8 @@ - + + + + + \ No newline at end of file diff --git a/TodoList/App.xaml.cs b/TodoList/App.xaml.cs index bf98aa1..033b4e6 100644 --- a/TodoList/App.xaml.cs +++ b/TodoList/App.xaml.cs @@ -7,16 +7,19 @@ using TodoList.Services; using TodoList.ViewModels; using TodoList.Views; using System.Linq; +using System.Reflection; namespace TodoList { public partial class App : System.Windows.Application { + private const string MainWindowTitle = "待办事项"; private IDataService _dataService; private GlobalShortcutService _shortcutService; private MainWindow _mainWindow; + private QuickEntryWindow? _quickEntryWindow; private SettingsService _settingsService; - private System.Windows.Forms.NotifyIcon _notifyIcon; + private NotifyIcon _notifyIcon; private Mutex _mutex; private EventWaitHandle _eventWaitHandle; private const string UniqueEventName = "Global\\TodoListApp_Event_v1"; @@ -59,7 +62,7 @@ namespace TodoList catch { // Fallback to old method if event open fails - var hWnd = FindWindow(null, "待办事项"); + var hWnd = FindWindow(null, MainWindowTitle); if (hWnd != IntPtr.Zero) { ShowWindow(hWnd, 9); // SW_RESTORE @@ -107,6 +110,7 @@ namespace TodoList var mainViewModel = new MainViewModel(_dataService, _settingsService); _mainWindow = new MainWindow(mainViewModel); + _mainWindow.Title = MainWindowTitle; _mainWindow.Loaded += MainWindow_Loaded; // Initialize Tray Icon @@ -131,7 +135,7 @@ namespace TodoList { try { - var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; + var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName; string cmd = $"\"{exePath}\" --silent"; // If running as dotnet tool, try to find the shim or stable entry point @@ -167,7 +171,7 @@ namespace TodoList private void InitializeTrayIcon() { - _notifyIcon = new System.Windows.Forms.NotifyIcon(); + _notifyIcon = new NotifyIcon(); // Try load icon from resource or file try @@ -194,15 +198,43 @@ namespace TodoList } _notifyIcon.Visible = true; - _notifyIcon.Text = "TodoList"; + _notifyIcon.Text = GetNotifyIconText(); _notifyIcon.DoubleClick += (s, e) => ShowMainWindow(); - var contextMenu = new System.Windows.Forms.ContextMenuStrip(); + var contextMenu = new ContextMenuStrip(); contextMenu.Items.Add("打开主界面", null, (s, e) => ShowMainWindow()); contextMenu.Items.Add("退出", null, (s, e) => ExitApplication()); _notifyIcon.ContextMenuStrip = contextMenu; } + private static string GetNotifyIconText() + { + var version = GetDisplayVersion(); + var text = string.IsNullOrWhiteSpace(version) ? MainWindowTitle : $"{MainWindowTitle} v{version}"; + + return text.Length > 63 ? text[..63] : text; + } + + private static string? GetDisplayVersion() + { + var asm = Assembly.GetExecutingAssembly(); + + var info = asm.GetCustomAttribute()?.InformationalVersion?.Trim(); + if (!string.IsNullOrWhiteSpace(info)) + { + var plus = info.IndexOf('+'); + return plus >= 0 ? info[..plus] : info; + } + + var file = asm.GetCustomAttribute()?.Version?.Trim(); + if (!string.IsNullOrWhiteSpace(file)) + { + return file; + } + + return asm.GetName().Version?.ToString(); + } + private void ShowMainWindow() { Log("ShowMainWindow called"); @@ -289,7 +321,31 @@ namespace TodoList private void OnHotKeyPressed() { Log("Hotkey pressed."); - ShowMainWindow(); + ShowQuickEntryWindow(); + } + + private void ShowQuickEntryWindow() + { + if (_quickEntryWindow == null) + { + _quickEntryWindow = new QuickEntryWindow(_dataService); + _quickEntryWindow.Title = "新建待办"; + } + + if (_quickEntryWindow.WindowState == WindowState.Minimized) + { + _quickEntryWindow.WindowState = WindowState.Normal; + } + + _quickEntryWindow.Show(); + _quickEntryWindow.Activate(); + + var helper = new WindowInteropHelper(_quickEntryWindow); + var handle = helper.Handle; + if (handle != IntPtr.Zero) + { + SetForegroundWindow(handle); + } } protected override void OnExit(ExitEventArgs e) diff --git a/TodoList/Services/FileDataService.cs b/TodoList/Services/FileDataService.cs index 7c0b10d..929da82 100644 --- a/TodoList/Services/FileDataService.cs +++ b/TodoList/Services/FileDataService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.Json; using System.Threading.Tasks; using TodoList.Models; @@ -19,7 +20,7 @@ namespace TodoList.Services _filePath = Path.Combine(folder, "tasks.json"); } - public async Task> LoadTasksAsync() + public async Task> LoadTasksAsync(bool? completed = null) { if (!File.Exists(_filePath)) { @@ -30,7 +31,13 @@ namespace TodoList.Services { using var stream = File.OpenRead(_filePath); var items = await JsonSerializer.DeserializeAsync>(stream); - return items ?? new List(); + var tasks = items ?? new List(); + + if (completed.HasValue) + { + return tasks.Where(t => t.IsCompleted == completed.Value).ToList(); + } + return tasks; } catch { @@ -38,7 +45,7 @@ namespace TodoList.Services } } - public async Task SaveTaskAsync(TodoItem task) + public async Task SaveTaskAsync(TodoItem task) { var tasks = await LoadTasksAsync(); var existing = tasks.Find(t => t.Id == task.Id); @@ -48,6 +55,34 @@ namespace TodoList.Services } tasks.Add(task); await SaveAllAsync(tasks); + return task; + } + + public async Task UpdateTaskAsync(TodoItem task) + { + var tasks = await LoadTasksAsync(); + var existing = tasks.Find(t => t.Id == task.Id); + if (existing != null) + { + tasks.Remove(existing); + tasks.Add(task); + await SaveAllAsync(tasks); + } + return task; + } + + public async Task ToggleCompleteAsync(string id) + { + var tasks = await LoadTasksAsync(); + var task = tasks.Find(t => t.Id == id); + if (task != null) + { + task.IsCompleted = !task.IsCompleted; + task.CompletedAt = task.IsCompleted ? DateTime.Now : null; + task.SyncStatus = SyncStatus.Pending; + await SaveAllAsync(tasks); + } + return task; } public async Task SaveAllAsync(List tasks) diff --git a/TodoList/Services/IDataService.cs b/TodoList/Services/IDataService.cs index 470378f..f5e690a 100644 --- a/TodoList/Services/IDataService.cs +++ b/TodoList/Services/IDataService.cs @@ -6,9 +6,10 @@ namespace TodoList.Services { public interface IDataService { - Task> LoadTasksAsync(); - Task SaveTaskAsync(TodoItem task); - Task SaveAllAsync(List tasks); + Task> LoadTasksAsync(bool? completed = null); + Task SaveTaskAsync(TodoItem task); + Task UpdateTaskAsync(TodoItem task); + Task ToggleCompleteAsync(string id); Task DeleteTaskAsync(string id); } } diff --git a/TodoList/Services/SqliteDataService.cs b/TodoList/Services/SqliteDataService.cs index bb2eabd..a541bd4 100644 --- a/TodoList/Services/SqliteDataService.cs +++ b/TodoList/Services/SqliteDataService.cs @@ -22,14 +22,39 @@ namespace TodoList.Services _database.CreateTableAsync().Wait(); } - public async Task> LoadTasksAsync() + public async Task> LoadTasksAsync(bool? completed = null) { - return await _database.Table().ToListAsync(); + var query = _database.Table(); + if (completed.HasValue) + { + query = query.Where(t => t.IsCompleted == completed.Value); + } + return await query.ToListAsync(); } - public async Task SaveTaskAsync(TodoItem task) + public async Task SaveTaskAsync(TodoItem task) { await _database.InsertOrReplaceAsync(task); + return task; + } + + public async Task UpdateTaskAsync(TodoItem task) + { + await _database.UpdateAsync(task); + return task; + } + + public async Task ToggleCompleteAsync(string id) + { + var task = await _database.FindAsync(id); + if (task != null) + { + task.IsCompleted = !task.IsCompleted; + task.CompletedAt = task.IsCompleted ? DateTime.Now : null; + task.SyncStatus = SyncStatus.Pending; + await _database.UpdateAsync(task); + } + return task; } public async Task SaveAllAsync(List tasks) diff --git a/TodoList/TodoList.csproj b/TodoList/TodoList.csproj index eac4167..f8328a4 100644 --- a/TodoList/TodoList.csproj +++ b/TodoList/TodoList.csproj @@ -5,6 +5,7 @@ net8.0-windows enable enable + 65001 true true icon.ico @@ -14,6 +15,7 @@ + diff --git a/TodoList/ViewModels/MainViewModel.cs b/TodoList/ViewModels/MainViewModel.cs index c0afc50..83c7454 100644 --- a/TodoList/ViewModels/MainViewModel.cs +++ b/TodoList/ViewModels/MainViewModel.cs @@ -55,8 +55,6 @@ namespace TodoList.ViewModels [ObservableProperty] private Models.SortOrder sortOrder = Models.SortOrder.Descending; - public string AppVersion => System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0"; - public string FullShortcut { get diff --git a/TodoList/Views/MainWindow.xaml b/TodoList/Views/MainWindow.xaml index 16af955..1fd19d8 100644 --- a/TodoList/Views/MainWindow.xaml +++ b/TodoList/Views/MainWindow.xaml @@ -7,7 +7,7 @@ xmlns:models="clr-namespace:TodoList.Models" xmlns:converters="clr-namespace:TodoList.Converters" mc:Ignorable="d" - Title="{Binding AppVersion, StringFormat='待办事项 v{0}'}" Height="600" Width="450" + Title="{Binding AppVersion, StringFormat='待办事项 v{0}'}" Height="450" Width="350" Background="#F5F5F7" Icon="/icon.ico" WindowStartupLocation="CenterScreen" @@ -73,7 +73,7 @@ - + @@ -83,10 +83,10 @@ - + + Width="70" VerticalContentAlignment="Center" Margin="0,0,3,0"> @@ -95,7 +95,7 @@ + Width="50" VerticalContentAlignment="Center"> @@ -106,7 +106,7 @@ - + VerticalAlignment="Center" Foreground="#555" FontSize="11"/> - + @@ -130,7 +130,7 @@ @@ -149,7 +149,7 @@ + Width="65" Margin="0,0,8,0" VerticalContentAlignment="Center"> @@ -158,7 +158,7 @@ - -