WPF 上位机开发模板

WPF 上位机开发模板

WPF上位机开发模板,集成了基础操作菜单、海康视觉实时图像界面、串口通讯、网口通讯、主流PLC通讯、数据存储、图片存储、参数配置、权限管理、第三方webapi接口接入、数据追溯与查询等功能。

一、项目结构

WpfSupervisor/
├── Models/                  # 数据模型
│   ├── DeviceModels.cs
│   ├── ImageModel.cs
│   ├── LogModel.cs
│   ├── ParameterModel.cs
│   └── UserModel.cs
├── Services/                # 服务层
│   ├── Communication/
│   │   ├── ComService.cs
│   │   ├── EthernetService.cs
│   │   ├── PlcService.cs
│   │   └── WebApiService.cs
│   ├── Database/
│   │   ├── DatabaseService.cs
│   │   └── ImageStorage.cs
│   ├── HikVision/
│   │   └── HikVisionService.cs
│   ├── Security/
│   │   ├── AuthService.cs
│   │   └── PermissionService.cs
│   └── Utility/
│       ├── ConfigManager.cs
│       └── Logger.cs
├── ViewModels/              # 视图模型
│   ├── MainViewModel.cs
│   ├── CommunicationViewModel.cs
│   ├── ImageViewModel.cs
│   ├── ParameterViewModel.cs
│   └── UserViewModel.cs
├── Views/                   # 视图
│   ├── MainWindow.xaml
│   ├── CommunicationView.xaml
│   ├── ImageView.xaml
│   ├── ParameterView.xaml
│   └── LoginView.xaml
├── Helpers/                 # 辅助类
│   ├── RelayCommand.cs
│   └── EnumExtensions.cs
└── App.xaml.cs              # 应用程序入口

二、核心代码实现

1. 数据模型 (Models/)

// DeviceModels.cs
public class PlcDevice
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string IpAddress { get; set; }
    public int Port { get; set; }
    public string Protocol { get; set; } // Modbus, S7, etc.
}

public class SerialDevice
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string PortName { get; set; }
    public int BaudRate { get; set; }
    public Parity Parity { get; set; }
    public int DataBits { get; set; }
    public StopBits StopBits { get; set; }
}

// ImageModel.cs
public class CapturedImage
{
    public string Id { get; set; }
    public byte[] ImageData { get; set; }
    public DateTime CaptureTime { get; set; }
    public string DeviceId { get; set; }
    public string FilePath { get; set; }
}

// LogModel.cs
public class SystemLog
{
    public string Id { get; set; }
    public DateTime Timestamp { get; set; }
    public string Level { get; set; } // Info, Warning, Error
    public string Message { get; set; }
    public string UserId { get; set; }
}

// ParameterModel.cs
public class SystemParameter
{
    public string Id { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }
    public string Description { get; set; }
    public string Category { get; set; }
}

// UserModel.cs
public class User
{
    public string Id { get; set; }
    public string Username { get; set; }
    public string PasswordHash { get; set; }
    public string FullName { get; set; }
    public string Role { get; set; } // Admin, Operator, etc.
    public DateTime LastLogin { get; set; }
}

2. 服务层 (Services/)

2.1 通信服务
// ComService.cs
public class ComService : IDisposable
{
    private SerialPort _serialPort;
    public event Action<string> DataReceived;
    
    public bool IsOpen => _serialPort?.IsOpen ?? false;
    
    public void Open(SerialDevice device)
    {
        _serialPort = new SerialPort(device.PortName, device.BaudRate, device.Parity, device.DataBits, device.StopBits);
        _serialPort.DataReceived += (s, e) => 
        {
            try
            {
                var data = _serialPort.ReadExisting();
                DataReceived?.Invoke(data);
            }
            catch (Exception ex)
            {
                Logger.LogError($"串口数据接收错误: {ex.Message}");
            }
        };
        
        _serialPort.Open();
    }
    
    public void Close() => _serialPort?.Close();
    
    public void Send(string data) => _serialPort?.Write(data);
    
    public void Dispose() => Close();
}

// EthernetService.cs
public class EthernetService : IDisposable
{
    private TcpClient _tcpClient;
    private NetworkStream _stream;
    public event Action<string> DataReceived;
    
    public bool IsConnected => _tcpClient?.Connected ?? false;
    
    public async Task ConnectAsync(string ipAddress, int port)
    {
        _tcpClient = new TcpClient();
        await _tcpClient.ConnectAsync(ipAddress, port);
        _stream = _tcpClient.GetStream();
        
        _ = ReceiveDataAsync();
    }
    
    private async Task ReceiveDataAsync()
    {
        try
        {
            var buffer = new byte[1024];
            while (_stream != null && _stream.CanRead)
            {
                int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
                var data = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                DataReceived?.Invoke(data);
            }
        }
        catch (Exception ex)
        {
            Logger.LogError($"以太网数据接收错误: {ex.Message}");
        }
    }
    
    public void Send(string data)
    {
        if (_stream == null || !_stream.CanWrite) return;
        
        var bytes = Encoding.ASCII.GetBytes(data);
        _stream.Write(bytes, 0, bytes.Length);
    }
    
    public void Disconnect() => _tcpClient?.Close();
    
    public void Dispose()
    {
        Disconnect();
        _stream?.Close();
    }
}

// PlcService.cs (使用S7.Net库示例)
public class PlcService : IDisposable
{
    private S7.Net.PLC _plc;
    public event Action<string> DataReceived;
    
    public bool IsConnected => _plc?.IsConnected ?? false;
    
    public async Task ConnectAsync(PlcDevice device)
    {
        _plc = new S7.Net.PLC(device.Protocol == "S7" 
            ? S7.Net.CpuType.S71200 
            : S7.Net.CpuType.S7300, 
            device.IpAddress, device.Port);
            
        await Task.Run(() => _plc.Open());
    }
    
    public async Task<T> ReadAsync<T>(string address)
    {
        if (!IsConnected) throw new InvalidOperationException("PLC未连接");
        
        return await Task.Run(() => (T)_plc.Read(address));
    }
    
    public async Task WriteAsync<T>(string address, T value)
    {
        if (!IsConnected) throw new InvalidOperationException("PLC未连接");
        
        await Task.Run(() => _plc.Write(address, value));
    }
    
    public void Dispose() => _plc?.Close();
}

// WebApiService.cs
public class WebApiService
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;
    
    public WebApiService(string baseUrl)
    {
        _baseUrl = baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/";
        _httpClient = new HttpClient();
    }
    
    public async Task<T> GetAsync<T>(string endpoint)
    {
        var response = await _httpClient.GetAsync(_baseUrl + endpoint);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsAsync<T>();
    }
    
    public async Task PostAsync<T>(string endpoint, object data)
    {
        var content = new StringContent(JsonConvert.SerializeObject(data), 
            Encoding.UTF8, "application/json");
        var response = await _httpClient.PostAsync(_baseUrl + endpoint, content);
        response.EnsureSuccessStatusCode();
    }
}
2.2 数据库服务
 
// DatabaseService.cs
public class DatabaseService
{
    private readonly string _connectionString;
    private SQLiteConnection _connection;
    
    public DatabaseService(string dbPath)
    {
        _connectionString = $"Data Source={dbPath};Version=3;";
        InitializeDatabase();
    }
    
    private void InitializeDatabase()
    {
        _connection = new SQLiteConnection(_connectionString);
        _connection.Open();
        
        // 创建表
        ExecuteNonQuery(@"
            CREATE TABLE IF NOT EXISTS SystemLogs (
                Id TEXT PRIMARY KEY,
                Timestamp TEXT,
                Level TEXT,
                Message TEXT,
                UserId TEXT
            );
            
            CREATE TABLE IF NOT EXISTS SystemParameters (
                Id TEXT PRIMARY KEY,
                Key TEXT,
                Value TEXT,
                Description TEXT,
                Category TEXT
            );
            
            CREATE TABLE IF NOT EXISTS Users (
                Id TEXT PRIMARY KEY,
                Username TEXT,
                PasswordHash TEXT,
                FullName TEXT,
                Role TEXT,
                LastLogin TEXT
            );
        ");
    }
    
    public void ExecuteNonQuery(string sql, params object[] parameters)
    {
        using (var cmd = new SQLiteCommand(sql, _connection))
        {
            for (int i = 0; i < parameters.Length; i++)
            {
                cmd.Parameters.AddWithValue($"@p{i}", parameters[i]);
            }
            cmd.ExecuteNonQuery();
        }
    }
    
    public T ExecuteScalar<T>(string sql, params object[] parameters)
    {
        using (var cmd = new SQLiteCommand(sql, _connection))
        {
            for (int i = 0; i < parameters.Length; i++)
            {
                cmd.Parameters.AddWithValue($"@p{i}", parameters[i]);
            }
            return (T)cmd.ExecuteScalar();
        }
    }
    
    public DataTable ExecuteQuery(string sql, params object[] parameters)
    {
        using (var cmd = new SQLiteCommand(sql, _connection))
        {
            for (int i = 0; i < parameters.Length; i++)
            {
                cmd.Parameters.AddWithValue($"@p{i}", parameters[i]);
            }
            
            var adapter = new SQLiteDataAdapter(cmd);
            var table = new DataTable();
            adapter.Fill(table);
            return table;
        }
    }
    
    public void Dispose()
    {
        _connection?.Close();
    }
}

// ImageStorage.cs
public class ImageStorage
{
    private readonly string _imageFolderPath;
    private readonly DatabaseService _dbService;
    
    public ImageStorage(string folderPath, DatabaseService dbService)
    {
        _imageFolderPath = folderPath;
        Directory.CreateDirectory(_imageFolderPath);
        _dbService = dbService;
    }
    
    public async Task SaveImageAsync(CapturedImage image)
    {
        // 保存到数据库
        var imageId = Guid.NewGuid().ToString();
        var parameters = new object[]
        {
            imageId,
            image.ImageData != null ? Convert.ToBase64String(image.ImageData) : null,
            image.CaptureTime.ToString("o"),
            image.DeviceId,
            image.FilePath
        };
        
        _dbService.ExecuteNonQuery(@"
            INSERT INTO Images (Id, Data, CaptureTime, DeviceId, FilePath)
            VALUES (@p0, @p1, @p2, @p3, @p4);
        ", parameters);
        
        // 保存文件
        if (image.ImageData != null)
        {
            var filePath = Path.Combine(_imageFolderPath, $"{imageId}.jpg");
            await File.WriteAllBytesAsync(filePath, image.ImageData);
            
            // 更新数据库中的文件路径
            _dbService.ExecuteNonQuery(@"
                UPDATE Images SET FilePath = @p0 WHERE Id = @p1;
            ", filePath, imageId);
        }
    }
    
    public async Task<CapturedImage> GetImageAsync(string id)
    {
        var row = _dbService.ExecuteQuery(
            "SELECT * FROM Images WHERE Id = @p0;", id).Rows[0];
            
        return new CapturedImage
        {
            Id = row["Id"].ToString(),
            ImageData = row["Data"] != DBNull.Value 
                ? Convert.FromBase64String(row["Data"].ToString()) 
                : null,
            CaptureTime = DateTime.Parse(row["CaptureTime"].ToString()),
            DeviceId = row["DeviceId"].ToString(),
            FilePath = row["FilePath"]?.ToString()
        };
    }
}
2.3 海康视觉服务
// HikVisionService.cs
public class HikVisionService
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;
    private readonly string _username;
    private readonly string _password;
    
    public event Action<byte[]> ImageReceived;
    
    public HikVisionService(string baseUrl, string username, string password)
    {
        _baseUrl = baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/";
        _username = username;
        _password = password;
        _httpClient = new HttpClient();
        
        // 登录
        Login();
    }
    
    private void Login()
    {
        var loginData = new Dictionary<string, string>
        {
            {"action", "login"},
            {"user", _username},
            {"password", _password}
        };
        
        var content = new FormUrlEncodedContent(loginData);
        var response = _httpClient.PostAsync(_baseUrl + "login", content).Result;
        response.EnsureSuccessStatusCode();
    }
    
    public async Task StartRealTimeImage()
    {
        // 启动实时图像流
        var streamResponse = await _httpClient.GetAsync($"{_baseUrl}stream");
        streamResponse.EnsureSuccessStatusCode();
        
        var stream = await streamResponse.Content.ReadAsStreamAsync();
        
        // 处理图像流数据
        using (var reader = new BinaryReader(stream))
        {
            while (true)
            {
                // 实际实现需要解析海康威视的流协议
                // 这里简化处理
                var buffer = reader.ReadBytes(1024);
                if (buffer.Length > 0)
                {
                    // 解码图像数据
                    var imageData = DecodeHikVisionImage(buffer);
                    ImageReceived?.Invoke(imageData);
                }
            }
        }
    }
    
    private byte[] DecodeHikVisionImage(byte[] buffer)
    {
        // 实际实现需要根据海康威视的图像编码格式解码
        // 这里简化处理,直接返回原始数据
        return buffer;
    }
}
2.4 安全服务
 
// AuthService.cs
public class AuthService
{
    private readonly DatabaseService _dbService;
    
    public AuthService(DatabaseService dbService)
    {
        _dbService = dbService;
    }
    
    public async Task<User> LoginAsync(string username, string password)
    {
        var userRow = _dbService.ExecuteQuery(
            "SELECT * FROM Users WHERE Username = @p0;", username).Rows[0];
            
        var user = MapUserFromRow(userRow);
        
        // 验证密码
        if (VerifyPassword(password, user.PasswordHash))
        {
            user.LastLogin = DateTime.UtcNow.ToString("o");
            await UpdateUserAsync(user);
            return user;
        }
        
        return null;
    }
    
    private bool VerifyPassword(string inputPassword, string storedHash)
    {
        // 实际实现应使用安全的密码哈希验证
        // 这里简化处理
        return inputPassword == storedHash; 
    }
    
    public async Task<User> GetUserAsync(string userId)
    {
        var row = _dbService.ExecuteQuery(
            "SELECT * FROM Users WHERE Id = @p0;", userId).Rows[0];
        return MapUserFromRow(row);
    }
    
    private User MapUserFromRow(DataRow row)
    {
        return new User
        {
            Id = row["Id"].ToString(),
            Username = row["Username"].ToString(),
            PasswordHash = row["PasswordHash"].ToString(),
            FullName = row["FullName"].ToString(),
            Role = row["Role"].ToString(),
            LastLogin = row["LastLogin"]?.ToString()
        };
    }
    
    private async Task UpdateUserAsync(User user)
    {
        _dbService.ExecuteNonQuery(@"
            UPDATE Users SET 
                LastLogin = @p0 
            WHERE Id = @p1;",
            user.LastLogin, user.Id);
    }
}

// PermissionService.cs
public class PermissionService
{
    private readonly DatabaseService _dbService;
    
    public PermissionService(DatabaseService dbService)
    {
        _dbService = dbService;
    }
    
    public async Task<bool> HasPermissionAsync(string userId, string permission)
    {
        // 从数据库查询用户权限
        var hasPermission = await _dbService.ExecuteScalarAsync<bool>(
            "SELECT COUNT(*) > 0 FROM UserPermissions WHERE UserId = @p0 AND Permission = @p1;",
            userId, permission);
            
        return hasPermission;
    }
    
    public async Task<IEnumerable<string>> GetUserPermissionsAsync(string userId)
    {
        var permissions = await _dbService.ExecuteQueryAsync(
            "SELECT Permission FROM UserPermissions WHERE UserId = @p0;", userId);
            
        return permissions.Select(r => r["Permission"].ToString());
    }
}

3. 视图模型 (ViewModels/)

// MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
    private readonly IEventAggregator _eventAggregator;
    private readonly AuthService _authService;
    private readonly PermissionService _permissionService;
    
    private object _currentView;
    private User _currentUser;
    
    public object CurrentView
    {
        get => _currentView;
        set { _currentView = value; OnPropertyChanged(); }
    }
    
    public User CurrentUser
    {
        get => _currentUser;
        private set { _currentUser = value; OnPropertyChanged(); }
    }
    
    public ICommand LoginCommand { get; }
    public ICommand LogoutCommand { get; }
    
    public MainViewModel(
        IEventAggregator eventAggregator,
        AuthService authService,
        PermissionService permissionService)
    {
        _eventAggregator = eventAggregator;
        _authService = authService;
        _permissionService = permissionService;
        
        LoginCommand = new RelayCommand(Login);
        LogoutCommand = new RelayCommand(Logout, CanLogout);
    }
    
    private async void Login()
    {
        // 实际实现应显示登录对话框
        var loginView = new LoginView();
        if (loginView.ShowDialog() == true)
        {
            var user = await _authService.LoginAsync(
                loginView.Username, 
                loginView.Password);
                
            if (user != null)
            {
                CurrentUser = user;
                CurrentView = new ShellViewModel(_eventAggregator, user).View;
                _eventAggregator.Publish(new UserLoggedInEvent(user));
            }
        }
    }
    
    private void Logout()
    {
        CurrentUser = null;
        CurrentView = new LoginView();
        _eventAggregator.Publish(new UserLoggedOutEvent());
    }
    
    private bool CanLogout() => CurrentUser != null;
    
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// CommunicationViewModel.cs
public class CommunicationViewModel : INotifyPropertyChanged
{
    private readonly ComService _comService;
    private readonly EthernetService _ethernetService;
    private readonly PlcService _plcService;
    private readonly WebApiService _webApiService;
    
    private SerialDevice _selectedSerialDevice;
    private PlcDevice _selectedPlcDevice;
    
    public ObservableCollection<SerialDevice> SerialDevices { get; } = new();
    public ObservableCollection<PlcDevice> PlcDevices { get; } = new();
    
    public SerialDevice SelectedSerialDevice
    {
        get => _selectedSerialDevice;
        set 
        { 
            _selectedSerialDevice = value; 
            OnPropertyChanged();
            OpenSerialPort();
        }
    }
    
    public PlcDevice SelectedPlcDevice
    {
        get => _selectedPlcDevice;
        set 
        { 
            _selectedPlcDevice = value; 
            OnPropertyChanged();
            ConnectPlc();
        }
    }
    
    public ICommand RefreshDevicesCommand { get; }
    public ICommand SendSerialCommand { get; }
    public ICommand ReadPlcCommand { get; }
    public ICommand WritePlcCommand { get; }
    
    public CommunicationViewModel(
        ComService comService,
        EthernetService ethernetService,
        PlcService plcService,
        WebApiService webApiService)
    {
        _comService = comService;
        _ethernetService = ethernetService;
        _plcService = plcService;
        _webApiService = webApiService;
        
        RefreshDevicesCommand = new RelayCommand(RefreshDevices);
        SendSerialCommand = new RelayCommand(SendSerialData, CanSendSerial);
        ReadPlcCommand = new RelayCommand(ReadPlcData, CanReadPlc);
        WritePlcCommand = new RelayCommand(WritePlcData, CanWritePlc);
        
        LoadDevices();
    }
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_shenbing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值