feat: 实现拨动开关的数字孪生
This commit is contained in:
		
							
								
								
									
										127
									
								
								server/src/Controllers/SwitchController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								server/src/Controllers/SwitchController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Cors;
 | 
				
			||||||
 | 
					using Peripherals.SwitchClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace server.Controllers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[ApiController]
 | 
				
			||||||
 | 
					[Route("api/[controller]")]
 | 
				
			||||||
 | 
					public class SwitchController : ControllerBase
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private readonly Database.UserManager _userManager = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// 获取示波器实例
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    private SwitchCtrl? GetSwitchCtrl()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var userName = User.Identity?.Name;
 | 
				
			||||||
 | 
					        if (string.IsNullOrEmpty(userName))
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var userRet = _userManager.GetUserByName(userName);
 | 
				
			||||||
 | 
					        if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var user = userRet.Value.Value;
 | 
				
			||||||
 | 
					        if (user.BoardID == Guid.Empty)
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var boardRet = _userManager.GetBoardByID(user.BoardID);
 | 
				
			||||||
 | 
					        if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var board = boardRet.Value.Value;
 | 
				
			||||||
 | 
					        return new SwitchCtrl(board.IpAddr, board.Port, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// 启用或禁用 Switch 外设
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    /// <param name="enable">是否启用</param>
 | 
				
			||||||
 | 
					    /// <returns>操作结果</returns>
 | 
				
			||||||
 | 
					    [HttpPost("enable")]
 | 
				
			||||||
 | 
					    [EnableCors("Users")]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
				
			||||||
 | 
					    public async Task<IActionResult> SetEnable([FromQuery] bool enable)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var switchCtrl = GetSwitchCtrl();
 | 
				
			||||||
 | 
					        if (switchCtrl == null)
 | 
				
			||||||
 | 
					            return BadRequest("Can't get user or board info");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var result = await switchCtrl.SetEnable(enable);
 | 
				
			||||||
 | 
					        if (!result.IsSuccessful)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error(result.Error, "SetEnable failed");
 | 
				
			||||||
 | 
					            return StatusCode(500, result.Error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Ok(result.Value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// 控制指定编号的 Switch 开关
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    /// <param name="num">开关编号</param>
 | 
				
			||||||
 | 
					    /// <param name="onOff">开/关</param>
 | 
				
			||||||
 | 
					    /// <returns>操作结果</returns>
 | 
				
			||||||
 | 
					    [HttpPost("switch")]
 | 
				
			||||||
 | 
					    [EnableCors("Users")]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
				
			||||||
 | 
					    public async Task<IActionResult> SetSwitchOnOff([FromQuery] int num, [FromQuery] bool onOff)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (num <= 0 || num > 6)
 | 
				
			||||||
 | 
					            return BadRequest(new ArgumentException($"Switch num should be 1~5, instead of {num}"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var switchCtrl = GetSwitchCtrl();
 | 
				
			||||||
 | 
					        if (switchCtrl == null)
 | 
				
			||||||
 | 
					            return BadRequest("Can't get user or board info");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var result = await switchCtrl.SetSwitchOnOff(num, onOff);
 | 
				
			||||||
 | 
					        if (!result.IsSuccessful)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error(result.Error, $"SetSwitchOnOff({num}, {onOff}) failed");
 | 
				
			||||||
 | 
					            return StatusCode(500, result.Error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Ok(result.Value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// 控制 Switch 开关
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    /// <param name="keyStatus">开关状态</param>
 | 
				
			||||||
 | 
					    /// <returns>操作结果</returns>
 | 
				
			||||||
 | 
					    [HttpPost("MultiSwitch")]
 | 
				
			||||||
 | 
					    [EnableCors("Users")]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
				
			||||||
 | 
					    public async Task<IActionResult> SetMultiSwitchsOnOff(bool[] keyStatus)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (keyStatus.Length == 0 || keyStatus.Length > 6) return BadRequest(
 | 
				
			||||||
 | 
					                new ArgumentException($"Switch num should be 1~5, instead of {keyStatus.Length}"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var switchCtrl = GetSwitchCtrl();
 | 
				
			||||||
 | 
					        if (switchCtrl == null)
 | 
				
			||||||
 | 
					            return BadRequest("Can't get user or board info");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int i = 0; i < keyStatus.Length; i++)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var result = await switchCtrl.SetSwitchOnOff(i, keyStatus[i]);
 | 
				
			||||||
 | 
					            if (!result.IsSuccessful)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                logger.Error(result.Error, $"SetSwitchOnOff({i}, {keyStatus[i]}) failed");
 | 
				
			||||||
 | 
					                return StatusCode(500, result.Error);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!result.Value) return Ok(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Ok(true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										65
									
								
								server/src/Peripherals/SwitchClient.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								server/src/Peripherals/SwitchClient.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					using System.Collections;
 | 
				
			||||||
 | 
					using System.Net;
 | 
				
			||||||
 | 
					using DotNext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Peripherals.SwitchClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SwitchCtrlAddr
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public const UInt32 BASE = 0xB0_00_00_20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public const UInt32 ENABLE = BASE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// <summary>
 | 
				
			||||||
 | 
					/// 矩阵键盘外设类,用于控制和管理矩阵键盘的功能。
 | 
				
			||||||
 | 
					/// </summary>
 | 
				
			||||||
 | 
					public class SwitchCtrl
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    readonly int timeout = 500;
 | 
				
			||||||
 | 
					    readonly int taskID;
 | 
				
			||||||
 | 
					    readonly int port;
 | 
				
			||||||
 | 
					    readonly string address;
 | 
				
			||||||
 | 
					    private IPEndPoint ep;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SwitchCtrl(string address, int port, int taskID, int timeout = 500)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (timeout < 0)
 | 
				
			||||||
 | 
					            throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
 | 
				
			||||||
 | 
					        this.address = address;
 | 
				
			||||||
 | 
					        this.port = port;
 | 
				
			||||||
 | 
					        this.ep = new IPEndPoint(IPAddress.Parse(address), port);
 | 
				
			||||||
 | 
					        this.taskID = taskID;
 | 
				
			||||||
 | 
					        this.timeout = timeout;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async ValueTask<Result<bool>> SetEnable(bool enable)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (MsgBus.IsRunning)
 | 
				
			||||||
 | 
					            MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
				
			||||||
 | 
					        else return new(new Exception("Message Bus not work!"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var ret = await UDPClientPool.WriteAddr(
 | 
				
			||||||
 | 
					            this.ep, this.taskID, SwitchCtrlAddr.ENABLE, enable ? 0x1U : 0x0U, this.timeout);
 | 
				
			||||||
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
 | 
					        return ret.Value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async ValueTask<Result<bool>> SetSwitchOnOff(int num, bool onOff)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (MsgBus.IsRunning)
 | 
				
			||||||
 | 
					            MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
				
			||||||
 | 
					        else return new(new Exception("Message Bus not work!"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var ret = await UDPClientPool.WriteAddr(
 | 
				
			||||||
 | 
					            this.ep, this.taskID, SwitchCtrlAddr.BASE + (UInt32)num, onOff ? 0x1U : 0x0U, this.timeout);
 | 
				
			||||||
 | 
					        if (!ret.IsSuccessful)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error($"Set Switch {onOff} failed: {ret.Error}");
 | 
				
			||||||
 | 
					            return new(ret.Error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ret.Value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										249
									
								
								src/APIClient.ts
									
									
									
									
									
								
							
							
						
						
									
										249
									
								
								src/APIClient.ts
									
									
									
									
									
								
							@@ -6936,6 +6936,255 @@ export class ResourceClient {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class SwitchClient {
 | 
				
			||||||
 | 
					    protected instance: AxiosInstance;
 | 
				
			||||||
 | 
					    protected baseUrl: string;
 | 
				
			||||||
 | 
					    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(baseUrl?: string, instance?: AxiosInstance) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.instance = instance || axios.create();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.baseUrl = baseUrl ?? "http://127.0.0.1:5000";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 启用或禁用 Switch 外设
 | 
				
			||||||
 | 
					     * @param enable (optional) 是否启用
 | 
				
			||||||
 | 
					     * @return 操作结果
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setEnable(enable: boolean | undefined, cancelToken?: CancelToken): Promise<boolean> {
 | 
				
			||||||
 | 
					        let url_ = this.baseUrl + "/api/Switch/enable?";
 | 
				
			||||||
 | 
					        if (enable === null)
 | 
				
			||||||
 | 
					            throw new Error("The parameter 'enable' cannot be null.");
 | 
				
			||||||
 | 
					        else if (enable !== undefined)
 | 
				
			||||||
 | 
					            url_ += "enable=" + encodeURIComponent("" + enable) + "&";
 | 
				
			||||||
 | 
					        url_ = url_.replace(/[?&]$/, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let options_: AxiosRequestConfig = {
 | 
				
			||||||
 | 
					            method: "POST",
 | 
				
			||||||
 | 
					            url: url_,
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                "Accept": "application/json"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            cancelToken
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.instance.request(options_).catch((_error: any) => {
 | 
				
			||||||
 | 
					            if (isAxiosError(_error) && _error.response) {
 | 
				
			||||||
 | 
					                return _error.response;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw _error;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).then((_response: AxiosResponse) => {
 | 
				
			||||||
 | 
					            return this.processSetEnable(_response);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected processSetEnable(response: AxiosResponse): Promise<boolean> {
 | 
				
			||||||
 | 
					        const status = response.status;
 | 
				
			||||||
 | 
					        let _headers: any = {};
 | 
				
			||||||
 | 
					        if (response.headers && typeof response.headers === "object") {
 | 
				
			||||||
 | 
					            for (const k in response.headers) {
 | 
				
			||||||
 | 
					                if (response.headers.hasOwnProperty(k)) {
 | 
				
			||||||
 | 
					                    _headers[k] = response.headers[k];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (status === 200) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result200: any = null;
 | 
				
			||||||
 | 
					            let resultData200  = _responseText;
 | 
				
			||||||
 | 
					                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            return Promise.resolve<boolean>(result200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 500) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result500: any = null;
 | 
				
			||||||
 | 
					            let resultData500  = _responseText;
 | 
				
			||||||
 | 
					            result500 = Exception.fromJS(resultData500);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 401) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result401: any = null;
 | 
				
			||||||
 | 
					            let resultData401  = _responseText;
 | 
				
			||||||
 | 
					            result401 = ProblemDetails.fromJS(resultData401);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result401);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status !== 200 && status !== 204) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Promise.resolve<boolean>(null as any);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 控制指定编号的 Switch 开关
 | 
				
			||||||
 | 
					     * @param num (optional) 开关编号
 | 
				
			||||||
 | 
					     * @param onOff (optional) 开/关
 | 
				
			||||||
 | 
					     * @return 操作结果
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setSwitchOnOff(num: number | undefined, onOff: boolean | undefined, cancelToken?: CancelToken): Promise<boolean> {
 | 
				
			||||||
 | 
					        let url_ = this.baseUrl + "/api/Switch/switch?";
 | 
				
			||||||
 | 
					        if (num === null)
 | 
				
			||||||
 | 
					            throw new Error("The parameter 'num' cannot be null.");
 | 
				
			||||||
 | 
					        else if (num !== undefined)
 | 
				
			||||||
 | 
					            url_ += "num=" + encodeURIComponent("" + num) + "&";
 | 
				
			||||||
 | 
					        if (onOff === null)
 | 
				
			||||||
 | 
					            throw new Error("The parameter 'onOff' cannot be null.");
 | 
				
			||||||
 | 
					        else if (onOff !== undefined)
 | 
				
			||||||
 | 
					            url_ += "onOff=" + encodeURIComponent("" + onOff) + "&";
 | 
				
			||||||
 | 
					        url_ = url_.replace(/[?&]$/, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let options_: AxiosRequestConfig = {
 | 
				
			||||||
 | 
					            method: "POST",
 | 
				
			||||||
 | 
					            url: url_,
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                "Accept": "application/json"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            cancelToken
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.instance.request(options_).catch((_error: any) => {
 | 
				
			||||||
 | 
					            if (isAxiosError(_error) && _error.response) {
 | 
				
			||||||
 | 
					                return _error.response;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw _error;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).then((_response: AxiosResponse) => {
 | 
				
			||||||
 | 
					            return this.processSetSwitchOnOff(_response);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected processSetSwitchOnOff(response: AxiosResponse): Promise<boolean> {
 | 
				
			||||||
 | 
					        const status = response.status;
 | 
				
			||||||
 | 
					        let _headers: any = {};
 | 
				
			||||||
 | 
					        if (response.headers && typeof response.headers === "object") {
 | 
				
			||||||
 | 
					            for (const k in response.headers) {
 | 
				
			||||||
 | 
					                if (response.headers.hasOwnProperty(k)) {
 | 
				
			||||||
 | 
					                    _headers[k] = response.headers[k];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (status === 200) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result200: any = null;
 | 
				
			||||||
 | 
					            let resultData200  = _responseText;
 | 
				
			||||||
 | 
					                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            return Promise.resolve<boolean>(result200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 400) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result400: any = null;
 | 
				
			||||||
 | 
					            let resultData400  = _responseText;
 | 
				
			||||||
 | 
					            result400 = ArgumentException.fromJS(resultData400);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result400);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 500) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result500: any = null;
 | 
				
			||||||
 | 
					            let resultData500  = _responseText;
 | 
				
			||||||
 | 
					            result500 = Exception.fromJS(resultData500);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 401) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result401: any = null;
 | 
				
			||||||
 | 
					            let resultData401  = _responseText;
 | 
				
			||||||
 | 
					            result401 = ProblemDetails.fromJS(resultData401);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result401);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status !== 200 && status !== 204) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Promise.resolve<boolean>(null as any);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 控制 Switch 开关
 | 
				
			||||||
 | 
					     * @param keyStatus 开关状态
 | 
				
			||||||
 | 
					     * @return 操作结果
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setMultiSwitchsOnOff(keyStatus: boolean[], cancelToken?: CancelToken): Promise<boolean> {
 | 
				
			||||||
 | 
					        let url_ = this.baseUrl + "/api/Switch/MultiSwitch";
 | 
				
			||||||
 | 
					        url_ = url_.replace(/[?&]$/, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const content_ = JSON.stringify(keyStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let options_: AxiosRequestConfig = {
 | 
				
			||||||
 | 
					            data: content_,
 | 
				
			||||||
 | 
					            method: "POST",
 | 
				
			||||||
 | 
					            url: url_,
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                "Content-Type": "application/json",
 | 
				
			||||||
 | 
					                "Accept": "application/json"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            cancelToken
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.instance.request(options_).catch((_error: any) => {
 | 
				
			||||||
 | 
					            if (isAxiosError(_error) && _error.response) {
 | 
				
			||||||
 | 
					                return _error.response;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw _error;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).then((_response: AxiosResponse) => {
 | 
				
			||||||
 | 
					            return this.processSetMultiSwitchsOnOff(_response);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected processSetMultiSwitchsOnOff(response: AxiosResponse): Promise<boolean> {
 | 
				
			||||||
 | 
					        const status = response.status;
 | 
				
			||||||
 | 
					        let _headers: any = {};
 | 
				
			||||||
 | 
					        if (response.headers && typeof response.headers === "object") {
 | 
				
			||||||
 | 
					            for (const k in response.headers) {
 | 
				
			||||||
 | 
					                if (response.headers.hasOwnProperty(k)) {
 | 
				
			||||||
 | 
					                    _headers[k] = response.headers[k];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (status === 200) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result200: any = null;
 | 
				
			||||||
 | 
					            let resultData200  = _responseText;
 | 
				
			||||||
 | 
					                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            return Promise.resolve<boolean>(result200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 400) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result400: any = null;
 | 
				
			||||||
 | 
					            let resultData400  = _responseText;
 | 
				
			||||||
 | 
					            result400 = ArgumentException.fromJS(resultData400);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result400);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 500) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result500: any = null;
 | 
				
			||||||
 | 
					            let resultData500  = _responseText;
 | 
				
			||||||
 | 
					            result500 = Exception.fromJS(resultData500);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status === 401) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            let result401: any = null;
 | 
				
			||||||
 | 
					            let resultData401  = _responseText;
 | 
				
			||||||
 | 
					            result401 = ProblemDetails.fromJS(resultData401);
 | 
				
			||||||
 | 
					            return throwException("A server side error occurred.", status, _responseText, _headers, result401);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (status !== 200 && status !== 204) {
 | 
				
			||||||
 | 
					            const _responseText = response.data;
 | 
				
			||||||
 | 
					            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Promise.resolve<boolean>(null as any);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class TutorialClient {
 | 
					export class TutorialClient {
 | 
				
			||||||
    protected instance: AxiosInstance;
 | 
					    protected instance: AxiosInstance;
 | 
				
			||||||
    protected baseUrl: string;
 | 
					    protected baseUrl: string;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
    xmlns="http://www.w3.org/2000/svg"
 | 
					    xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
    :width="width"
 | 
					    :width="width"
 | 
				
			||||||
    :height="height"
 | 
					    :height="height"
 | 
				
			||||||
    :viewBox="`4 6 ${props.switchCount + 2} 4`"
 | 
					    :viewBox="`4 6 ${switchCount + 2} 4`"
 | 
				
			||||||
    class="dip-switch"
 | 
					    class="dip-switch"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <defs>
 | 
					    <defs>
 | 
				
			||||||
@@ -38,7 +38,7 @@
 | 
				
			|||||||
    </defs>
 | 
					    </defs>
 | 
				
			||||||
    <g>
 | 
					    <g>
 | 
				
			||||||
      <rect
 | 
					      <rect
 | 
				
			||||||
        :width="props.switchCount + 2"
 | 
					        :width="switchCount + 2"
 | 
				
			||||||
        height="4"
 | 
					        height="4"
 | 
				
			||||||
        x="4"
 | 
					        x="4"
 | 
				
			||||||
        y="6"
 | 
					        y="6"
 | 
				
			||||||
@@ -55,7 +55,7 @@
 | 
				
			|||||||
        ON
 | 
					        ON
 | 
				
			||||||
      </text>
 | 
					      </text>
 | 
				
			||||||
      <g>
 | 
					      <g>
 | 
				
			||||||
        <template v-for="(_, index) in Array(props.switchCount)" :key="index">
 | 
					        <template v-for="(_, index) in Array(switchCount)" :key="index">
 | 
				
			||||||
          <rect
 | 
					          <rect
 | 
				
			||||||
            class="glow interactive"
 | 
					            class="glow interactive"
 | 
				
			||||||
            @click="toggleBtnStatus(index)"
 | 
					            @click="toggleBtnStatus(index)"
 | 
				
			||||||
@@ -101,27 +101,36 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { SwitchClient } from "@/APIClient";
 | 
				
			||||||
 | 
					import { AuthManager } from "@/utils/AuthManager";
 | 
				
			||||||
 | 
					import { isUndefined } from "lodash";
 | 
				
			||||||
import { ref, computed, watch, onMounted } from "vue";
 | 
					import { ref, computed, watch, onMounted } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					interface Props {
 | 
				
			||||||
  size?: number;
 | 
					  size?: number;
 | 
				
			||||||
 | 
					  enableDigitalTwin?: boolean;
 | 
				
			||||||
  switchCount?: number;
 | 
					  switchCount?: number;
 | 
				
			||||||
  initialValues?: boolean[] | string;
 | 
					  initialValues?: string;
 | 
				
			||||||
  showLabels?: boolean;
 | 
					  showLabels?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {
 | 
					const props = withDefaults(defineProps<Props>(), {
 | 
				
			||||||
  size: 1,
 | 
					  size: 1,
 | 
				
			||||||
 | 
					  enableDigitalTwin: false,
 | 
				
			||||||
  switchCount: 6,
 | 
					  switchCount: 6,
 | 
				
			||||||
  initialValues: () => [],
 | 
					  initialValues: "",
 | 
				
			||||||
  showLabels: true,
 | 
					  showLabels: true,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
const emit = defineEmits(["change"]);
 | 
					
 | 
				
			||||||
 | 
					const switchCount = computed(() => {
 | 
				
			||||||
 | 
					  if (props.enableDigitalTwin) return 5;
 | 
				
			||||||
 | 
					  else return props.switchCount;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 解析初始值
 | 
					// 解析初始值
 | 
				
			||||||
function parseInitialValues(): boolean[] {
 | 
					function parseInitialValues(): boolean[] {
 | 
				
			||||||
  if (Array.isArray(props.initialValues)) {
 | 
					  if (Array.isArray(props.initialValues)) {
 | 
				
			||||||
    return [...props.initialValues].slice(0, props.switchCount);
 | 
					    return [...props.initialValues].slice(0, switchCount.value);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (
 | 
					  if (
 | 
				
			||||||
    typeof props.initialValues === "string" &&
 | 
					    typeof props.initialValues === "string" &&
 | 
				
			||||||
@@ -133,14 +142,14 @@ function parseInitialValues(): boolean[] {
 | 
				
			|||||||
    while (arr.length < props.switchCount) arr.push(false);
 | 
					    while (arr.length < props.switchCount) arr.push(false);
 | 
				
			||||||
    return arr.slice(0, props.switchCount);
 | 
					    return arr.slice(0, props.switchCount);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return Array(props.switchCount).fill(false);
 | 
					  return Array(switchCount.value).fill(false);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 状态唯一真相
 | 
					// 状态唯一真相
 | 
				
			||||||
const btnStatus = ref<boolean[]>(parseInitialValues());
 | 
					const btnStatus = ref<boolean[]>(parseInitialValues());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 计算宽高
 | 
					// 计算宽高
 | 
				
			||||||
const width = computed(() => (props.switchCount * 25 + 20) * props.size);
 | 
					const width = computed(() => (switchCount.value * 25 + 20) * props.size);
 | 
				
			||||||
const height = computed(() => 85 * props.size);
 | 
					const height = computed(() => 85 * props.size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 按钮位置
 | 
					// 按钮位置
 | 
				
			||||||
@@ -150,13 +159,14 @@ const btnLocation = computed(() =>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 状态变更统一处理
 | 
					// 状态变更统一处理
 | 
				
			||||||
function updateStatus(newStates: boolean[], index?: number) {
 | 
					function updateStatus(newStates: boolean[], index?: number) {
 | 
				
			||||||
  btnStatus.value = newStates.slice(0, props.switchCount);
 | 
					  btnStatus.value = newStates.slice(0, switchCount.value);
 | 
				
			||||||
  SwitchClient.setStates(btnStatus.value); // 同步后端
 | 
					  if (props.enableDigitalTwin) {
 | 
				
			||||||
  emit("change", {
 | 
					    try {
 | 
				
			||||||
    index,
 | 
					      const client = AuthManager.createClient(SwitchClient);
 | 
				
			||||||
    value: index !== undefined ? btnStatus.value[index] : undefined,
 | 
					      if (!isUndefined(index)) client.setSwitchOnOff(index, newStates[index]);
 | 
				
			||||||
    states: [...btnStatus.value],
 | 
					      else client.setMultiSwitchsOnOff(btnStatus.value);
 | 
				
			||||||
  });
 | 
					    } catch (error: any) {}
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 切换单个
 | 
					// 切换单个
 | 
				
			||||||
@@ -167,11 +177,6 @@ function toggleBtnStatus(idx: number) {
 | 
				
			|||||||
  updateStatus(newStates, idx);
 | 
					  updateStatus(newStates, idx);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 一次性设置全部
 | 
					 | 
				
			||||||
function setAllStates(states: boolean[]) {
 | 
					 | 
				
			||||||
  updateStatus(states);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 单个设置
 | 
					// 单个设置
 | 
				
			||||||
function setBtnStatus(idx: number, isOn: boolean) {
 | 
					function setBtnStatus(idx: number, isOn: boolean) {
 | 
				
			||||||
  if (idx < 0 || idx >= btnStatus.value.length) return;
 | 
					  if (idx < 0 || idx >= btnStatus.value.length) return;
 | 
				
			||||||
@@ -182,19 +187,12 @@ function setBtnStatus(idx: number, isOn: boolean) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 监听 props 变化只同步一次
 | 
					// 监听 props 变化只同步一次
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
  () => [props.switchCount, props.initialValues],
 | 
					  () => [switchCount.value, props.initialValues],
 | 
				
			||||||
  () => {
 | 
					  () => {
 | 
				
			||||||
    btnStatus.value = parseInitialValues();
 | 
					    btnStatus.value = parseInitialValues();
 | 
				
			||||||
    SwitchClient.setStates(btnStatus.value);
 | 
					    updateStatus(btnStatus.value);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					 | 
				
			||||||
// 监听后端推送
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					 | 
				
			||||||
  SwitchClient.onStateChange((states: boolean[]) => {
 | 
					 | 
				
			||||||
    btnStatus.value = states.slice(0, props.switchCount);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped lang="postcss">
 | 
					<style scoped lang="postcss">
 | 
				
			||||||
@@ -214,3 +212,15 @@ rect {
 | 
				
			|||||||
  cursor: pointer;
 | 
					  cursor: pointer;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					export function getDefaultProps() {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    size: 1,
 | 
				
			||||||
 | 
					    enableDigitalTwin: false,
 | 
				
			||||||
 | 
					    switchCount: 6,
 | 
				
			||||||
 | 
					    initialValues: "",
 | 
				
			||||||
 | 
					    showLabels: true,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user