feat: 添加管理员实验板管理界面
This commit is contained in:
parent
546b9250fa
commit
8789d6f9ee
|
@ -9,6 +9,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@svgdotjs/svg.js": "^3.2.4",
|
"@svgdotjs/svg.js": "^3.2.4",
|
||||||
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
"@vueuse/core": "^13.5.0",
|
"@vueuse/core": "^13.5.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
|
@ -1743,6 +1744,19 @@
|
||||||
"tailwindcss": "4.1.4"
|
"tailwindcss": "4.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/table-core": {
|
||||||
|
"version": "8.21.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
|
||||||
|
"integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/virtual-core": {
|
"node_modules/@tanstack/virtual-core": {
|
||||||
"version": "3.13.12",
|
"version": "3.13.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
|
||||||
|
@ -1753,6 +1767,25 @@
|
||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/vue-table": {
|
||||||
|
"version": "8.21.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.3.tgz",
|
||||||
|
"integrity": "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/table-core": "8.21.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": ">=3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/vue-virtual": {
|
"node_modules/@tanstack/vue-virtual": {
|
||||||
"version": "3.13.12",
|
"version": "3.13.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@svgdotjs/svg.js": "^3.2.4",
|
"@svgdotjs/svg.js": "^3.2.4",
|
||||||
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
"@vueuse/core": "^13.5.0",
|
"@vueuse/core": "^13.5.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
|
|
|
@ -62,6 +62,7 @@ try
|
||||||
options.Authority = "http://localhost:5000";
|
options.Authority = "http://localhost:5000";
|
||||||
options.RequireHttpsMetadata = false;
|
options.RequireHttpsMetadata = false;
|
||||||
});
|
});
|
||||||
|
// Add JWT Token Authorization Policy
|
||||||
builder.Services.AddAuthorization(options =>
|
builder.Services.AddAuthorization(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy("Admin", policy =>
|
options.AddPolicy("Admin", policy =>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Net;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -17,7 +18,10 @@ public class DataController : ControllerBase
|
||||||
{
|
{
|
||||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public class GetUserInfoResponse
|
/// <summary>
|
||||||
|
/// [TODO:description]
|
||||||
|
/// </summary>
|
||||||
|
public class UserInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户的唯一标识符
|
/// 用户的唯一标识符
|
||||||
|
@ -118,7 +122,7 @@ public class DataController : ControllerBase
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("GetUserInfo")]
|
[HttpGet("GetUserInfo")]
|
||||||
[EnableCors("Users")]
|
[EnableCors("Users")]
|
||||||
[ProducesResponseType(typeof(GetUserInfoResponse), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(UserInfo), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
@ -139,7 +143,7 @@ public class DataController : ControllerBase
|
||||||
return BadRequest("用户不存在");
|
return BadRequest("用户不存在");
|
||||||
|
|
||||||
var user = ret.Value.Value;
|
var user = ret.Value.Value;
|
||||||
return Ok(new GetUserInfoResponse
|
return Ok(new UserInfo
|
||||||
{
|
{
|
||||||
ID = user.ID,
|
ID = user.ID,
|
||||||
Name = user.Name,
|
Name = user.Name,
|
||||||
|
@ -187,5 +191,85 @@ public class DataController : ControllerBase
|
||||||
return StatusCode(StatusCodes.Status500InternalServerError, "注册失败,请稍后重试");
|
return StatusCode(StatusCodes.Status500InternalServerError, "注册失败,请稍后重试");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新增板子(管理员权限)
|
||||||
|
/// </summary>
|
||||||
|
[Authorize("Admin")]
|
||||||
|
[HttpPost("AddBoard")]
|
||||||
|
[EnableCors("Users")]
|
||||||
|
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public IActionResult AddBoard(string name, string ipAddr, int port)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
return BadRequest("板子名称不能为空");
|
||||||
|
if (string.IsNullOrWhiteSpace(ipAddr))
|
||||||
|
return BadRequest("IP地址不能为空");
|
||||||
|
if (port <= 0 || port > 65535)
|
||||||
|
return BadRequest("端口号不合法");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var db = new Database.AppDataConnection();
|
||||||
|
var ret = db.AddBoard(name, ipAddr, port);
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Error(ex, "新增板子时发生异常");
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "新增失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除板子(管理员权限)
|
||||||
|
/// </summary>
|
||||||
|
[Authorize("Admin")]
|
||||||
|
[HttpDelete("DeleteBoard")]
|
||||||
|
[EnableCors("Users")]
|
||||||
|
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public IActionResult DeleteBoard(Guid id)
|
||||||
|
{
|
||||||
|
if (id == Guid.Empty)
|
||||||
|
return BadRequest("板子Guid不能为空");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var db = new Database.AppDataConnection();
|
||||||
|
var ret = db.DeleteBoardByID(id);
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Error(ex, "删除板子时发生异常");
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "删除失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取全部板子(管理员权限)
|
||||||
|
/// </summary>
|
||||||
|
[Authorize("Admin")]
|
||||||
|
[HttpGet("GetAllBoards")]
|
||||||
|
[EnableCors("Users")]
|
||||||
|
[ProducesResponseType(typeof(Database.Board[]), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public IActionResult GetAllBoards()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var db = new Database.AppDataConnection();
|
||||||
|
var boards = db.GetAllBoard();
|
||||||
|
return Ok(boards);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Error(ex, "获取全部板子时发生异常");
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "获取失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using DotNext;
|
using DotNext;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ public class RemoteUpdateController : ControllerBase
|
||||||
/// <param name="bitstream2">比特流文件2</param>
|
/// <param name="bitstream2">比特流文件2</param>
|
||||||
/// <param name="bitstream3">比特流文件3</param>
|
/// <param name="bitstream3">比特流文件3</param>
|
||||||
/// <returns>上传结果</returns>
|
/// <returns>上传结果</returns>
|
||||||
|
[Authorize("Admin")]
|
||||||
[HttpPost("UploadBitstream")]
|
[HttpPost("UploadBitstream")]
|
||||||
[EnableCors("Users")]
|
[EnableCors("Users")]
|
||||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||||
|
@ -129,6 +131,7 @@ public class RemoteUpdateController : ControllerBase
|
||||||
/// <param name="address"> 设备地址 </param>
|
/// <param name="address"> 设备地址 </param>
|
||||||
/// <param name="port"> 设备端口 </param>
|
/// <param name="port"> 设备端口 </param>
|
||||||
/// <param name="bitstreamNum"> 比特流位号 </param>
|
/// <param name="bitstreamNum"> 比特流位号 </param>
|
||||||
|
[Authorize("Admin")]
|
||||||
[HttpPost("DownloadBitstream")]
|
[HttpPost("DownloadBitstream")]
|
||||||
[EnableCors("Users")]
|
[EnableCors("Users")]
|
||||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||||
|
@ -179,6 +182,7 @@ public class RemoteUpdateController : ControllerBase
|
||||||
/// <param name="port">设备端口</param>
|
/// <param name="port">设备端口</param>
|
||||||
/// <param name="bitstreamNum">比特流编号</param>
|
/// <param name="bitstreamNum">比特流编号</param>
|
||||||
/// <returns>总共上传比特流的数量</returns>
|
/// <returns>总共上传比特流的数量</returns>
|
||||||
|
[Authorize("Admin")]
|
||||||
[HttpPost("DownloadMultiBitstreams")]
|
[HttpPost("DownloadMultiBitstreams")]
|
||||||
[EnableCors("Users")]
|
[EnableCors("Users")]
|
||||||
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
|
||||||
|
@ -239,6 +243,7 @@ public class RemoteUpdateController : ControllerBase
|
||||||
/// <param name="port">设备端口</param>
|
/// <param name="port">设备端口</param>
|
||||||
/// <param name="bitstreamNum">比特流编号</param>
|
/// <param name="bitstreamNum">比特流编号</param>
|
||||||
/// <returns>操作结果</returns>
|
/// <returns>操作结果</returns>
|
||||||
|
[Authorize("Admin")]
|
||||||
[HttpPost("HotResetBitstream")]
|
[HttpPost("HotResetBitstream")]
|
||||||
[EnableCors("Users")]
|
[EnableCors("Users")]
|
||||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||||
|
@ -267,6 +272,7 @@ public class RemoteUpdateController : ControllerBase
|
||||||
/// <param name="address">[TODO:parameter]</param>
|
/// <param name="address">[TODO:parameter]</param>
|
||||||
/// <param name="port">[TODO:parameter]</param>
|
/// <param name="port">[TODO:parameter]</param>
|
||||||
/// <returns>[TODO:return]</returns>
|
/// <returns>[TODO:return]</returns>
|
||||||
|
[Authorize("Admin")]
|
||||||
[HttpPost("GetFirmwareVersion")]
|
[HttpPost("GetFirmwareVersion")]
|
||||||
[EnableCors("Users")]
|
[EnableCors("Users")]
|
||||||
[ProducesResponseType(typeof(UInt32), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(UInt32), StatusCodes.Status200OK)]
|
||||||
|
|
|
@ -98,6 +98,12 @@ public class Board
|
||||||
[NotNull]
|
[NotNull]
|
||||||
public required BoardStatus Status { get; set; }
|
public required BoardStatus Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [TODO:description]
|
||||||
|
/// </summary>
|
||||||
|
[NotNull]
|
||||||
|
public string FirmVersion { get; set; } = "1.0.0";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [TODO:description]
|
/// [TODO:description]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -273,6 +279,35 @@ public class AppDataConnection : DataConnection
|
||||||
return this.Insert(board);
|
return this.Insert(board);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [TODO:description]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public int DeleteBoardByName(string name)
|
||||||
|
{
|
||||||
|
return this.Board.Where(board => board.BoardName == name).Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [TODO:description]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public int DeleteBoardByID(Guid id)
|
||||||
|
{
|
||||||
|
return this.Board.Where(board => board.ID == id).Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [TODO:description]
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public Board[] GetAllBoard()
|
||||||
|
{
|
||||||
|
return this.Board.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [TODO:description]
|
/// [TODO:description]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
308
src/APIClient.ts
308
src/APIClient.ts
|
@ -482,11 +482,57 @@ export class DataClient {
|
||||||
return Promise.resolve<string>(null as any);
|
return Promise.resolve<string>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试管理员用户认证,需携带有效 JWT
|
||||||
|
* @return 认证成功信息
|
||||||
|
*/
|
||||||
|
testAdminAuth(): Promise<string> {
|
||||||
|
let url_ = this.baseUrl + "/api/Data/TestAdminAuth";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processTestAdminAuth(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processTestAdminAuth(response: Response): Promise<string> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<string>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前用户信息
|
* 获取当前用户信息
|
||||||
* @return 用户信息,包括ID、用户名、邮箱和板卡ID
|
* @return 用户信息,包括ID、用户名、邮箱和板卡ID
|
||||||
*/
|
*/
|
||||||
getUserInfo(): Promise<GetUserInfoResponse> {
|
getUserInfo(): Promise<UserInfo> {
|
||||||
let url_ = this.baseUrl + "/api/Data/GetUserInfo";
|
let url_ = this.baseUrl + "/api/Data/GetUserInfo";
|
||||||
url_ = url_.replace(/[?&]$/, "");
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
@ -502,14 +548,14 @@ export class DataClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processGetUserInfo(response: Response): Promise<GetUserInfoResponse> {
|
protected processGetUserInfo(response: Response): Promise<UserInfo> {
|
||||||
const status = response.status;
|
const status = response.status;
|
||||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
return response.text().then((_responseText) => {
|
return response.text().then((_responseText) => {
|
||||||
let result200: any = null;
|
let result200: any = null;
|
||||||
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
result200 = GetUserInfoResponse.fromJS(resultData200);
|
result200 = UserInfo.fromJS(resultData200);
|
||||||
return result200;
|
return result200;
|
||||||
});
|
});
|
||||||
} else if (status === 400) {
|
} else if (status === 400) {
|
||||||
|
@ -535,7 +581,7 @@ export class DataClient {
|
||||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve<GetUserInfoResponse>(null as any);
|
return Promise.resolve<UserInfo>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -602,6 +648,172 @@ export class DataClient {
|
||||||
}
|
}
|
||||||
return Promise.resolve<boolean>(null as any);
|
return Promise.resolve<boolean>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增板子(管理员权限)
|
||||||
|
* @param name (optional)
|
||||||
|
* @param ipAddr (optional)
|
||||||
|
* @param port (optional)
|
||||||
|
*/
|
||||||
|
addBoard(name: string | undefined, ipAddr: string | undefined, port: number | undefined): Promise<number> {
|
||||||
|
let url_ = this.baseUrl + "/api/Data/AddBoard?";
|
||||||
|
if (name === null)
|
||||||
|
throw new Error("The parameter 'name' cannot be null.");
|
||||||
|
else if (name !== undefined)
|
||||||
|
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
||||||
|
if (ipAddr === null)
|
||||||
|
throw new Error("The parameter 'ipAddr' cannot be null.");
|
||||||
|
else if (ipAddr !== undefined)
|
||||||
|
url_ += "ipAddr=" + encodeURIComponent("" + ipAddr) + "&";
|
||||||
|
if (port === null)
|
||||||
|
throw new Error("The parameter 'port' cannot be null.");
|
||||||
|
else if (port !== undefined)
|
||||||
|
url_ += "port=" + encodeURIComponent("" + port) + "&";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processAddBoard(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processAddBoard(response: Response): Promise<number> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<number>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除板子(管理员权限)
|
||||||
|
* @param id (optional)
|
||||||
|
*/
|
||||||
|
deleteBoard(id: string | undefined): Promise<number> {
|
||||||
|
let url_ = this.baseUrl + "/api/Data/DeleteBoard?";
|
||||||
|
if (id === null)
|
||||||
|
throw new Error("The parameter 'id' cannot be null.");
|
||||||
|
else if (id !== undefined)
|
||||||
|
url_ += "id=" + encodeURIComponent("" + id) + "&";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processDeleteBoard(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processDeleteBoard(response: Response): Promise<number> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<number>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全部板子(管理员权限)
|
||||||
|
*/
|
||||||
|
getAllBoards(): Promise<Board[]> {
|
||||||
|
let url_ = this.baseUrl + "/api/Data/GetAllBoards";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processGetAllBoards(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processGetAllBoards(response: Response): Promise<Board[]> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
if (Array.isArray(resultData200)) {
|
||||||
|
result200 = [] as any;
|
||||||
|
for (let item of resultData200)
|
||||||
|
result200!.push(Board.fromJS(item));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result200 = <any>null;
|
||||||
|
}
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<Board[]>(null as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DDSClient {
|
export class DDSClient {
|
||||||
|
@ -2445,7 +2657,8 @@ export interface IProblemDetails {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GetUserInfoResponse implements IGetUserInfoResponse {
|
/** [TODO:description] */
|
||||||
|
export class UserInfo implements IUserInfo {
|
||||||
/** 用户的唯一标识符 */
|
/** 用户的唯一标识符 */
|
||||||
id!: string;
|
id!: string;
|
||||||
/** 用户的名称 */
|
/** 用户的名称 */
|
||||||
|
@ -2455,7 +2668,7 @@ export class GetUserInfoResponse implements IGetUserInfoResponse {
|
||||||
/** 用户关联的板卡ID */
|
/** 用户关联的板卡ID */
|
||||||
boardID!: string;
|
boardID!: string;
|
||||||
|
|
||||||
constructor(data?: IGetUserInfoResponse) {
|
constructor(data?: IUserInfo) {
|
||||||
if (data) {
|
if (data) {
|
||||||
for (var property in data) {
|
for (var property in data) {
|
||||||
if (data.hasOwnProperty(property))
|
if (data.hasOwnProperty(property))
|
||||||
|
@ -2473,9 +2686,9 @@ export class GetUserInfoResponse implements IGetUserInfoResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJS(data: any): GetUserInfoResponse {
|
static fromJS(data: any): UserInfo {
|
||||||
data = typeof data === 'object' ? data : {};
|
data = typeof data === 'object' ? data : {};
|
||||||
let result = new GetUserInfoResponse();
|
let result = new UserInfo();
|
||||||
result.init(data);
|
result.init(data);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -2490,7 +2703,8 @@ export class GetUserInfoResponse implements IGetUserInfoResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGetUserInfoResponse {
|
/** [TODO:description] */
|
||||||
|
export interface IUserInfo {
|
||||||
/** 用户的唯一标识符 */
|
/** 用户的唯一标识符 */
|
||||||
id: string;
|
id: string;
|
||||||
/** 用户的名称 */
|
/** 用户的名称 */
|
||||||
|
@ -2501,6 +2715,82 @@ export interface IGetUserInfoResponse {
|
||||||
boardID: string;
|
boardID: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** FPGA 板子类,表示板子信息 */
|
||||||
|
export class Board implements IBoard {
|
||||||
|
/** FPGA 板子的唯一标识符 */
|
||||||
|
id!: string;
|
||||||
|
/** FPGA 板子的名称 */
|
||||||
|
boardName!: string;
|
||||||
|
/** [TODO:description] */
|
||||||
|
ipAddr!: string;
|
||||||
|
/** [TODO:description] */
|
||||||
|
port!: number;
|
||||||
|
/** [TODO:description] */
|
||||||
|
status!: BoardStatus;
|
||||||
|
/** [TODO:description] */
|
||||||
|
firmVersion!: string;
|
||||||
|
|
||||||
|
constructor(data?: IBoard) {
|
||||||
|
if (data) {
|
||||||
|
for (var property in data) {
|
||||||
|
if (data.hasOwnProperty(property))
|
||||||
|
(<any>this)[property] = (<any>data)[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_data?: any) {
|
||||||
|
if (_data) {
|
||||||
|
this.id = _data["id"];
|
||||||
|
this.boardName = _data["boardName"];
|
||||||
|
this.ipAddr = _data["ipAddr"];
|
||||||
|
this.port = _data["port"];
|
||||||
|
this.status = _data["status"];
|
||||||
|
this.firmVersion = _data["firmVersion"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJS(data: any): Board {
|
||||||
|
data = typeof data === 'object' ? data : {};
|
||||||
|
let result = new Board();
|
||||||
|
result.init(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(data?: any) {
|
||||||
|
data = typeof data === 'object' ? data : {};
|
||||||
|
data["id"] = this.id;
|
||||||
|
data["boardName"] = this.boardName;
|
||||||
|
data["ipAddr"] = this.ipAddr;
|
||||||
|
data["port"] = this.port;
|
||||||
|
data["status"] = this.status;
|
||||||
|
data["firmVersion"] = this.firmVersion;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** FPGA 板子类,表示板子信息 */
|
||||||
|
export interface IBoard {
|
||||||
|
/** FPGA 板子的唯一标识符 */
|
||||||
|
id: string;
|
||||||
|
/** FPGA 板子的名称 */
|
||||||
|
boardName: string;
|
||||||
|
/** [TODO:description] */
|
||||||
|
ipAddr: string;
|
||||||
|
/** [TODO:description] */
|
||||||
|
port: number;
|
||||||
|
/** [TODO:description] */
|
||||||
|
status: BoardStatus;
|
||||||
|
/** [TODO:description] */
|
||||||
|
firmVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [TODO:description] */
|
||||||
|
export enum BoardStatus {
|
||||||
|
Busy = 0,
|
||||||
|
Available = 1,
|
||||||
|
}
|
||||||
|
|
||||||
export class SystemException extends Exception implements ISystemException {
|
export class SystemException extends Exception implements ISystemException {
|
||||||
|
|
||||||
constructor(data?: ISystemException) {
|
constructor(data?: ISystemException) {
|
||||||
|
|
|
@ -1,93 +1,122 @@
|
||||||
import { DataClient } from '@/APIClient'
|
import { DataClient } from "@/APIClient";
|
||||||
|
|
||||||
export class AuthManager {
|
export class AuthManager {
|
||||||
// 存储token到localStorage
|
// 存储token到localStorage
|
||||||
public static setToken(token: string): void {
|
public static setToken(token: string): void {
|
||||||
localStorage.setItem('authToken', token)
|
localStorage.setItem("authToken", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从localStorage获取token
|
// 从localStorage获取token
|
||||||
public static getToken(): string | null {
|
public static getToken(): string | null {
|
||||||
return localStorage.getItem('authToken')
|
return localStorage.getItem("authToken");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除token
|
// 清除token
|
||||||
public static clearToken(): void {
|
public static clearToken(): void {
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem("authToken");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已认证
|
// 检查是否已认证
|
||||||
public static async isAuthenticated(): Promise<boolean> {
|
public static async isAuthenticated(): Promise<boolean> {
|
||||||
return await AuthManager.verifyToken()
|
return await AuthManager.verifyToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为HTTP请求添加Authorization header
|
// 为HTTP请求添加Authorization header
|
||||||
public static addAuthHeader(client: any): void {
|
public static addAuthHeader(client: any): void {
|
||||||
const token = AuthManager.getToken()
|
const token = AuthManager.getToken();
|
||||||
if (token && client.http) {
|
if (token && client.http) {
|
||||||
const originalFetch = client.http.fetch
|
const originalFetch = client.http.fetch;
|
||||||
client.http.fetch = (url: RequestInfo, init?: RequestInit) => {
|
client.http.fetch = (url: RequestInfo, init?: RequestInit) => {
|
||||||
if (!init) init = {}
|
if (!init) init = {};
|
||||||
if (!init.headers) init.headers = {}
|
if (!init.headers) init.headers = {};
|
||||||
|
|
||||||
// 添加Authorization header
|
// 添加Authorization header
|
||||||
if (typeof init.headers === 'object' && init.headers !== null) {
|
if (typeof init.headers === "object" && init.headers !== null) {
|
||||||
(init.headers as any)['Authorization'] = `Bearer ${token}`
|
(init.headers as any)["Authorization"] = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalFetch(url, init)
|
return originalFetch(url, init);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建已配置认证的API客户端
|
// 创建已配置认证的API客户端
|
||||||
public static createAuthenticatedClient(): DataClient {
|
public static createAuthenticatedClient(): DataClient {
|
||||||
const client = new DataClient()
|
const client = new DataClient();
|
||||||
AuthManager.addAuthHeader(client)
|
AuthManager.addAuthHeader(client);
|
||||||
return client
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录函数
|
// 登录函数
|
||||||
public static async login(username: string, password: string): Promise<boolean> {
|
public static async login(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const client = new DataClient()
|
const client = new DataClient();
|
||||||
const token = await client.login(username, password)
|
const token = await client.login(username, password);
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
AuthManager.setToken(token)
|
AuthManager.setToken(token);
|
||||||
|
|
||||||
// 验证token
|
// 验证token
|
||||||
const authClient = AuthManager.createAuthenticatedClient()
|
const authClient = AuthManager.createAuthenticatedClient();
|
||||||
await authClient.testAuth()
|
await authClient.testAuth();
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
AuthManager.clearToken()
|
AuthManager.clearToken();
|
||||||
throw error
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登出函数
|
// 登出函数
|
||||||
public static logout(): void {
|
public static logout(): void {
|
||||||
AuthManager.clearToken()
|
AuthManager.clearToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证当前token是否有效
|
// 验证当前token是否有效
|
||||||
public static async verifyToken(): Promise<boolean> {
|
public static async verifyToken(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const token = AuthManager.getToken()
|
const token = AuthManager.getToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = AuthManager.createAuthenticatedClient()
|
const client = AuthManager.createAuthenticatedClient();
|
||||||
await client.testAuth()
|
await client.testAuth();
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
AuthManager.clearToken()
|
AuthManager.clearToken();
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 验证管理员权限
|
||||||
|
public static async verifyAdminAuth(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const token = AuthManager.getToken();
|
||||||
|
if (!token) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = AuthManager.createAuthenticatedClient();
|
||||||
|
await client.testAdminAuth();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
// 如果是401错误,说明token有效但不是管理员,不需要清除token
|
||||||
|
// 如果是其他错误,可能token无效,清除token
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"status" in error &&
|
||||||
|
error.status !== 401
|
||||||
|
) {
|
||||||
|
AuthManager.clearToken();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-base-200 min-h-screen">
|
<div class="bg-base-200 min-h-screen">
|
||||||
<main class="hero min-h-screen bg-base-200">
|
<main class="hero min-h-screen bg-base-200">
|
||||||
<div class="hero-content flex-col xl:flex-row-reverse gap-8 xl:gap-12 py-10 px-4"> <!-- 例程轮播容器 -->
|
<div
|
||||||
<div class="w-full flex justify-center" style="min-width: 650px;">
|
class="hero-content flex-col xl:flex-row-reverse gap-8 xl:gap-12 py-10 px-4"
|
||||||
|
>
|
||||||
|
<!-- 例程轮播容器 -->
|
||||||
|
<div class="w-full flex justify-center" style="min-width: 650px">
|
||||||
<TutorialCarousel :autoRotationInterval="3000" />
|
<TutorialCarousel :autoRotationInterval="3000" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 内容容器 -->
|
<!-- 内容容器 -->
|
||||||
<div class="content-container max-w-md lg:max-w-2xl transform transition-all duration-500 ease-in-out">
|
<div
|
||||||
|
class="content-container max-w-md lg:max-w-2xl transform transition-all duration-500 ease-in-out"
|
||||||
|
>
|
||||||
<h1 class="text-4xl md:text-5xl font-bold mb-3 relative group">
|
<h1 class="text-4xl md:text-5xl font-bold mb-3 relative group">
|
||||||
<span class="relative z-10 bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
<span
|
||||||
|
class="relative z-10 bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent"
|
||||||
|
>
|
||||||
Welcome to
|
Welcome to
|
||||||
</span>
|
</span>
|
||||||
<span class="text-base-content">FPGA Web Lab!</span>
|
<span class="text-base-content">FPGA Web Lab!</span>
|
||||||
<span
|
<span
|
||||||
class="absolute bottom-0 left-0 w-0 h-1 bg-primary transition-all duration-500 ease-in-out group-hover:w-3/4"></span>
|
class="absolute bottom-0 left-0 w-0 h-1 bg-primary transition-all duration-500 ease-in-out group-hover:w-3/4"
|
||||||
|
></span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="py-6 text-lg opacity-80 leading-relaxed">
|
<p class="py-6 text-lg opacity-80 leading-relaxed">
|
||||||
|
@ -22,59 +30,63 @@
|
||||||
designs seamlessly.
|
designs seamlessly.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-wrap gap-4 actions-container">
|
<div class="flex flex-wrap gap-4 actions-container">
|
||||||
<router-link to="/project"
|
<router-link
|
||||||
class="btn btn-primary text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1">
|
to="/project"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none"
|
class="btn btn-primary text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1"
|
||||||
stroke="currentColor" stroke-width="2">
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mr-2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
||||||
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
进入工程界面
|
进入工程界面
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link to="/login"
|
<router-link
|
||||||
class="btn btn-secondary text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1">
|
to="/login"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none"
|
class="btn btn-secondary text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1"
|
||||||
stroke="currentColor" stroke-width="2">
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mr-2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||||
</svg>
|
</svg>
|
||||||
登录
|
登录
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link to="/user"
|
<router-link
|
||||||
class="btn btn-accent text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1">
|
to="/user"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none"
|
class="btn btn-accent text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1"
|
||||||
stroke="currentColor" stroke-width="2">
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mr-2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||||
<circle cx="12" cy="7" r="4"></circle>
|
<circle cx="12" cy="7" r="4"></circle>
|
||||||
</svg>
|
</svg>
|
||||||
用户中心
|
用户中心
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/test"
|
|
||||||
class="btn btn-info text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
|
|
||||||
<polyline points="17 21 17 13 7 13 7 21"></polyline>
|
|
||||||
<polyline points="7 3 7 8 15 8"></polyline>
|
|
||||||
</svg>
|
|
||||||
测试功能
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link to="/admin"
|
|
||||||
class="btn btn-error text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5z">
|
|
||||||
</path>
|
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
|
||||||
</svg>
|
|
||||||
管理控制台
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mt-8 p-4 bg-base-300 rounded-lg shadow-inner opacity-80 transition-all duration-300 hover:opacity-100 hover:shadow-md">
|
class="mt-8 p-4 bg-base-300 rounded-lg shadow-inner opacity-80 transition-all duration-300 hover:opacity-100 hover:shadow-md"
|
||||||
|
>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
<span class="font-semibold text-primary">提示:</span>
|
<span class="font-semibold text-primary">提示:</span>
|
||||||
您可以在工程界面中创建、编辑和测试您的FPGA项目,使用我们简洁直观的界面轻松进行硬件设计。
|
您可以在工程界面中创建、编辑和测试您的FPGA项目,使用我们简洁直观的界面轻松进行硬件设计。
|
||||||
|
|
|
@ -1,119 +1,193 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-base-200 min-h-screen p-6">
|
<div class="flex flex-row justify-between items-center">
|
||||||
<div class="flex flex-row justify-between items-center">
|
<h1 class="text-3xl font-bold mb-6">FPGA 设备管理</h1>
|
||||||
<h1 class="text-3xl font-bold mb-6">FPGA 设备管理</h1>
|
<button
|
||||||
<button class="btn btn-ghost text-error hover:underline" @click="
|
class="btn btn-ghost text-error hover:underline"
|
||||||
|
@click="
|
||||||
() => {
|
() => {
|
||||||
isEditMode = !isEditMode;
|
isEditMode = !isEditMode;
|
||||||
}
|
}
|
||||||
">
|
"
|
||||||
编辑
|
>
|
||||||
</button>
|
编辑
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex flex-row justify-between items-center">
|
<div class="flex flex-row justify-between items-center mb-4">
|
||||||
<h2 class="card-title mb-4">IP 地址列表</h2>
|
<h2 class="card-title">IP 地址列表</h2>
|
||||||
<button class="btn btn-ghost" @click="">刷新</button>
|
<button class="btn btn-ghost" @click="refreshData">刷新</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<!-- 搜索和列控制 -->
|
||||||
<table class="table w-full">
|
<div class="flex items-center py-4 gap-4">
|
||||||
<!-- 表头 -->
|
<input
|
||||||
<thead>
|
type="text"
|
||||||
<tr class="bg-base-300">
|
placeholder="筛选 IP 地址..."
|
||||||
<th class="w-50">IP 地址</th>
|
class="input input-bordered max-w-sm"
|
||||||
<th class="w-30">版本号</th>
|
:value="table.getColumn('devAddr')?.getFilterValue() as string"
|
||||||
<th class="w-50">默认启动位流</th>
|
@input="
|
||||||
<th class="w-80">黄金位流</th>
|
table
|
||||||
<th class="w-80">应用位流1</th>
|
.getColumn('devAddr')
|
||||||
<th class="w-80">应用位流2</th>
|
?.setFilterValue(($event.target as HTMLInputElement).value)
|
||||||
<th class="w-80">应用位流3</th>
|
"
|
||||||
<th>操作</th>
|
/>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<!-- 表格内容 -->
|
<div class="dropdown dropdown-end ml-auto">
|
||||||
<tbody>
|
<div tabindex="0" role="button" class="btn btn-outline">
|
||||||
<tr class="hover">
|
列显示
|
||||||
<td class="font-medium">
|
<svg
|
||||||
<input v-if="isEditMode" type="text" placeholder="Type here" class="input m-0" v-model="devAddr" />
|
class="w-4 h-4 ml-2"
|
||||||
<span v-else>{{ devAddr }}</span>
|
fill="none"
|
||||||
</td>
|
stroke="currentColor"
|
||||||
<td>v1.2.3</td>
|
viewBox="0 0 24 24"
|
||||||
<td>
|
>
|
||||||
<select class="select select-bordered w-full max-w-xs" v-model="selectBitstream">
|
<path
|
||||||
<option selected>黄金位流</option>
|
stroke-linecap="round"
|
||||||
<option>应用位流1</option>
|
stroke-linejoin="round"
|
||||||
<option>应用位流2</option>
|
stroke-width="2"
|
||||||
<option>应用位流3</option>
|
d="m19 9-7 7-7-7"
|
||||||
</select>
|
></path>
|
||||||
</td>
|
</svg>
|
||||||
<!-- 黄金位流上传区 -->
|
</div>
|
||||||
<td>
|
<ul
|
||||||
<div class="flex flex-col items-center gap-2">
|
tabindex="0"
|
||||||
<input type="file" class="file-input file-input-primary" @change="handleFileChange($event, 0)" />
|
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52"
|
||||||
</div>
|
>
|
||||||
</td>
|
<li
|
||||||
<!-- 应用位流1上传区 -->
|
v-for="column in table
|
||||||
<td>
|
.getAllColumns()
|
||||||
<div class="flex flex-col items-center gap-2">
|
.filter((column) => column.getCanHide())"
|
||||||
<input type="file" class="file-input file-input-secondary" @change="handleFileChange($event, 1)" />
|
:key="column.id"
|
||||||
</div>
|
>
|
||||||
</td>
|
<label class="label cursor-pointer">
|
||||||
<!-- 应用位流2上传区 -->
|
<span class="label-text capitalize">{{ column.id }}</span>
|
||||||
<td>
|
<input
|
||||||
<div class="flex flex-col items-center gap-2">
|
type="checkbox"
|
||||||
<input type="file" class="file-input file-input-accent" @change="handleFileChange($event, 2)" />
|
class="checkbox checkbox-sm"
|
||||||
</div>
|
:checked="column.getIsVisible()"
|
||||||
</td>
|
@change="
|
||||||
<!-- 应用位流3上传区 -->
|
column.toggleVisibility(
|
||||||
<td>
|
!!($event.target as HTMLInputElement).checked,
|
||||||
<div class="flex flex-col items-center gap-2">
|
|
||||||
<input type="file" class="file-input file-input-info" @change="handleFileChange($event, 3)" />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<td class="flex gap-2">
|
|
||||||
<button class="btn grow btn-warning" @click="
|
|
||||||
uploadAndDownloadBitstreams(
|
|
||||||
devAddr,
|
|
||||||
goldBitstreamFile,
|
|
||||||
appBitstream1File,
|
|
||||||
appBitstream2File,
|
|
||||||
appBitstream3File,
|
|
||||||
)
|
)
|
||||||
">
|
"
|
||||||
固化
|
/>
|
||||||
</button>
|
</label>
|
||||||
<button class="btn grow btn-success" @click="
|
</li>
|
||||||
hotresetBitstream(devAddr, getSelectedBitstreamNum())
|
</ul>
|
||||||
">
|
|
||||||
切换并热启动
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 bg-base-300 p-4 rounded-lg">
|
<!-- 表格 -->
|
||||||
<p class="text-sm opacity-80">
|
<div class="overflow-x-auto border border-base-300 rounded-lg">
|
||||||
<span class="font-semibold text-error">提示:</span>
|
<table class="table w-full">
|
||||||
请谨慎操作FPGA固化和热启动功能,确保上传的位流文件无误,以避免设备损坏。
|
<thead>
|
||||||
</p>
|
<tr
|
||||||
|
v-for="headerGroup in table.getHeaderGroups()"
|
||||||
|
:key="headerGroup.id"
|
||||||
|
class="bg-base-300"
|
||||||
|
>
|
||||||
|
<th v-for="header in headerGroup.headers" :key="header.id">
|
||||||
|
<FlexRender
|
||||||
|
v-if="!header.isPlaceholder"
|
||||||
|
:render="header.column.columnDef.header"
|
||||||
|
:props="header.getContext()"
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
|
<tr
|
||||||
|
class="hover"
|
||||||
|
:class="{ 'bg-primary/10': row.getIsSelected() }"
|
||||||
|
>
|
||||||
|
<td v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
|
<FlexRender
|
||||||
|
:render="cell.column.columnDef.cell"
|
||||||
|
:props="cell.getContext()"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="row.getIsExpanded()">
|
||||||
|
<td :colspan="row.getAllCells().length" class="bg-base-200">
|
||||||
|
<div class="p-4">
|
||||||
|
<pre class="text-sm">{{
|
||||||
|
JSON.stringify(row.original, null, 2)
|
||||||
|
}}</pre>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<tr v-else>
|
||||||
|
<td
|
||||||
|
:colspan="columns.length"
|
||||||
|
class="h-24 text-center text-base-content/60"
|
||||||
|
>
|
||||||
|
暂无数据
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页控制 -->
|
||||||
|
<div class="flex items-center justify-between py-4">
|
||||||
|
<div class="text-sm text-base-content/60">
|
||||||
|
已选择 {{ table.getFilteredSelectedRowModel().rows.length }} /
|
||||||
|
{{ table.getFilteredRowModel().rows.length }} 行
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
:disabled="!table.getCanPreviousPage()"
|
||||||
|
@click="table.previousPage()"
|
||||||
|
>
|
||||||
|
上一页
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
:disabled="!table.getCanNextPage()"
|
||||||
|
@click="table.nextPage()"
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 bg-base-300 p-4 rounded-lg">
|
||||||
|
<p class="text-sm opacity-80">
|
||||||
|
<span class="font-semibold text-error">提示:</span>
|
||||||
|
请谨慎操作FPGA固化和热启动功能,确保上传的位流文件无误,以避免设备损坏。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
} from "@tanstack/vue-table";
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from "@tanstack/vue-table";
|
||||||
import { isNull, isUndefined } from "lodash";
|
import { isNull, isUndefined } from "lodash";
|
||||||
import { ref } from "vue";
|
import { h, ref } from "vue";
|
||||||
import { RemoteUpdateClient } from "@/APIClient";
|
import { RemoteUpdateClient } from "@/APIClient";
|
||||||
import { useDialogStore } from "@/stores/dialog";
|
import { useDialogStore } from "@/stores/dialog";
|
||||||
import { Common } from "@/utils/Common";
|
import { Common } from "@/utils/Common";
|
||||||
|
|
||||||
|
@ -122,52 +196,285 @@ const dialog = useDialogStore();
|
||||||
// 编辑状态
|
// 编辑状态
|
||||||
const isEditMode = ref(false);
|
const isEditMode = ref(false);
|
||||||
|
|
||||||
// 选择热切换的比特流
|
|
||||||
const selectBitstream = ref("黄金位流");
|
|
||||||
|
|
||||||
// 存储上传文件的信息
|
|
||||||
const goldBitstreamFile = ref<File>();
|
|
||||||
const appBitstream1File = ref<File>();
|
|
||||||
const appBitstream2File = ref<File>();
|
|
||||||
const appBitstream3File = ref<File>();
|
|
||||||
|
|
||||||
// 远程升级相关参数
|
// 远程升级相关参数
|
||||||
const devAddr = ref("192.168.1.100");
|
|
||||||
const devPort = 1234;
|
const devPort = 1234;
|
||||||
const remoteUpdater = new RemoteUpdateClient();
|
const remoteUpdater = new RemoteUpdateClient();
|
||||||
|
|
||||||
|
// 设备数据接口
|
||||||
|
export interface DeviceData {
|
||||||
|
id: string;
|
||||||
|
devAddr: string;
|
||||||
|
version: string;
|
||||||
|
defaultBitstream: string;
|
||||||
|
goldBitstreamFile?: File;
|
||||||
|
appBitstream1File?: File;
|
||||||
|
appBitstream2File?: File;
|
||||||
|
appBitstream3File?: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟数据,实际应该从API获取
|
||||||
|
const data = ref<DeviceData[]>([
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
devAddr: "192.168.1.100",
|
||||||
|
version: "v1.2.3",
|
||||||
|
defaultBitstream: "黄金位流",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns: ColumnDef<DeviceData>[] = [
|
||||||
|
{
|
||||||
|
id: "select",
|
||||||
|
header: ({ table }) =>
|
||||||
|
h("input", {
|
||||||
|
type: "checkbox",
|
||||||
|
class: "checkbox",
|
||||||
|
checked:
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() ? "indeterminate" : false),
|
||||||
|
onChange: (event: Event) =>
|
||||||
|
table.toggleAllPageRowsSelected(
|
||||||
|
!!(event.target as HTMLInputElement).checked,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
cell: ({ row }) =>
|
||||||
|
h("input", {
|
||||||
|
type: "checkbox",
|
||||||
|
class: "checkbox",
|
||||||
|
checked: row.getIsSelected(),
|
||||||
|
onChange: (event: Event) =>
|
||||||
|
row.toggleSelected(!!(event.target as HTMLInputElement).checked),
|
||||||
|
}),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "devAddr",
|
||||||
|
header: "IP 地址",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const device = row.original;
|
||||||
|
return isEditMode.value
|
||||||
|
? h("input", {
|
||||||
|
type: "text",
|
||||||
|
class: "input input-sm w-full",
|
||||||
|
value: device.devAddr,
|
||||||
|
onInput: (e: Event) => {
|
||||||
|
device.devAddr = (e.target as HTMLInputElement).value;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: h("span", { class: "font-medium" }, device.devAddr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "version",
|
||||||
|
header: "版本号",
|
||||||
|
cell: ({ row }) => row.getValue("version"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "defaultBitstream",
|
||||||
|
header: "默认启动位流",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const device = row.original;
|
||||||
|
return h(
|
||||||
|
"select",
|
||||||
|
{
|
||||||
|
class: "select select-bordered select-sm w-full",
|
||||||
|
value: device.defaultBitstream,
|
||||||
|
onChange: (e: Event) => {
|
||||||
|
device.defaultBitstream = (e.target as HTMLSelectElement).value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h("option", { value: "黄金位流" }, "黄金位流"),
|
||||||
|
h("option", { value: "应用位流1" }, "应用位流1"),
|
||||||
|
h("option", { value: "应用位流2" }, "应用位流2"),
|
||||||
|
h("option", { value: "应用位流3" }, "应用位流3"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "goldBitstream",
|
||||||
|
header: "黄金位流",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const device = row.original;
|
||||||
|
return h("input", {
|
||||||
|
type: "file",
|
||||||
|
class: "file-input file-input-primary file-input-sm",
|
||||||
|
onChange: (e: Event) =>
|
||||||
|
handleFileChange(e, device, "goldBitstreamFile"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "appBitstream1",
|
||||||
|
header: "应用位流1",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const device = row.original;
|
||||||
|
return h("input", {
|
||||||
|
type: "file",
|
||||||
|
class: "file-input file-input-secondary file-input-sm",
|
||||||
|
onChange: (e: Event) =>
|
||||||
|
handleFileChange(e, device, "appBitstream1File"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "appBitstream2",
|
||||||
|
header: "应用位流2",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const device = row.original;
|
||||||
|
return h("input", {
|
||||||
|
type: "file",
|
||||||
|
class: "file-input file-input-accent file-input-sm",
|
||||||
|
onChange: (e: Event) =>
|
||||||
|
handleFileChange(e, device, "appBitstream2File"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "appBitstream3",
|
||||||
|
header: "应用位流3",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const device = row.original;
|
||||||
|
return h("input", {
|
||||||
|
type: "file",
|
||||||
|
class: "file-input file-input-info file-input-sm",
|
||||||
|
onChange: (e: Event) =>
|
||||||
|
handleFileChange(e, device, "appBitstream3File"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
header: "操作",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const device = row.original;
|
||||||
|
return h("div", { class: "flex gap-2 min-w-30" }, [
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "btn btn-warning btn-sm",
|
||||||
|
onClick: () =>
|
||||||
|
uploadAndDownloadBitstreams(
|
||||||
|
device.devAddr,
|
||||||
|
device.goldBitstreamFile,
|
||||||
|
device.appBitstream1File,
|
||||||
|
device.appBitstream2File,
|
||||||
|
device.appBitstream3File,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"固化",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "btn btn-success btn-sm",
|
||||||
|
onClick: () =>
|
||||||
|
hotresetBitstream(
|
||||||
|
device.devAddr,
|
||||||
|
getSelectedBitstreamNum(device.defaultBitstream),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"热启动",
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// TanStack Table 状态
|
||||||
|
const sorting = ref<SortingState>([]);
|
||||||
|
const columnFilters = ref<ColumnFiltersState>([]);
|
||||||
|
const columnVisibility = ref<VisibilityState>({});
|
||||||
|
const rowSelection = ref({});
|
||||||
|
const expanded = ref<ExpandedState>({});
|
||||||
|
|
||||||
|
// 创建表格实例
|
||||||
|
const table = useVueTable({
|
||||||
|
get data() {
|
||||||
|
return data.value;
|
||||||
|
},
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
|
onSortingChange: (updaterOrValue) => {
|
||||||
|
if (typeof updaterOrValue === "function") {
|
||||||
|
sorting.value = updaterOrValue(sorting.value);
|
||||||
|
} else {
|
||||||
|
sorting.value = updaterOrValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onColumnFiltersChange: (updaterOrValue) => {
|
||||||
|
if (typeof updaterOrValue === "function") {
|
||||||
|
columnFilters.value = updaterOrValue(columnFilters.value);
|
||||||
|
} else {
|
||||||
|
columnFilters.value = updaterOrValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onColumnVisibilityChange: (updaterOrValue) => {
|
||||||
|
if (typeof updaterOrValue === "function") {
|
||||||
|
columnVisibility.value = updaterOrValue(columnVisibility.value);
|
||||||
|
} else {
|
||||||
|
columnVisibility.value = updaterOrValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRowSelectionChange: (updaterOrValue) => {
|
||||||
|
if (typeof updaterOrValue === "function") {
|
||||||
|
rowSelection.value = updaterOrValue(rowSelection.value);
|
||||||
|
} else {
|
||||||
|
rowSelection.value = updaterOrValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onExpandedChange: (updaterOrValue) => {
|
||||||
|
if (typeof updaterOrValue === "function") {
|
||||||
|
expanded.value = updaterOrValue(expanded.value);
|
||||||
|
} else {
|
||||||
|
expanded.value = updaterOrValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
get sorting() {
|
||||||
|
return sorting.value;
|
||||||
|
},
|
||||||
|
get columnFilters() {
|
||||||
|
return columnFilters.value;
|
||||||
|
},
|
||||||
|
get columnVisibility() {
|
||||||
|
return columnVisibility.value;
|
||||||
|
},
|
||||||
|
get rowSelection() {
|
||||||
|
return rowSelection.value;
|
||||||
|
},
|
||||||
|
get expanded() {
|
||||||
|
return expanded.value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 处理文件上传
|
// 处理文件上传
|
||||||
function handleFileChange(event: Event, bistreamNum: number) {
|
function handleFileChange(
|
||||||
|
event: Event,
|
||||||
|
device: DeviceData,
|
||||||
|
fileKey: keyof DeviceData,
|
||||||
|
) {
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
const file = target.files?.[0]; // 获取选中的第一个文件
|
const file = target.files?.[0];
|
||||||
|
if (file) {
|
||||||
if (!file) {
|
(device as any)[fileKey] = file;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bistreamNum === 0) {
|
|
||||||
goldBitstreamFile.value = file;
|
|
||||||
} else if (bistreamNum === 1) {
|
|
||||||
appBitstream1File.value = file;
|
|
||||||
} else if (bistreamNum === 2) {
|
|
||||||
appBitstream2File.value = file;
|
|
||||||
} else if (bistreamNum === 3) {
|
|
||||||
appBitstream3File.value = file;
|
|
||||||
} else {
|
|
||||||
goldBitstreamFile.value = file;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedBitstreamNum(): number {
|
function getSelectedBitstreamNum(bitstreamName: string): number {
|
||||||
if (selectBitstream.value == "黄金位流") {
|
if (bitstreamName === "黄金位流") return 0;
|
||||||
return 0;
|
if (bitstreamName === "应用位流1") return 1;
|
||||||
} else if (selectBitstream.value == "应用位流1") {
|
if (bitstreamName === "应用位流2") return 2;
|
||||||
return 1;
|
if (bitstreamName === "应用位流3") return 3;
|
||||||
} else if (selectBitstream.value == "应用位流2") {
|
|
||||||
return 2;
|
|
||||||
} else if (selectBitstream.value == "应用位流3") {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +513,7 @@ async function uploadAndDownloadBitstreams(
|
||||||
const ret = await remoteUpdater.downloadMultiBitstreams(
|
const ret = await remoteUpdater.downloadMultiBitstreams(
|
||||||
devAddr,
|
devAddr,
|
||||||
devPort,
|
devPort,
|
||||||
getSelectedBitstreamNum(),
|
getSelectedBitstreamNum(data.value[0].defaultBitstream),
|
||||||
);
|
);
|
||||||
if (ret != cnt) {
|
if (ret != cnt) {
|
||||||
dialog.warn("固化比特流出错");
|
dialog.warn("固化比特流出错");
|
||||||
|
@ -240,7 +547,14 @@ async function hotresetBitstream(devAddr: string, bitstreamNum: number) {
|
||||||
|
|
||||||
async function refreshData() {
|
async function refreshData() {
|
||||||
try {
|
try {
|
||||||
const ret = await remoteUpdater.getFirmwareVersion(devAddr.value, devPort);
|
const ret = await remoteUpdater.getFirmwareVersion(
|
||||||
|
data.value[0].devAddr,
|
||||||
|
devPort,
|
||||||
|
);
|
||||||
|
// 更新版本信息
|
||||||
|
if (ret) {
|
||||||
|
data.value[0].version = String(ret);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.error("获取数据失败");
|
dialog.error("获取数据失败");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -9,25 +9,40 @@
|
||||||
<li id="2" @click="setActivePage">
|
<li id="2" @click="setActivePage">
|
||||||
<a :class="{ 'menu-active': activePage === 2 }">Item 2</a>
|
<a :class="{ 'menu-active': activePage === 2 }">Item 2</a>
|
||||||
</li>
|
</li>
|
||||||
<li id="100" @click="setActivePage">
|
<li v-if="isAdmin" id="100" @click="setActivePage">
|
||||||
<a :class="{ 'menu-active': activePage === 100 }">实验板控制台</a>
|
<a :class="{ 'menu-active': activePage === 100 }">实验板控制台</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="divider divider-horizontal h-full"></div>
|
<div class="divider divider-horizontal h-full"></div>
|
||||||
<div class="card bg-base-200 w-300"></div>
|
<div class="card bg-base-200 w-300 rounded-2xl p-7">
|
||||||
|
<div v-if="activePage === 1">
|
||||||
|
<h2 class="card-title">用户信息</h2>
|
||||||
|
<p>这里是用户信息页面的内容。</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="activePage === 100">
|
||||||
|
<BoardControl />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import BoardControl from "./BoardControl.vue";
|
||||||
import { toNumber } from "lodash";
|
import { toNumber } from "lodash";
|
||||||
import { ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
|
import { AuthManager } from "@/utils/AuthManager";
|
||||||
|
|
||||||
const activePage = ref(1);
|
const activePage = ref(1);
|
||||||
|
const isAdmin = ref(false);
|
||||||
|
|
||||||
function setActivePage(event: Event) {
|
function setActivePage(event: Event) {
|
||||||
const target = event.currentTarget as HTMLLinkElement;
|
const target = event.currentTarget as HTMLLinkElement;
|
||||||
activePage.value = toNumber(target.id);
|
activePage.value = toNumber(target.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async ()=>{
|
||||||
|
isAdmin.value = await AuthManager.verifyAdminAuth();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
Loading…
Reference in New Issue