531 lines
21 KiB
C#
531 lines
21 KiB
C#
using System;
|
||
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Input;
|
||
using henlo_migration.Common;
|
||
using henlo_migration.Enums;
|
||
using henlo_migration.Models;
|
||
using henlo_migration.Services;
|
||
|
||
namespace henlo_migration.ViewModels
|
||
{
|
||
public class MainViewModel : INotifyPropertyChanged
|
||
{
|
||
// 六个 Tab 的数据
|
||
public TabServices TableTab { get; }
|
||
public TabServices TableFieldTab { get; }
|
||
public TabServices BosTab { get; }
|
||
public TabServices BosFieldTab { get; }
|
||
public TabServices PrcTab { get; }
|
||
public TabServices DataTab { get; }
|
||
|
||
// 当前激活的 TabServices
|
||
private TabServices? _currentTab;
|
||
private TabServices? CurrentTab
|
||
{
|
||
get => _currentTab;
|
||
set
|
||
{
|
||
_currentTab = value;
|
||
OnPropertyChanged();
|
||
OnPropertyChanged(nameof(TablesData));
|
||
OnPropertyChanged(nameof(TableFieldsData));
|
||
OnPropertyChanged(nameof(BosTablesData));
|
||
OnPropertyChanged(nameof(BosFieldsData));
|
||
OnPropertyChanged(nameof(StoredProcsData));
|
||
OnPropertyChanged(nameof(CurrentDataData));
|
||
}
|
||
}
|
||
|
||
// Tab 切换
|
||
private int _selectedTabIndex;
|
||
public int SelectedTabIndex
|
||
{
|
||
get => _selectedTabIndex;
|
||
set
|
||
{
|
||
_selectedTabIndex = value;
|
||
CurrentTab = value switch
|
||
{
|
||
0 => TableTab,
|
||
1 => TableFieldTab,
|
||
2 => BosTab,
|
||
3 => BosFieldTab,
|
||
4 => PrcTab,
|
||
5 => DataTab,
|
||
_ => TableTab
|
||
};
|
||
OnPropertyChanged();
|
||
OnPropertyChanged(nameof(TablesData));
|
||
OnPropertyChanged(nameof(TableFieldsData));
|
||
OnPropertyChanged(nameof(BosTablesData));
|
||
OnPropertyChanged(nameof(BosFieldsData));
|
||
OnPropertyChanged(nameof(StoredProcsData));
|
||
OnPropertyChanged(nameof(CurrentDataData));
|
||
OnPropertyChanged(nameof(CurrentTabName));
|
||
OnPropertyChanged(nameof(CurrentTabCount));
|
||
}
|
||
}
|
||
|
||
// 六个Tab数据源别名(供DataGrid绑定)
|
||
public ObservableCollection<TableRowModel> TablesData => TableTab.List;
|
||
public ObservableCollection<TableRowModel> TableFieldsData => TableFieldTab.List;
|
||
public ObservableCollection<TableRowModel> BosTablesData => BosTab.List;
|
||
public ObservableCollection<TableRowModel> BosFieldsData => BosFieldTab.List;
|
||
public ObservableCollection<TableRowModel> StoredProcsData => PrcTab.List;
|
||
public ObservableCollection<TableRowModel> CurrentDataData => DataTab.List;
|
||
|
||
public string CurrentTabName => SelectedTabIndex switch
|
||
{
|
||
0 => "表配置",
|
||
1 => "表字段",
|
||
2 => "BOS配置",
|
||
3 => "BOS字段配置",
|
||
4 => "存储过程",
|
||
5 => "表数据",
|
||
_ => ""
|
||
};
|
||
|
||
public string CurrentTabCount => CurrentTab?.List.Count.ToString() ?? "0";
|
||
|
||
// 选中的行
|
||
private TableRowModel? _selectedRow;
|
||
public TableRowModel? SelectedRow
|
||
{
|
||
get => _selectedRow;
|
||
set
|
||
{
|
||
_selectedRow = value;
|
||
OnPropertyChanged();
|
||
// 选中行时自动加载已有文件内容
|
||
LoadSelectedRowContent();
|
||
}
|
||
}
|
||
|
||
private void LoadSelectedRowContent()
|
||
{
|
||
if (_selectedRow == null || CurrentTab == null) return;
|
||
|
||
// 构建文件路径
|
||
string fileName = $"{_selectedRow.Name}_{CurrentTab.GetCodeExt()}.txt";
|
||
string filePath = Path.Combine(CurrentTab.GetContentFolderPath(), fileName);
|
||
|
||
// 始终设置 CurrentFileName,这样保存按钮可用
|
||
CurrentFileName = filePath;
|
||
|
||
if (File.Exists(filePath))
|
||
{
|
||
try
|
||
{
|
||
string content = File.ReadAllText(filePath);
|
||
CurrentFileContent = content;
|
||
SqlCode = content;
|
||
}
|
||
catch { CurrentFileContent = ""; SqlCode = ""; }
|
||
}
|
||
else
|
||
{
|
||
// 文件不存在,清空内容但保留文件路径
|
||
CurrentFileContent = "";
|
||
SqlCode = "";
|
||
}
|
||
}
|
||
|
||
// SQL 编辑区
|
||
private string _sqlCode = "";
|
||
public string SqlCode
|
||
{
|
||
get => _sqlCode;
|
||
set { _sqlCode = value; OnPropertyChanged(); }
|
||
}
|
||
|
||
// 选中行详情面板
|
||
private string _currentFileName = "";
|
||
public string CurrentFileName
|
||
{
|
||
get => _currentFileName;
|
||
set { _currentFileName = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasCurrentFile)); (SaveCurrentContentCommand as RelayCommand)?.RaiseCanExecuteChanged(); }
|
||
}
|
||
|
||
private string _currentFileContent = "";
|
||
public string CurrentFileContent
|
||
{
|
||
get => _currentFileContent;
|
||
set { _currentFileContent = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasCurrentFile)); }
|
||
}
|
||
|
||
public bool HasCurrentFile => !string.IsNullOrEmpty(CurrentFileName);
|
||
|
||
// 保存当前文件内容命令
|
||
public ICommand SaveCurrentContentCommand { get; }
|
||
|
||
// 输入框
|
||
private string _inputText = "";
|
||
public string InputText
|
||
{
|
||
get => _inputText;
|
||
set { _inputText = value; OnPropertyChanged(); }
|
||
}
|
||
|
||
// 数据库连接状态
|
||
private bool _origConnected;
|
||
public bool OrigConnected
|
||
{
|
||
get => _origConnected;
|
||
set
|
||
{
|
||
_origConnected = value;
|
||
OnPropertyChanged();
|
||
// 触发命令可用性更新
|
||
(ConnectOrigCommand as RelayCommand)?.RaiseCanExecuteChanged();
|
||
(DisconnectOrigCommand as RelayCommand)?.RaiseCanExecuteChanged();
|
||
}
|
||
}
|
||
|
||
private bool _destConnected;
|
||
public bool DestConnected
|
||
{
|
||
get => _destConnected;
|
||
set
|
||
{
|
||
_destConnected = value;
|
||
OnPropertyChanged();
|
||
(ConnectDestCommand as RelayCommand)?.RaiseCanExecuteChanged();
|
||
(DisconnectDestCommand as RelayCommand)?.RaiseCanExecuteChanged();
|
||
}
|
||
}
|
||
|
||
// 连接中状态
|
||
private bool _isConnecting;
|
||
public bool IsConnecting
|
||
{
|
||
get => _isConnecting;
|
||
set
|
||
{
|
||
_isConnecting = value;
|
||
OnPropertyChanged();
|
||
OnPropertyChanged(nameof(CanConnect));
|
||
(ConnectOrigCommand as RelayCommand)?.RaiseCanExecuteChanged();
|
||
(ConnectDestCommand as RelayCommand)?.RaiseCanExecuteChanged();
|
||
}
|
||
}
|
||
|
||
public bool CanConnect => !IsConnecting;
|
||
|
||
// 连接配置属性(密码改为明文显示,方便用户确认,保存时才加密写入)
|
||
private string _origIp = "";
|
||
public string OrigIp { get => _origIp; set { _origIp = value; OnPropertyChanged(); OnPropertyChanged(nameof(OrigConnDisplay)); } }
|
||
private string _origExam = "";
|
||
public string OrigExam { get => _origExam; set { _origExam = value; OnPropertyChanged(); OnPropertyChanged(nameof(OrigConnDisplay)); } }
|
||
private string _origUser = "";
|
||
public string OrigUser { get => _origUser; set { _origUser = value; OnPropertyChanged(); OnPropertyChanged(nameof(OrigConnDisplay)); } }
|
||
private string _origPwd = "";
|
||
public string OrigPwd { get => _origPwd; set { _origPwd = value; OnPropertyChanged(); } }
|
||
|
||
private string _destIp = "";
|
||
public string DestIp { get => _destIp; set { _destIp = value; OnPropertyChanged(); OnPropertyChanged(nameof(DestConnDisplay)); } }
|
||
private string _destExam = "";
|
||
public string DestExam { get => _destExam; set { _destExam = value; OnPropertyChanged(); OnPropertyChanged(nameof(DestConnDisplay)); } }
|
||
private string _destUser = "";
|
||
public string DestUser { get => _destUser; set { _destUser = value; OnPropertyChanged(); OnPropertyChanged(nameof(DestConnDisplay)); } }
|
||
private string _destPwd = "";
|
||
public string DestPwd { get => _destPwd; set { _destPwd = value; OnPropertyChanged(); } }
|
||
|
||
public string OrigConnDisplay => string.IsNullOrEmpty(OrigIp) ? "未配置" : $"{OrigIp}/{OrigExam}/{OrigUser}";
|
||
public string DestConnDisplay => string.IsNullOrEmpty(DestIp) ? "未配置" : $"{DestIp}/{DestExam}/{DestUser}";
|
||
|
||
// 密码遮罩显示(用于 TextBox 绑定)
|
||
public string OrigPwdDisplay => new string('●', OrigPwd?.Length ?? 0);
|
||
public string DestPwdDisplay => new string('●', DestPwd?.Length ?? 0);
|
||
|
||
// 命令
|
||
public ICommand ConnectOrigCommand { get; }
|
||
public ICommand ConnectDestCommand { get; }
|
||
public ICommand SaveOrigCommand { get; }
|
||
public ICommand SaveDestCommand { get; }
|
||
public ICommand DisconnectOrigCommand { get; }
|
||
public ICommand DisconnectDestCommand { get; }
|
||
public ICommand AddRowCommand { get; }
|
||
public ICommand OpenDataFolderCommand { get; }
|
||
public ICommand BatchReadCommand { get; }
|
||
public ICommand BatchWriteCommand { get; }
|
||
|
||
// 行操作命令
|
||
public ICommand ReadCommand { get; }
|
||
public ICommand WriteCommand { get; }
|
||
public ICommand DeleteCommand { get; }
|
||
public ICommand MoveUpCommand { get; }
|
||
public ICommand MoveDownCommand { get; }
|
||
public ICommand MoveTopCommand { get; }
|
||
|
||
public MainViewModel()
|
||
{
|
||
// 从配置加载连接信息
|
||
OrigIp = SysConfig.GetOrigIp();
|
||
OrigExam = SysConfig.GetOrigExam();
|
||
OrigUser = SysConfig.GetOrigUser();
|
||
OrigPwd = SysConfig.GetOrigPwd();
|
||
DestIp = SysConfig.GetDestIp();
|
||
DestExam = SysConfig.GetDestExam();
|
||
DestUser = SysConfig.GetDestUser();
|
||
DestPwd = SysConfig.GetDestPwd();
|
||
|
||
// 初始化六个 TabServices(索引文件在 from/,内容文件在 data/)
|
||
TableTab = new TabServices(new TabConfig(TabsEnum.表, CommonHelper.DataFromPath, CommonHelper.DataPath, "table.txt", CodeExt.creat,
|
||
DbServices.GetTableSql, (p, c) => SqlCode = c));
|
||
|
||
TableFieldTab = new TabServices(new TabConfig(TabsEnum.表字段, CommonHelper.DataFromPath, CommonHelper.DataPath, "table_field.txt", CodeExt.table_field,
|
||
DbServices.GetTableField, (p, c) => SqlCode = c));
|
||
|
||
BosTab = new TabServices(new TabConfig(TabsEnum.BOS表配置, CommonHelper.DataFromPath, CommonHelper.DataPath, "bos.txt", CodeExt.bos,
|
||
DbServices.GetTableBos, (p, c) => SqlCode = c));
|
||
|
||
BosFieldTab = new TabServices(new TabConfig(TabsEnum.BOS字段配置, CommonHelper.DataFromPath, CommonHelper.DataPath, "bos_field.txt", CodeExt.bos_field,
|
||
DbServices.GetTableBosField, (p, c) => SqlCode = c));
|
||
|
||
PrcTab = new TabServices(new TabConfig(TabsEnum.存储过程, CommonHelper.DataFromPath, CommonHelper.DataPath, "prc.txt", CodeExt.prc,
|
||
name => DbServices.GetPrcSql(name), (p, c) => SqlCode = c));
|
||
|
||
DataTab = new TabServices(new TabConfig(TabsEnum.表数据, CommonHelper.DataFromPath, CommonHelper.DataPath, "data.txt", CodeExt.data,
|
||
DbServices.GetTableData, (p, c) => SqlCode = c));
|
||
|
||
// Tab行点击 -> 显示SQL
|
||
foreach (var tab in new[] { TableTab, TableFieldTab, BosTab, BosFieldTab, PrcTab, DataTab })
|
||
{
|
||
tab.RowClickEvent += (path, code) =>
|
||
{
|
||
SqlCode = code;
|
||
CurrentFileName = path;
|
||
CurrentFileContent = code;
|
||
};
|
||
}
|
||
|
||
// 命令初始化
|
||
ConnectOrigCommand = new RelayCommand(async () => await ConnectOrigAsync(), () => !IsConnecting && !OrigConnected);
|
||
ConnectDestCommand = new RelayCommand(async () => await ConnectDestAsync(), () => !IsConnecting && !DestConnected);
|
||
SaveOrigCommand = new RelayCommand(SaveOrig);
|
||
SaveDestCommand = new RelayCommand(SaveDest);
|
||
DisconnectOrigCommand = new RelayCommand(DisconnectOrig, () => OrigConnected);
|
||
DisconnectDestCommand = new RelayCommand(DisconnectDest, () => DestConnected);
|
||
AddRowCommand = new RelayCommand(AddRow, () => CurrentTab != null && !string.IsNullOrWhiteSpace(InputText));
|
||
OpenDataFolderCommand = new RelayCommand(OpenDataFolder);
|
||
BatchReadCommand = new RelayCommand(async () => await BatchReadAsync());
|
||
BatchWriteCommand = new RelayCommand(async () => await BatchWriteAsync());
|
||
|
||
ReadCommand = new RelayCommand<TableRowModel>(async row => await ReadRowAsync(row));
|
||
WriteCommand = new RelayCommand<TableRowModel>(async row => await WriteRowAsync(row));
|
||
DeleteCommand = new RelayCommand<TableRowModel>(DeleteRow);
|
||
MoveUpCommand = new RelayCommand<TableRowModel>(MoveUp);
|
||
MoveDownCommand = new RelayCommand<TableRowModel>(MoveDown);
|
||
MoveTopCommand = new RelayCommand<TableRowModel>(MoveTop);
|
||
SaveCurrentContentCommand = new RelayCommand(SaveCurrentContent, () => HasCurrentFile);
|
||
|
||
// 默认选中第一个Tab
|
||
CurrentTab = TableTab;
|
||
}
|
||
|
||
private async Task ConnectOrigAsync()
|
||
{
|
||
IsConnecting = true;
|
||
try
|
||
{
|
||
SysConfig.SetOrig(OrigIp, OrigExam, OrigUser, OrigPwd);
|
||
SqlSugarHelper.Connect();
|
||
OrigConnected = SqlSugarHelper.CheckOrigConnect();
|
||
if (!OrigConnected) HandyControl.Controls.Growl.Warning("源数据库连接失败,请检查配置");
|
||
else HandyControl.Controls.Growl.Success("源数据库连接成功");
|
||
}
|
||
catch
|
||
{
|
||
OrigConnected = false;
|
||
HandyControl.Controls.Growl.Warning("源数据库连接失败");
|
||
}
|
||
finally { IsConnecting = false; }
|
||
}
|
||
|
||
private async Task ConnectDestAsync()
|
||
{
|
||
IsConnecting = true;
|
||
try
|
||
{
|
||
SysConfig.SetDest(DestIp, DestExam, DestUser, DestPwd);
|
||
SqlSugarHelper.Connect();
|
||
DestConnected = SqlSugarHelper.CheckDescConnect();
|
||
if (!DestConnected) HandyControl.Controls.Growl.Warning("目标数据库连接失败,请检查配置");
|
||
else HandyControl.Controls.Growl.Success("目标数据库连接成功");
|
||
}
|
||
catch
|
||
{
|
||
DestConnected = false;
|
||
HandyControl.Controls.Growl.Warning("目标数据库连接失败");
|
||
}
|
||
finally { IsConnecting = false; }
|
||
}
|
||
|
||
// 保存配置(只写 INI,不断开/重连)
|
||
private void SaveOrig() { SysConfig.SetOrig(OrigIp, OrigExam, OrigUser, OrigPwd); HandyControl.Controls.Growl.Success("源库配置已保存"); }
|
||
private void SaveDest() { SysConfig.SetDest(DestIp, DestExam, DestUser, DestPwd); HandyControl.Controls.Growl.Success("目标库配置已保存"); }
|
||
|
||
// 断开连接(只改状态,不真正关闭 SqlSugarScope,保持实例可用)
|
||
private void DisconnectOrig()
|
||
{
|
||
OrigConnected = false;
|
||
HandyControl.Controls.Growl.Info("已断开源数据库");
|
||
}
|
||
|
||
private void DisconnectDest()
|
||
{
|
||
DestConnected = false;
|
||
HandyControl.Controls.Growl.Info("已断开目标数据库");
|
||
}
|
||
|
||
private void AddRow()
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(InputText))
|
||
{
|
||
string itemName = InputText.Trim();
|
||
if (CurrentTab == TableFieldTab || CurrentTab == BosFieldTab)
|
||
{
|
||
if (!DbServices.TryParseTableFieldName(itemName, out string tableName, out string columnName))
|
||
{
|
||
string tabName = CurrentTab == TableFieldTab ? "表字段" : "BOS 字段配置";
|
||
HandyControl.Controls.Growl.Warning($"{tabName}必须使用 TABLE.COLUMN 格式");
|
||
return;
|
||
}
|
||
itemName = $"{tableName}.{columnName}";
|
||
}
|
||
|
||
CurrentTab?.AddRow(itemName.ToLowerInvariant());
|
||
InputText = "";
|
||
}
|
||
}
|
||
|
||
private void SaveCurrentContent()
|
||
{
|
||
if (string.IsNullOrEmpty(CurrentFileName)) return;
|
||
try
|
||
{
|
||
File.WriteAllText(CurrentFileName, CurrentFileContent);
|
||
HandyControl.Controls.Growl.Success("保存成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
HandyControl.Controls.Growl.Error($"保存失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void OpenDataFolder()
|
||
{
|
||
Process.Start("explorer.exe", CommonHelper.DataPath);
|
||
}
|
||
|
||
private async Task BatchReadAsync()
|
||
{
|
||
if (CurrentTab == null) return;
|
||
foreach (var row in CurrentTab.List)
|
||
await CurrentTab.ReadRow(row);
|
||
}
|
||
|
||
/// <summary>批量写入断点:记录上次失败行的索引,下次从此处继续</summary>
|
||
private int _breakpointIndex = -1;
|
||
public int BreakpointIndex
|
||
{
|
||
get => _breakpointIndex;
|
||
set { _breakpointIndex = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasBreakpoint)); }
|
||
}
|
||
public bool HasBreakpoint => _breakpointIndex >= 0;
|
||
|
||
private async Task BatchWriteAsync()
|
||
{
|
||
if (CurrentTab == null) return;
|
||
int startIdx = _breakpointIndex >= 0 ? _breakpointIndex : 0;
|
||
bool hasNewFailure = false;
|
||
for (int i = startIdx; i < CurrentTab.List.Count; i++)
|
||
{
|
||
var row = CurrentTab.List[i];
|
||
await CurrentTab.WriteRow(row);
|
||
if (row.WStatus == StatusEnum.写入失败)
|
||
{
|
||
_breakpointIndex = i;
|
||
OnPropertyChanged(nameof(BreakpointIndex));
|
||
OnPropertyChanged(nameof(HasBreakpoint));
|
||
hasNewFailure = true;
|
||
break;
|
||
}
|
||
}
|
||
// 全部成功(或从断点后全部成功)→ 重置断点
|
||
if (!hasNewFailure && _breakpointIndex >= 0)
|
||
{
|
||
_breakpointIndex = -1;
|
||
OnPropertyChanged(nameof(BreakpointIndex));
|
||
OnPropertyChanged(nameof(HasBreakpoint));
|
||
}
|
||
}
|
||
|
||
private async Task ReadRowAsync(TableRowModel? row)
|
||
{
|
||
if (row == null || CurrentTab == null) return;
|
||
await CurrentTab.ReadRow(row);
|
||
}
|
||
|
||
private async Task WriteRowAsync(TableRowModel? row)
|
||
{
|
||
if (row == null || CurrentTab == null) return;
|
||
await CurrentTab.WriteRow(row);
|
||
}
|
||
|
||
private void DeleteRow(TableRowModel? row)
|
||
{
|
||
CurrentTab?.DeleteRow(row!);
|
||
}
|
||
|
||
private void MoveUp(TableRowModel? row)
|
||
{
|
||
CurrentTab?.MoveUp(row!);
|
||
}
|
||
|
||
private void MoveDown(TableRowModel? row)
|
||
{
|
||
CurrentTab?.MoveDown(row!);
|
||
}
|
||
|
||
private void MoveTop(TableRowModel? row)
|
||
{
|
||
CurrentTab?.MoveToTop(row!);
|
||
}
|
||
|
||
public event PropertyChangedEventHandler? PropertyChanged;
|
||
protected void OnPropertyChanged([CallerMemberName] string? name = null)
|
||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||
}
|
||
|
||
// 简单命令(无参数)
|
||
public class RelayCommand : ICommand
|
||
{
|
||
private readonly Action _act;
|
||
private readonly Func<bool>? _can;
|
||
public RelayCommand(Action act, Func<bool>? can = null) { _act = act; _can = can; }
|
||
public bool CanExecute(object? p) => _can?.Invoke() ?? true;
|
||
public void Execute(object? p) => _act();
|
||
public event EventHandler? CanExecuteChanged;
|
||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||
}
|
||
|
||
// 简单命令(带参数)
|
||
public class RelayCommand<T> : ICommand
|
||
{
|
||
private readonly Action<T?> _act;
|
||
public RelayCommand(Action<T?> act) { _act = act; }
|
||
public bool CanExecute(object? p) => true;
|
||
public void Execute(object? p) => _act((T?)p);
|
||
#pragma warning disable CS0067
|
||
public event EventHandler? CanExecuteChanged;
|
||
#pragma warning restore CS0067
|
||
}
|
||
}
|