feat: 实现拨动开关的数字孪生
This commit is contained in:
parent
a2ac1bcb3b
commit
b6720d867d
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue