diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..b394797 --- /dev/null +++ b/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..fe653b1 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection; +using SunlightAggregationTerminal.Models; + +namespace SunlightAggregationTerminal +{ + public partial class App : Application + { + public static DataModel GlobalData { get; set;} = new DataModel(); + public App() + { + InitializeComponent(); + } + + protected override Window CreateWindow(IActivationState? activationState) + { + return new Window(new AppShell()); + } + } +} \ No newline at end of file diff --git a/AppShell.xaml b/AppShell.xaml new file mode 100644 index 0000000..051864f --- /dev/null +++ b/AppShell.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + diff --git a/AppShell.xaml.cs b/AppShell.xaml.cs new file mode 100644 index 0000000..c8445dc --- /dev/null +++ b/AppShell.xaml.cs @@ -0,0 +1,35 @@ +using SunlightAggregationTerminal.Class; +using SunlightAggregationTerminal.Models; +using SunlightAggregationTerminal.View; + +namespace SunlightAggregationTerminal +{ + public partial class AppShell : Shell + { + public AppShell() + { + InitializeComponent(); + this.BindingContext = new AppModels(); + } + + + private void Shell_Loaded(object sender, EventArgs e) + { + //打开登录 + if (!App.GlobalData.LogNo) + { + var logpage = new View.LogPage(); + Navigation.PushModalAsync(new NavigationPage(logpage)); + } + else + {//发送登录请求 + /* var dat = TcpServer.Query(""); + if (dat != null) + {//拒绝登陆时打开登录页面 + var logpage = new View.LogPage(); + Navigation.PushModalAsync(logpage); + }*/ + } + } + } +} diff --git a/Class/SQLiteConfig.cs b/Class/SQLiteConfig.cs new file mode 100644 index 0000000..6b4b8b9 --- /dev/null +++ b/Class/SQLiteConfig.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SunlightAggregationTerminal.Class +{ + + public class SQLiteConfig() + { + + public static SqliteHelper SQLitedata = null!; //定义数据库 + + public static SqliteHelper? Config(string dat) + { + try + { + SQLitedata = new SqliteHelper(dat); //数据库连接路径(获取各平台的应用数据目录) + SQLitedata.Open(); //打开数据库 + if (SQLitedata.TableExists("Users")) + { + CheckAndRepairTableSchema(); + } + else + { + CreateFreshUserTable(); + } + + if (SQLitedata.TableExists("Notification")) + { + CheckAndRepairTableSchemaNotification(); + } + else + { + CreateFreshUserTableNotification(); + } + + if (SQLitedata.TableExists("Server")) + { + CheckAndRepairTableSchemaSERVER(); + } + else + { + CreateFreshUserTableSERVER(); + } + + SQLitedata.Close(); + + return SQLitedata; + } + catch (Exception) + { + return null; + } + } + + // --- 逻辑分支 表存在,检查字段完整性 --- + private static void CheckAndRepairTableSchema() + { + // 获取现有字段列表 + // 使用 PRAGMA table_info 获取结构 + string pragmaSql = "PRAGMA table_info('Users');"; + + // 复用现有的 ExecuteReader 方法 + // ExecuteReader 返回 null 代表出错,否则返回 DataReader + using (var reader = SQLitedata.ExecuteReader(pragmaSql, null)) + { + if (reader == null) return; + + var existingColumns = new List(); + while (reader.Read()) + { + // PRAGMA table_info 返回结果的第2列(Index=1)是列名 + string colName = reader.GetString(1); + existingColumns.Add(colName); + } + reader.Close(); // 记得关闭 Reader + + // 检查并添加缺失字段 (直接拼接 SQL) + // 检查 User + if (!existingColumns.Contains("User")) + { + string sql = "ALTER TABLE Users ADD COLUMN User TEXT NOT NULL;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 UserId + if (!existingColumns.Contains("UserId")) + { + string sql = "ALTER TABLE Users ADD COLUMN UserId TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 UserName + if (!existingColumns.Contains("UserName")) + { + string sql = "ALTER TABLE Users ADD COLUMN UserName TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 UserPassword + if (!existingColumns.Contains("UserPassword")) + { + string sql = "ALTER TABLE Users ADD COLUMN UserPassword TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 Enterprise + if (!existingColumns.Contains("Enterprise")) + { + string sql = "ALTER TABLE Users ADD COLUMN Enterprise TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 Department + if (!existingColumns.Contains("Department")) + { + string sql = "ALTER TABLE Users ADD COLUMN Department TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 ServerID + if (!existingColumns.Contains("ServerID")) + { + string sql = "ALTER TABLE Users ADD COLUMN ServerID TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 Group + if (!existingColumns.Contains("Groups")) + { + string sql = "ALTER TABLE Users ADD COLUMN Groups TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 ProxyID + if (!existingColumns.Contains("ProxyID")) + { + string sql = "ALTER TABLE Users ADD COLUMN ProxyID TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 LogNo + if (!existingColumns.Contains("LogNo")) + { + string sql = "ALTER TABLE Users ADD COLUMN LogNo BOOL DEFAULT (0);"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 LocalAreaNetworkMode + if (!existingColumns.Contains("LocalAreaNetworkMode")) + { + string sql = "ALTER TABLE Users ADD COLUMN LocalAreaNetworkMode BOOL DEFAULT (0);"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 LocalAreaNetworkMode + if (!existingColumns.Contains("DarkMode")) + { + string sql = "ALTER TABLE Users ADD COLUMN DarkMode BOOL DEFAULT (0);"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 LocalAreaNetworkMode + if (!existingColumns.Contains("MessageNotificationMode")) + { + string sql = "ALTER TABLE Users ADD COLUMN MessageNotificationMode BOOL DEFAULT (0);"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 GUID + if (!existingColumns.Contains("GUID")) + { + string sql = "ALTER TABLE Users ADD COLUMN GUID TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + string sqlupdata = "UPDATE Users SET GUID = '"+ Models.AppModels.GetAppUniqueId() + "';"; + SQLitedata.ExecuteNonQuery(sqlupdata, null); + } + } + } + private static void CheckAndRepairTableSchemaNotification() + { + // 获取现有字段列表 + // 使用 PRAGMA table_info 获取结构 + string pragmaSql = "PRAGMA table_info('Notification');"; + + // 复用现有的 ExecuteReader 方法 + // ExecuteReader 返回 null 代表出错,否则返回 DataReader + using (var reader = SQLitedata.ExecuteReader(pragmaSql, null)) + { + if (reader == null) return; + + var existingColumns = new List(); + while (reader.Read()) + { + // PRAGMA table_info 返回结果的第2列(Index=1)是列名 + string colName = reader.GetString(1); + existingColumns.Add(colName); + } + reader.Close(); // 记得关闭 Reader + + // 检查并添加缺失字段 (直接拼接 SQL) + // 检查 Title + if (!existingColumns.Contains("Title")) + { + string sql = "ALTER TABLE Notification ADD COLUMN Title TEXT NULL;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 Content + if (!existingColumns.Contains("Content")) + { + string sql = "ALTER TABLE Notification ADD COLUMN Content TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 Time + if (!existingColumns.Contains("Time")) + { + string sql = "ALTER TABLE Notification ADD COLUMN Time TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 Type + if (!existingColumns.Contains("Type")) + { + string sql = "ALTER TABLE Notification ADD COLUMN Type INT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + } + } + private static void CheckAndRepairTableSchemaSERVER() + { + // 获取现有字段列表 + // 使用 PRAGMA table_info 获取结构 + string pragmaSql = "PRAGMA table_info('Server');"; + + // 复用现有的 ExecuteReader 方法 + // ExecuteReader 返回 null 代表出错,否则返回 DataReader + using (var reader = SQLitedata.ExecuteReader(pragmaSql, null)) + { + if (reader == null) return; + + var existingColumns = new List(); + while (reader.Read()) + { + // PRAGMA table_info 返回结果的第2列(Index=1)是列名 + string colName = reader.GetString(1); + existingColumns.Add(colName); + } + reader.Close(); // 记得关闭 Reader + + // 检查并添加缺失字段 (直接拼接 SQL) + // 检查 Enterprise + if (!existingColumns.Contains("Enterprise")) + { + string sql = "ALTER TABLE Server ADD COLUMN Enterprise TEXT NULL;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + // 检查 ServerID + if (!existingColumns.Contains("ServerID")) + { + string sql = "ALTER TABLE Server ADD COLUMN ServerID TEXT;"; + SQLitedata.ExecuteNonQuery(sql, null); + } + } + } + // --- 逻辑分支 表不存在,直接创建 --- + private static void CreateFreshUserTable() + { + // 建表语句 + string createSql = @" + CREATE TABLE Users ( + User TEXT NOT NULL, + UserId TEXT, + UserName TEXT, + UserPassword TEXT, + Enterprise TEXT, + Department TEXT, + ServerID TEXT, + Groups TEXT, + ProxyID TEXT, + LogNo BOOL DEFAULT (0), + LocalAreaNetworkMode BOOL DEFAULT (0), + DarkMode BOOL DEFAULT (0), + MessageNotificationMode BOOL DEFAULT (0), + GUID TEXT + );"; + + // 复用现有的 ExecuteNonQuery 方法 + SQLitedata.ExecuteNonQuery(createSql, null); + + string Sql = @" + INSERT INTO Users (User,UserId ,UserName ,UserPassword ,Enterprise ,Department ,ServerID ,Groups ,ProxyID ,LogNo ,LocalAreaNetworkMode ,DarkMode ,MessageNotificationMode ,GUID + )VALUES('SUNLIGHT','SUNLIGHT','SUNLIGHT','SUNLIGHT','SUNLIGHT','ENGINEER','TEST','ENGINEER','',0,0,0,0,'"+ Models.AppModels.GetAppUniqueId() + "');"; + + // 复用现有的 ExecuteNonQuery 方法 + SQLitedata.ExecuteNonQuery(Sql, null); + } + private static void CreateFreshUserTableNotification() + { + // 建表语句 + string createSql = @" + CREATE TABLE Notification ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Title TEXT, + Content TEXT, + Time TEXT, + Type INT + );"; + + // 复用现有的 ExecuteNonQuery 方法 + SQLitedata.ExecuteNonQuery(createSql, null); + } + private static void CreateFreshUserTableSERVER() + { + // 建表语句 + string createSql = @" + CREATE TABLE Server ( + Enterprise TEXT, + ServerID TEXT + );"; + + // 复用现有的 ExecuteNonQuery 方法 + SQLitedata.ExecuteNonQuery(createSql, null); + } + } + +} diff --git a/Class/SqliteHelper.cs b/Class/SqliteHelper.cs new file mode 100644 index 0000000..8ccb658 --- /dev/null +++ b/Class/SqliteHelper.cs @@ -0,0 +1,504 @@ +using Microsoft.Data.Sqlite; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + + +namespace SunlightAggregationTerminal.Class +{ + public class ClsLock : IDisposable + { + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + + public IDisposable Read() + { + _lock.EnterReadLock(); + return new DisposableAction(() => _lock.ExitReadLock()); + } + + public void Dispose() => _lock?.Dispose(); + + private class DisposableAction : IDisposable + { + private readonly Action _action; + public DisposableAction(Action action) => _action = action; + public void Dispose() => _action?.Invoke(); + } + } + + public class SqliteHelper + { + #region 字段 + private SqliteTransaction? dbTrans = null!; + private static readonly Dictionary RWL = new Dictionary(); + private readonly string mDataFile; + private readonly string mPassWord = ""; + private readonly string lockName = ""; + private SqliteConnection mConn = null!; + #endregion + + #region 构造函数 + public SqliteHelper(string dataFile) + { + this.mDataFile = dataFile ?? throw new ArgumentNullException(nameof(dataFile)); + this.mDataFile = dataFile; // 注意:原代码有两次赋值,保留逻辑 + if (!RWL.ContainsKey(dataFile)) + { + lockName = dataFile; + RWL.Add(dataFile, new ClsLock()); + } + } + + public SqliteHelper(string dataFile, string PassWord) + { + this.mDataFile = dataFile ?? throw new ArgumentNullException(nameof(dataFile)); + this.mPassWord = PassWord ?? throw new ArgumentNullException(nameof(PassWord)); + this.mDataFile = dataFile; + if (!RWL.ContainsKey(dataFile)) + { + lockName = dataFile; + RWL.Add(dataFile, new ClsLock()); + } + } + #endregion + + #region 打开/关闭 数据库 + // 注意:Microsoft.Data.Sqlite.Core 不支持连接字符串构建器,需手动拼接 + // 加密支持通常需要 Microsoft.Data.Sqlite.Core + SQLitePCLRaw.bundle_green (且加密功能可能受限) + + public void Open() + { + // 构建连接字符串 + var connectionString = $"Data Source={mDataFile}"; + if (!string.IsNullOrWhiteSpace(mPassWord)) + { + connectionString += $";Password={mPassWord}"; // 注意:原生 Microsoft.Data.Sqlite 不支持加密 + // 如果需要加密,通常需要使用 SQLitePCLRaw 的特定提供程序或第三方库 + } + + mConn = new SqliteConnection(connectionString); + + // 如果文件不存在,Open() 会自动创建数据库文件 + // 但表结构需要你自己执行 CREATE TABLE + mConn.Open(); + + Console.WriteLine("The database was opened successfully"); + } + + public void Close() + { + if (this.mConn != null) + { + try + { + this.mConn.Close(); + // 注意:不要在这里移除 RWL,因为这是静态的,可能影响其他实例 + // if (RWL.ContainsKey(LockName)) { RWL.Remove(LockName); } + } + catch + { + Console.WriteLine("Shutdown failed"); + } + } + Console.WriteLine("The database was shut down successfully"); + } + #endregion + + #region 事务 + public void BeginTrain() + { + EnsureConnection(); + dbTrans = mConn.BeginTransaction(); + } + + public void DBCommit() + { + try + { + dbTrans?.Commit(); + dbTrans?.Dispose(); + dbTrans = null; // 提交后置空 + } + catch (Exception) + { + dbTrans?.Rollback(); + dbTrans?.Dispose(); + dbTrans = null; + } + } + #endregion + + #region 工具 + // OpenConnection 逻辑已合并到 Open() 方法中,因为 SqliteConnection 构造函数只接受字符串 + + public SqliteConnection Connection + { + get { return mConn; } + private set { mConn = value ?? throw new ArgumentNullException(); } + } + + protected void EnsureConnection() + { + if (this.mConn == null) + { + throw new Exception("SQLiteManager.Connection=null"); + } + if (mConn.State != ConnectionState.Open) + { + mConn.Open(); + } + } + + public string GetDataFile() => this.mDataFile; + + public bool TableExists(string table) + { + if (string.IsNullOrEmpty(table)) return false; + EnsureConnection(); + + // 注意:参数化查询在表名/列名上无效,这里使用字符串拼接(需确保table变量安全) + // 或者使用 PRAGMA table_info(table_name) + var sql = $"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='{table}'"; + + using (var cmd = new SqliteCommand(sql, Connection)) + { + var result = cmd.ExecuteScalar(); + return Convert.ToInt32(result) > 0; + } + } + + public bool Vacuum() + { + try + { + using (var Command = new SqliteCommand("VACUUM", Connection)) + { + Command.ExecuteNonQuery(); + } + return true; + } + catch (Exception) // SqliteException + { + return false; + } + } + + /// + /// 将 IDataReader 转换为 DataSet + /// + /// 已经执行了查询的 DataReader + /// 包含数据的 DataSet + public DataSet ReaderToDataSet(IDataReader reader) + { + DataSet ds = new DataSet(); + + do + { + DataTable schemaTable = reader.GetSchemaTable()!; + DataTable dataTable = new DataTable(); + + if (schemaTable != null) + { + // 创建列 + foreach (DataRow schemaRow in schemaTable.Rows) + { + string colName = schemaRow["ColumnName"].ToString()!; + Type dataType = (Type)schemaRow["DataType"]; + + // 防止列名重复 + string uniqueColName = colName; + int i = 1; + while (dataTable.Columns.Contains(uniqueColName)) + { + uniqueColName = colName + i++; + } + + DataColumn column = new DataColumn(uniqueColName, dataType); + dataTable.Columns.Add(column); + } + + // 读取行 + while (reader.Read()) + { + object[] values = new object[dataTable.Columns.Count]; + for (int i = 0; i < dataTable.Columns.Count; i++) + { + values[i] = reader.IsDBNull(i) ? DBNull.Value : reader.GetValue(i); + } + dataTable.Rows.Add(values); + } + } + else + { + // 如果没有 Schema (例如 "SELECT 1" 这种标量查询) + // 需要手动创建列 + for (int i = 0; i < reader.FieldCount; i++) + { + dataTable.Columns.Add("Column" + i); + } + while (reader.Read()) + { + object[] values = new object[reader.FieldCount]; + reader.GetValues(values); + dataTable.Rows.Add(values); + } + } + + ds.Tables.Add(dataTable); + } while (reader.NextResult()); // 处理多个结果集 + + return ds; + } + + #endregion + + + #region 执行SQL (Reader & Scalar) + + public SqliteDataReader? ExecuteReader(string sql, SqliteParameter[]? paramArr) + { + if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql)); + EnsureConnection(); + + // 注意:using 语句会立即释放 Reader,导致无法读取 + // 这里的 RWL 锁在 using 外部处理,或者调用方自己处理锁 + // 这里简化逻辑,直接返回 Reader,调用方需自行管理连接状态 + var cmd = new SqliteCommand(sql, Connection); + + if (paramArr != null) + { + cmd.Parameters.AddRange(paramArr); + } + + // CommandBehavior.CloseConnection 确保 Reader 关闭时连接也关闭 + // 但这里我们管理连接,所以不加 + try + { + // 注意:不要在这里 Dispose Command,否则 Reader 会失效 + // 返回 Reader,由调用方在读取完毕后关闭 + return cmd.ExecuteReader(); + } + catch(Exception ex) + { + Console.WriteLine("[SQLITE_ExecuteReader:err]" + ex.ToString()); + cmd?.Dispose(); + return null; + } + } + + public DataSet? ExecuteDataSet(string sql, SqliteParameter[]? paramArr) + { + var dat = ExecuteReader(sql, paramArr); + if (dat == null) + { + return null; + } + return ReaderToDataSet(dat); + } + + // ExecuteScalar + public object? ExecuteScalar(string sql, SqliteParameter[]? paramArr) + { + if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql)); + EnsureConnection(); + + using (RWL[lockName].Read()) + { + using (var cmd = new SqliteCommand(sql, Connection)) + { + if (paramArr != null) + { + cmd.Parameters.AddRange(paramArr); + } + try + { + return cmd.ExecuteScalar(); + } + catch (Exception ex) + { + Console.WriteLine("[SQLITE_ExecuteScalar:err]" + ex.ToString()); + return null; + } + } + } + } + + // QueryOne (返回字典或对象) + public Dictionary? QueryOne(string table, string conditionCol, object conditionVal) + { + if (string.IsNullOrEmpty(table)) return null; + EnsureConnection(); + + string sql = $"SELECT * FROM [{table}]"; // 使用 [] 防止关键字冲突 + var parameters = new List(); + + if (!string.IsNullOrEmpty(conditionCol)) + { + sql += $" WHERE [{conditionCol}] = @{conditionCol}"; + parameters.Add(new SqliteParameter($"@{conditionCol}", conditionVal)); + } + + using (var cmd = new SqliteCommand(sql, Connection)) + { + cmd.Parameters.AddRange(parameters.ToArray()); + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + var dict = new Dictionary(); + for (int i = 0; i < reader.FieldCount; i++) + { + var value = reader.GetValue(i); + if (value == DBNull.Value) value = null; + dict[reader.GetName(i)] = value; + } + return dict; + } + } + } + return null; + } + #endregion + + #region 增 删 改 + // 注意:参数前缀统一为 @ + + public int InsertData(string table, Dictionary entity) + { + if (string.IsNullOrEmpty(table)) throw new ArgumentNullException(nameof(table)); + EnsureConnection(); + + string sql = BuildInsert(table, entity); + var parameters = BuildParamArray(entity); + return ExecuteNonQuery(sql, parameters); + } + + public int Update(string table, Dictionary entity, string where, SqliteParameter[]? whereParams) + { + if (string.IsNullOrEmpty(table)) throw new ArgumentNullException(nameof(table)); + EnsureConnection(); + + string sql = BuildUpdate(table, entity); + var parameters = BuildParamArray(entity); + + if (!string.IsNullOrEmpty(where)) + { + sql += " WHERE " + where; + if (whereParams != null) + { + var combined = new SqliteParameter[parameters.Length + whereParams.Length]; + parameters.CopyTo(combined, 0); + whereParams.CopyTo(combined, parameters.Length); + parameters = combined; + } + } + + return ExecuteNonQuery(sql, parameters); + } + + public int Delete(string table, string where, SqliteParameter[]? whereParams) + { + if (string.IsNullOrEmpty(table)) return 0; + EnsureConnection(); + + string sql = $"DELETE FROM [{table}]"; + if (!string.IsNullOrEmpty(where)) + { + sql += " WHERE " + where; + } + + return ExecuteNonQuery(sql, whereParams); + } + + public int ExecuteNonQuery(string sql, SqliteParameter[]? paramArr) + { + if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql)); + EnsureConnection(); + + using (RWL[lockName].Read()) // 注意:写操作通常建议用 Write 锁 + { + using (var cmd = new SqliteCommand(sql, Connection)) + { + if (paramArr != null) + { + cmd.Parameters.AddRange(paramArr); + } + try + { + return cmd.ExecuteNonQuery(); + } + catch (Exception ex) + { + Console.WriteLine("[SQLITE_ExecuteScalar:err]" + ex.ToString()); + return 0; + } + } + } + } + + // 构建 SQL 和 参数的方法保持不变,仅泛型类型变更 + private SqliteParameter[] BuildParamArray(Dictionary entity) + { + var list = new List(); + foreach (string key in entity.Keys) + { + // 注意:参数名必须包含 @ 前缀 + list.Add(new SqliteParameter($"@{key}", entity[key])); + } + return list.ToArray(); + } + + private string BuildInsert(string table, Dictionary entity) + { + var buf = new StringBuilder(); + buf.Append("INSERT INTO ").Append(table).Append(" ("); + var cols = new List(); + var vals = new List(); + foreach (string key in entity.Keys) + { + cols.Add(key); + vals.Add($"@{key}"); + } + buf.Append(string.Join(",", cols)).Append(") VALUES ("); + buf.Append(string.Join(",", vals)).Append(")"); + return buf.ToString(); + } + + private string BuildUpdate(string table, Dictionary entity) + { + var buf = new StringBuilder(); + buf.Append("UPDATE ").Append(table).Append(" SET "); + var sets = new List(); + foreach (string key in entity.Keys) + { + sets.Add($"{key} = @{key}"); + } + buf.Append(string.Join(",", sets)); + return buf.ToString(); + } + + // 辅助方法:DataRow 转 Dictionary (由于没有 DataTable,通常直接从 Reader 转) + // 这里保留原方法签名,但实际使用中可能需要调整 + public Dictionary DataTableToDictionary(DataTable dataTable) + { + var result = new Dictionary(); + if (dataTable.Rows.Count > 0) + { + var row = dataTable.Rows[0]; + foreach (DataColumn column in dataTable.Columns) + { + var value = row[column]; + if (value == DBNull.Value) value = null; + result[column.ColumnName] = value; + } + } + return result; + } + #endregion + } +} diff --git a/Class/TcpServer.cs b/Class/TcpServer.cs new file mode 100644 index 0000000..4f4dd86 --- /dev/null +++ b/Class/TcpServer.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace SunlightAggregationTerminal.Class +{ + public class TcpServer + { + public static string ServerIP = ""; + public static string ServerName = ""; + public static string ServerPassword = ""; + public static string ServerTerminal = ""; + + public static TcpClient tcpClient = new TcpClient(); + + public static void Configuration(string ip, string Name, string Password, string Terminal) + { + ServerIP = ip; + ServerName = Name; + ServerPassword = Password; + ServerTerminal = Terminal; + } + public static async void Query(string Dat) + { + + //发送字符串数据 + await tcpClient.SendAsync("hello"); + } + + + private static async Task CreateCustomService() + { + tcpClient.Config.SetRemoteIPHost(ServerIP); + + tcpClient.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到服务器,此时已经创建socket,但是还未建立tcp + tcpClient.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到服务器 + tcpClient.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从服务器断开连接。此处仅主动断开才有效。 + tcpClient.Closed = (client, e) => { return EasyTask.CompletedTask; };//从服务器断开连接,当连接不成功时不会触发。 + #region Tcp客户端使用Received异步委托接收数据 + tcpClient.Received = (client, e) => + { + //从服务器收到信息。但是一般byteBlock和requestInfo会根据适配器呈现不同的值。 + var mes = e.Memory.Span.ToString(Encoding.UTF8); + tcpClient.Logger.Info($"客户端接收到信息:{mes}"); + return EasyTask.CompletedTask; + }; + #endregion + + await tcpClient.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + } + } +} diff --git a/Images/adduserfilled.svg b/Images/adduserfilled.svg new file mode 100644 index 0000000..7fdd12e --- /dev/null +++ b/Images/adduserfilled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Images/infofeed.svg b/Images/infofeed.svg new file mode 100644 index 0000000..18c65f8 --- /dev/null +++ b/Images/infofeed.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Images/ixnotifications.svg b/Images/ixnotifications.svg new file mode 100644 index 0000000..ad74185 --- /dev/null +++ b/Images/ixnotifications.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Images/lucidescanline.png b/Images/lucidescanline.png new file mode 100644 index 0000000..ebc6c05 Binary files /dev/null and b/Images/lucidescanline.png differ diff --git a/Images/search.svg b/Images/search.svg new file mode 100644 index 0000000..7f99d79 --- /dev/null +++ b/Images/search.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/InfoPage.xaml b/InfoPage.xaml new file mode 100644 index 0000000..00e33b1 --- /dev/null +++ b/InfoPage.xaml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/InfoPage.xaml.cs b/InfoPage.xaml.cs new file mode 100644 index 0000000..c74c0ce --- /dev/null +++ b/InfoPage.xaml.cs @@ -0,0 +1,9 @@ +namespace SunlightAggregationTerminal; + +public partial class InfoPage : ContentPage +{ + public InfoPage() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/MauiProgram.cs b/MauiProgram.cs new file mode 100644 index 0000000..0d63c16 --- /dev/null +++ b/MauiProgram.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Maui.Handlers; +using SunlightAggregationTerminal.Models; +using ZXing.Net.Maui.Controls; + +namespace SunlightAggregationTerminal +{ + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .UseBarcodeReader() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }) + .ConfigureMauiHandlers(handlers => + { + // 这里的 "NoUnderline" 是自定义映射名称,可以随意命名 + EntryHandler.Mapper.AppendToMapping("NoUnderline", (handler, view) => + { + #if ANDROID + // 将背景 tint 设为透明 + // 这会移除 Android 默认的下划线颜色 + handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Android.Graphics.Color.Transparent); + + // 如果想彻底移除背景绘制(不仅仅是颜色透明) + // handler.PlatformView.Background = null; + #endif + }); + }); + // 将 HttpClient 注册为单例服务 + builder.Services.AddSingleton(); + + builder.Services.AddSingleton(); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } + } +} diff --git a/Models/AppModels.cs b/Models/AppModels.cs new file mode 100644 index 0000000..c80c9a9 --- /dev/null +++ b/Models/AppModels.cs @@ -0,0 +1,89 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using SunlightAggregationTerminal.Class; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; + +namespace SunlightAggregationTerminal.Models +{ + public class AppModels : ObservableObject + { + + public static SqliteHelper sqliteHelper = SQLiteConfig.Config(Path.Combine(FileSystem.AppDataDirectory, "MyAppData.db"))!; //数据库连接路径(获取各平台的应用数据目录) + + public AppModels() + { + if(sqliteHelper != null) + {//获取配置信息 + sqliteHelper.Open(); + var userdata= sqliteHelper.ExecuteDataSet("select * from Users Where LogNo = 1", null); + sqliteHelper.Close(); + if (userdata != null) + { + var dat = userdata.Tables[0].AsEnumerable().FirstOrDefault(); + if (dat != null) + { + App.GlobalData.UserName = dat.Field("UserName") ?? ""; + App.GlobalData.UserId = dat.Field("UserId") ?? ""; + App.GlobalData.User = dat.Field("User") ?? ""; + App.GlobalData.UserPassword = dat.Field("UserPassword") ?? ""; + App.GlobalData.Enterprise = dat.Field("Enterprise") ?? ""; + App.GlobalData.Department = dat.Field("Department") ?? ""; + App.GlobalData.Groups = dat.Field("Groups") ?? ""; + App.GlobalData.ServerID = dat.Field("ServerID") ?? ""; + App.GlobalData.ProxyID = dat.Field("ProxyID") ?? ""; + App.GlobalData.LogNo = Convert.ToBoolean(dat.Field("LogNo")); + App.GlobalData.LocalAreaNetworkMode = Convert.ToBoolean(dat.Field("LocalAreaNetworkMode")); + App.GlobalData.DarkMode = Convert.ToBoolean(dat.Field("DarkMode")); + App.GlobalData.MessageNotificationMode = Convert.ToBoolean(dat.Field("MessageNotificationMode")); + + TcpServer.Configuration(App.GlobalData.ServerID, App.GlobalData.User, App.GlobalData.UserPassword, dat.Field("GUID") ?? "Not"); + } + } + } + } + + public static void Updata(string sql) + { + sqliteHelper.Open(); + sqliteHelper.ExecuteNonQuery(sql, null); + sqliteHelper.Close(); + } + + public static void INSERT(string sql) + { + sqliteHelper.Open(); + sqliteHelper.ExecuteNonQuery(sql, null); + sqliteHelper.Close(); + } + + public static DataSet? Select(string sql) + { + sqliteHelper.Open(); + var userdata = sqliteHelper.ExecuteDataSet(sql, null); + sqliteHelper.Close(); + + return userdata; + } + + public static string GetAppUniqueId() + { + const string key = "sunlight_app_unique_id"; + + // 尝试从安全存储中读取 + string? id = SecureStorage.GetAsync(key).Result; + + if (string.IsNullOrEmpty(id)) + { + // 如果不存在,生成一个新的 GUID + id = Guid.NewGuid().ToString(); + // 保存到安全存储 + SecureStorage.SetAsync(key, id).Wait(); + } + + return id; + } + + } +} diff --git a/Models/DataModel.cs b/Models/DataModel.cs new file mode 100644 index 0000000..7793f4a --- /dev/null +++ b/Models/DataModel.cs @@ -0,0 +1,40 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SunlightAggregationTerminal.Models +{ +#pragma warning disable CA1416 + public partial class DataModel : ObservableObject + { + [ObservableProperty] + public partial string User { get; set; } + [ObservableProperty] + public partial string UserId { get; set; } + [ObservableProperty] + public partial string UserName { get; set; } + [ObservableProperty] + public partial string UserPassword { get; set; } + [ObservableProperty] + public partial string Enterprise { get; set; } + [ObservableProperty] + public partial string Department { get; set; } + [ObservableProperty] + public partial string ServerID { get; set; } + [ObservableProperty] + public partial string Groups { get; set; } + [ObservableProperty] + public partial string ProxyID { get; set; } + [ObservableProperty] + public partial bool LogNo { get; set; } + public bool IsNotLogNo => !LogNo; + [ObservableProperty] + public partial bool LocalAreaNetworkMode { get; set; } + [ObservableProperty] + public partial bool DarkMode { get; set; } + [ObservableProperty] + public partial bool MessageNotificationMode { get; set; } + + } +} diff --git a/Models/DataSource.cs b/Models/DataSource.cs new file mode 100644 index 0000000..57aa2b2 --- /dev/null +++ b/Models/DataSource.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Linq; + +namespace SunlightAggregationTerminal.Models +{ + // 定义枚举 + public enum MsgType + { + Notice, // 通知 + Message, // 信息 + System // 系统 + } + + // 定义显示用的模型 + public class NotificationItem + { + public Int64 Id { get; set; } + public string? Title { get; set; } + public string? Content { get; set; } + public DateTime Time { get; set; } + public MsgType Type { get; set; } + } + + public static class DataService + { + public static DataSet? data = AppModels.Select("select * from Notification Where Time > '" + DateTime.Now.AddDays(-30) + "'"); + + // 直接返回 List + public static List _NotificationData = new List(); + public static List GetAllItems() + { + _NotificationData.Clear(); + + if (data != null) + { + foreach (DataRow dr in data.Tables[0].Rows) + { + _NotificationData.Add(new NotificationItem + { + Id = dr.Field("Id"), + Title = dr.Field("Title") , + Content = dr.Field("Content"), + Time = DateTime.Parse(dr.Field("Time")!), + Type = (MsgType)dr.Field("Type") + }); + } + } + + // 倒序返回 + return _NotificationData.OrderByDescending(x => x.Time).ToList(); + } + + + } +} diff --git a/NotificationPage.xaml b/NotificationPage.xaml new file mode 100644 index 0000000..4632886 --- /dev/null +++ b/NotificationPage.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NotificationPage.xaml.cs b/NotificationPage.xaml.cs new file mode 100644 index 0000000..d8461e1 --- /dev/null +++ b/NotificationPage.xaml.cs @@ -0,0 +1,93 @@ +using SunlightAggregationTerminal.Models; +using System.Data; +using System.Linq; + +namespace SunlightAggregationTerminal; + +public partial class NotificationPage : ContentPage +{ + public NotificationItem NoticeItem { get; set; } = new NotificationItem(); + public NotificationItem MessageItem { get; set; } = new NotificationItem(); + public NotificationItem SystemItem { get; set; } = new NotificationItem(); + + public NotificationPage() + { + InitializeComponent(); + this.BindingContext = this; + } + + private void ContentPage_Loaded(object sender, EventArgs e) + { + LoadSummaryData(); + } + + private void LoadSummaryData() + { + var allItems = DataService.GetAllItems(); + + // 筛选出 Type 为 Notice 的数据,并取第一条 + // 对应 DataTable 的 Select("Type = ...").First() + var noticeItem = allItems.FirstOrDefault(x => x.Type == MsgType.Notice); + + // 读取数据 (注意判空,防止找不到数据时报错) + if (noticeItem != null) + { + NoticeItemTitle.Text = noticeItem.Title; + NoticeItemContent.Text = noticeItem.Content; + } + else + { + // 处理没有找到数据的情况 + NoticeItemTitle.Text = "暂无通知"; + NoticeItemContent.Text = "通知"; + } + + var messageItem = allItems.FirstOrDefault(x => x.Type == MsgType.Message); + + // 读取数据 (注意判空,防止找不到数据时报错) + if (messageItem != null) + { + MessageItemTitle.Text = messageItem.Title; + MessageItemContent.Text = messageItem.Content; + } + else + { + // 处理没有找到数据的情况 + MessageItemTitle.Text = "暂无通知"; + MessageItemContent.Text = "信息"; + } + var systemItem = allItems.FirstOrDefault(x => x.Type == MsgType.System); + + // 读取数据 (注意判空,防止找不到数据时报错) + if (systemItem != null) + { + SystemItemTitle.Text = systemItem.Title; + SystemItemContent.Text = systemItem.Content; + } + else + { + // 处理没有找到数据的情况 + SystemItemTitle.Text = "暂无通知"; + SystemItemContent.Text = "系统信息"; + } + + } + + // 点击跳转到独立页面 + private async void OnNoticeTapped(object sender, TappedEventArgs e) + { + await Navigation.PushAsync(new NotificationView.NoticePage()); + } + + private async void OnMessageTapped(object sender, TappedEventArgs e) + { + await Navigation.PushAsync(new NotificationView.MessagePage()); + } + + private async void OnSystemTapped(object sender, TappedEventArgs e) + { + await Navigation.PushAsync(new NotificationView.SystemPage()); + } + + +} \ No newline at end of file diff --git a/NotificationView/MessagePage.xaml b/NotificationView/MessagePage.xaml new file mode 100644 index 0000000..012eb39 --- /dev/null +++ b/NotificationView/MessagePage.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NotificationView/MessagePage.xaml.cs b/NotificationView/MessagePage.xaml.cs new file mode 100644 index 0000000..888dc8b --- /dev/null +++ b/NotificationView/MessagePage.xaml.cs @@ -0,0 +1,42 @@ +using Microsoft.Maui.Controls.Shapes; +using SunlightAggregationTerminal.Models; +using System.Collections.ObjectModel; + +namespace SunlightAggregationTerminal.NotificationView; + +public partial class MessagePage : ContentPage +{ + public ObservableCollection MessageItems { get; set; } = new(); + + public MessagePage() + { + InitializeComponent(); + this.BindingContext = this; + LoadData(); // 加载数据 + } + + private void LoadData() + { + // 假设这是你获取数据的地方 + var allData = DataService._NotificationData; + if (allData != null) + { + // 清空旧数据 + MessageItems.Clear(); + + // 按时间倒序,并只取最新的 N 条(例如 50 条) + var itemsToShow = allData.OrderByDescending(x => x.Time).Take(50).Where(x => x.Type == MsgType.Message); + + // 将数据添加到集合中,UI 会自动更新 + foreach (var item in itemsToShow) + { + MessageItems.Add(item); + } + } + } + + private void CardCollectionView_RemainingItemsThresholdReached(object sender, EventArgs e) + { + + } +} \ No newline at end of file diff --git a/NotificationView/NoticePage.xaml b/NotificationView/NoticePage.xaml new file mode 100644 index 0000000..5f8eafb --- /dev/null +++ b/NotificationView/NoticePage.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NotificationView/NoticePage.xaml.cs b/NotificationView/NoticePage.xaml.cs new file mode 100644 index 0000000..bc89357 --- /dev/null +++ b/NotificationView/NoticePage.xaml.cs @@ -0,0 +1,43 @@ +using Microsoft.Maui.Controls.Shapes; +using SunlightAggregationTerminal.Models; +using System.Collections.ObjectModel; +using System.Data; + +namespace SunlightAggregationTerminal.NotificationView; + +public partial class NoticePage : ContentPage +{ + public ObservableCollection NoticeItems { get; set; } = new(); + public NoticePage() + { + InitializeComponent(); + this.BindingContext = this; + LoadData(); // 加载数据 + } + + private void LoadData() + { + // 假设这是你获取数据的地方 + var allData = DataService._NotificationData; + if (allData != null) + { + // 清空旧数据 + NoticeItems.Clear(); + + // 按时间倒序,并只取最新的 N 条(例如 50 条) + var itemsToShow = allData.OrderByDescending(x => x.Time).Take(50).Where(x => x.Type == MsgType.Notice); + + // 将数据添加到集合中,UI 会自动更新 + foreach (var item in itemsToShow) + { + NoticeItems.Add(item); + } + } + } + + private void CardCollectionView_RemainingItemsThresholdReached(object sender, EventArgs e) + { + + } + +} \ No newline at end of file diff --git a/NotificationView/SystemPage.xaml b/NotificationView/SystemPage.xaml new file mode 100644 index 0000000..92851b2 --- /dev/null +++ b/NotificationView/SystemPage.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NotificationView/SystemPage.xaml.cs b/NotificationView/SystemPage.xaml.cs new file mode 100644 index 0000000..c2da81e --- /dev/null +++ b/NotificationView/SystemPage.xaml.cs @@ -0,0 +1,42 @@ +using Microsoft.Maui.Controls.Shapes; +using SunlightAggregationTerminal.Models; +using System.Collections.ObjectModel; + +namespace SunlightAggregationTerminal.NotificationView; + +public partial class SystemPage : ContentPage +{ + public ObservableCollection SystemItems { get; set; } = new(); + public SystemPage() + { + InitializeComponent(); + this.BindingContext = this; + LoadData(); // 加载数据 + } + + private void LoadData() + { + // 假设这是你获取数据的地方 + var allData = DataService._NotificationData; + if (allData != null) + { + // 清空旧数据 + SystemItems.Clear(); + + // 按时间倒序,并只取最新的 N 条(例如 50 条) + var itemsToShow = allData.OrderByDescending(x => x.Time).Take(50).Where(x => x.Type == MsgType.System); + + // 将数据添加到集合中,UI 会自动更新 + foreach (var item in itemsToShow) + { + SystemItems.Add(item); + } + } + } + + private void CardCollectionView_RemainingItemsThresholdReached(object sender, EventArgs e) + { + + } + +} \ No newline at end of file diff --git a/Platforms/Android/AndroidManifest.xml b/Platforms/Android/AndroidManifest.xml new file mode 100644 index 0000000..4a8bfa2 --- /dev/null +++ b/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Platforms/Android/MainActivity.cs b/Platforms/Android/MainActivity.cs new file mode 100644 index 0000000..4ff1e22 --- /dev/null +++ b/Platforms/Android/MainActivity.cs @@ -0,0 +1,11 @@ +using Android.App; +using Android.Content.PM; +using Android.OS; + +namespace SunlightAggregationTerminal +{ + [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] + public class MainActivity : MauiAppCompatActivity + { + } +} diff --git a/Platforms/Android/MainApplication.cs b/Platforms/Android/MainApplication.cs new file mode 100644 index 0000000..a56b269 --- /dev/null +++ b/Platforms/Android/MainApplication.cs @@ -0,0 +1,16 @@ +using Android.App; +using Android.Runtime; + +namespace SunlightAggregationTerminal +{ + [Application(UsesCleartextTraffic = true)] + public class MainApplication : MauiApplication + { + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/Platforms/Android/Resources/values/colors.xml b/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 0000000..c04d749 --- /dev/null +++ b/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/Platforms/MacCatalyst/AppDelegate.cs b/Platforms/MacCatalyst/AppDelegate.cs new file mode 100644 index 0000000..40cc50a --- /dev/null +++ b/Platforms/MacCatalyst/AppDelegate.cs @@ -0,0 +1,10 @@ +using Foundation; + +namespace SunlightAggregationTerminal +{ + [Register("AppDelegate")] + public class AppDelegate : MauiUIApplicationDelegate + { + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/Platforms/MacCatalyst/Entitlements.plist b/Platforms/MacCatalyst/Entitlements.plist new file mode 100644 index 0000000..de4adc9 --- /dev/null +++ b/Platforms/MacCatalyst/Entitlements.plist @@ -0,0 +1,14 @@ + + + + + + + com.apple.security.app-sandbox + + + com.apple.security.network.client + + + + diff --git a/Platforms/MacCatalyst/Info.plist b/Platforms/MacCatalyst/Info.plist new file mode 100644 index 0000000..f2e0987 --- /dev/null +++ b/Platforms/MacCatalyst/Info.plist @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + UIDeviceFamily + + 2 + + LSApplicationCategoryType + public.app-category.lifestyle + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/Platforms/MacCatalyst/Program.cs b/Platforms/MacCatalyst/Program.cs new file mode 100644 index 0000000..be97404 --- /dev/null +++ b/Platforms/MacCatalyst/Program.cs @@ -0,0 +1,16 @@ +using ObjCRuntime; +using UIKit; + +namespace SunlightAggregationTerminal +{ + public class Program + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/Platforms/Windows/App.xaml b/Platforms/Windows/App.xaml new file mode 100644 index 0000000..f735933 --- /dev/null +++ b/Platforms/Windows/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/Platforms/Windows/App.xaml.cs b/Platforms/Windows/App.xaml.cs new file mode 100644 index 0000000..b30bebb --- /dev/null +++ b/Platforms/Windows/App.xaml.cs @@ -0,0 +1,25 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace SunlightAggregationTerminal.WinUI +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : MauiWinUIApplication + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } + +} diff --git a/Platforms/Windows/Package.appxmanifest b/Platforms/Windows/Package.appxmanifest new file mode 100644 index 0000000..468595a --- /dev/null +++ b/Platforms/Windows/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + + + $placeholder$ + User Name + $placeholder$.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Platforms/Windows/app.manifest b/Platforms/Windows/app.manifest new file mode 100644 index 0000000..823fc32 --- /dev/null +++ b/Platforms/Windows/app.manifest @@ -0,0 +1,17 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + true + + + diff --git a/Platforms/iOS/AppDelegate.cs b/Platforms/iOS/AppDelegate.cs new file mode 100644 index 0000000..40cc50a --- /dev/null +++ b/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,10 @@ +using Foundation; + +namespace SunlightAggregationTerminal +{ + [Register("AppDelegate")] + public class AppDelegate : MauiUIApplicationDelegate + { + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/Platforms/iOS/Info.plist b/Platforms/iOS/Info.plist new file mode 100644 index 0000000..0004a4f --- /dev/null +++ b/Platforms/iOS/Info.plist @@ -0,0 +1,32 @@ + + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/Platforms/iOS/Program.cs b/Platforms/iOS/Program.cs new file mode 100644 index 0000000..be97404 --- /dev/null +++ b/Platforms/iOS/Program.cs @@ -0,0 +1,16 @@ +using ObjCRuntime; +using UIKit; + +namespace SunlightAggregationTerminal +{ + public class Program + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/Platforms/iOS/Resources/PrivacyInfo.xcprivacy b/Platforms/iOS/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..24ab3b4 --- /dev/null +++ b/Platforms/iOS/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,51 @@ + + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + + diff --git a/ProfilePage.xaml b/ProfilePage.xaml new file mode 100644 index 0000000..03c6c79 --- /dev/null +++ b/ProfilePage.xaml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QueryPage.xaml.cs b/QueryPage.xaml.cs new file mode 100644 index 0000000..f9bdba0 --- /dev/null +++ b/QueryPage.xaml.cs @@ -0,0 +1,35 @@ +using CommunityToolkit.Mvvm.Messaging; + +namespace SunlightAggregationTerminal; + +public partial class QueryPage : ContentPage +{ + public QueryPage() + { + InitializeComponent(); + + WeakReferenceMessenger.Default.Register(this, (r, scannedValue) => + { + ResultEntry.Text = scannedValue; + }); + } + private async void OnScanButtonClicked(object sender, EventArgs e) + { + // 跳转到扫码页面 + await Navigation.PushAsync(new ScanPage(0)); + } + + // 处理“回车/完成”按钮点击 + /* private void OnEntryCompleted() + { + string inputText = ResultEntry.Text; + + if (!string.IsNullOrWhiteSpace(inputText)) + { + // 在这里处理用户手动输入的内容 + // 例如:验证格式、跳转页面、或者搜索商品 + //DisplayAlert("提示", $"你输入了: {inputText}", "确定"); + } + }*/ + +} \ No newline at end of file diff --git a/Resources/AppIcon/appicon.svg b/Resources/AppIcon/appicon.svg new file mode 100644 index 0000000..9d63b65 --- /dev/null +++ b/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Resources/AppIcon/appiconfg.svg b/Resources/AppIcon/appiconfg.svg new file mode 100644 index 0000000..21dfb25 --- /dev/null +++ b/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Resources/Fonts/OpenSans-Regular.ttf b/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000..2cc82d2 Binary files /dev/null and b/Resources/Fonts/OpenSans-Regular.ttf differ diff --git a/Resources/Fonts/OpenSans-Semibold.ttf b/Resources/Fonts/OpenSans-Semibold.ttf new file mode 100644 index 0000000..fcb5284 Binary files /dev/null and b/Resources/Fonts/OpenSans-Semibold.ttf differ diff --git a/Resources/Images/dotnet_bot.png b/Resources/Images/dotnet_bot.png new file mode 100644 index 0000000..054167e Binary files /dev/null and b/Resources/Images/dotnet_bot.png differ diff --git a/Resources/Raw/AboutAssets.txt b/Resources/Raw/AboutAssets.txt new file mode 100644 index 0000000..89dc758 --- /dev/null +++ b/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +These files will be deployed with your package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/Resources/Splash/splash.svg b/Resources/Splash/splash.svg new file mode 100644 index 0000000..21dfb25 --- /dev/null +++ b/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Resources/Styles/Colors.xaml b/Resources/Styles/Colors.xaml new file mode 100644 index 0000000..d57fcc6 --- /dev/null +++ b/Resources/Styles/Colors.xaml @@ -0,0 +1,44 @@ + + + + + + #512BD4 + #ac99ea + #242424 + #DFD8F7 + #9880e5 + #2B0B98 + + White + Black + #D600AA + #190649 + #1f1f1f + + #E1E1E1 + #C8C8C8 + #ACACAC + #919191 + #6E6E6E + #404040 + #212121 + #141414 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/Styles/Styles.xaml b/Resources/Styles/Styles.xaml new file mode 100644 index 0000000..5fef12a --- /dev/null +++ b/Resources/Styles/Styles.xaml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ScanPage.xaml b/ScanPage.xaml new file mode 100644 index 0000000..16b83e1 --- /dev/null +++ b/ScanPage.xaml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/ScanPage.xaml.cs b/ScanPage.xaml.cs new file mode 100644 index 0000000..bae0e9d --- /dev/null +++ b/ScanPage.xaml.cs @@ -0,0 +1,53 @@ +using CommunityToolkit.Mvvm.Messaging; +using SunlightAggregationTerminal.View; +using ZXing.Multi; +using ZXing.Net.Maui; +using ZXing.QrCode.Internal; + +namespace SunlightAggregationTerminal; + +public partial class ScanPage : ContentPage +{ + private int mode; + public ScanPage(int i) + { + InitializeComponent(); + mode = i; + } + private async void OnBarcodesDetected(object sender, BarcodeDetectionEventArgs e) + { + var firstResult = e.Results?.FirstOrDefault(); + if (firstResult is not null) + { + barcodeReader.IsDetecting = false; + + // 触发回调 + if (mode == 0) + { + MainThread.BeginInvokeOnMainThread(() => + { + // 震动 + try { Vibration.Default.Vibrate(TimeSpan.FromMilliseconds(100)); } catch { } + // 传入信息 + WeakReferenceMessenger.Default.Send(firstResult.Value); + // 等待 1.5 秒(非阻塞,界面不会卡死) + Task.Delay(1500); + // 关闭页面 + Navigation.PopAsync(); + }); + } + if (mode == 3) + { + MainThread.BeginInvokeOnMainThread(() => + { + try { Vibration.Default.Vibrate(TimeSpan.FromMilliseconds(100)); } catch { } + WeakReferenceMessenger.Default.Send(firstResult.Value); + Task.Delay(1500); + // 关闭页面 + Navigation.PopModalAsync(); + }); + } + } + } + +} \ No newline at end of file diff --git a/SunlightAggregationTerminal.csproj b/SunlightAggregationTerminal.csproj new file mode 100644 index 0000000..30979ec --- /dev/null +++ b/SunlightAggregationTerminal.csproj @@ -0,0 +1,131 @@ + + + + net10.0-android + $(TargetFrameworks);net10.0-ios;net10.0-maccatalyst + $(TargetFrameworks);net10.0-windows10.0.19041.0 + + + + + Exe + SunlightAggregationTerminal + true + true + enable + enable + + + SourceGen + + + SunlightAggregationTerminal + + + com.companyname.sunlightaggregationterminal + + + 1.0 + 1 + + + None + + 15.0 + 15.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + + + + + + diff --git a/SunlightAggregationTerminal.slnx b/SunlightAggregationTerminal.slnx new file mode 100644 index 0000000..beeab89 --- /dev/null +++ b/SunlightAggregationTerminal.slnx @@ -0,0 +1,3 @@ + + + diff --git a/View/LogPage.xaml b/View/LogPage.xaml new file mode 100644 index 0000000..45a2257 --- /dev/null +++ b/View/LogPage.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +