Merge remote-tracking branch 'origin/main'
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,7 +16,7 @@ build/
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
logs
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
|
||||
@@ -70,6 +70,41 @@ CREATE TABLE `qc_photo` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='QC 测试照片表';
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 固件表
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE `app_aat_version` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`firmware_type` VARCHAR(50) NOT NULL COMMENT '固件类型: lb_main / lb_signal_master / lbPro_main / lbPro_signal_master / lbPro_screen_ui',
|
||||
`firmware_name` VARCHAR(100) NOT NULL COMMENT '固件文件名',
|
||||
`firmware_version` VARCHAR(20) NOT NULL COMMENT '固件版本号',
|
||||
`min_hardware_version` VARCHAR(20) NOT NULL DEFAULT '0.0' COMMENT '最低硬件版本',
|
||||
`download_url` VARCHAR(500) DEFAULT NULL COMMENT '固件下载地址',
|
||||
`firmware_size` BIGINT DEFAULT NULL COMMENT '固件大小(字节)',
|
||||
`firmware_description` TEXT DEFAULT NULL COMMENT '固件描述',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_type_version` (`firmware_type`, `firmware_version`),
|
||||
KEY `idx_firmware_type` (`firmware_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AAT固件版本表';
|
||||
|
||||
-- 插入固件数据
|
||||
INSERT INTO `app_aat_version` (`firmware_type`, `firmware_name`, `firmware_version`, `min_hardware_version`)
|
||||
VALUES
|
||||
-- lb_main
|
||||
('lb_main', 'lb_main_2.1.bin', '2.1', '1.0'),
|
||||
-- lb_signal_master
|
||||
('lb_signal_master', 'lbsm_1.1.bin', '1.1', '0.0'),
|
||||
-- lbPro_main
|
||||
('lbPro_main', 'lbPro_main_2.3.bin', '2.3', '0.0'),
|
||||
-- lbPro_signal_master
|
||||
('lbPro_signal_master', 'lbPro_sm_1.4.bin', '1.4', '0.0'),
|
||||
-- lbPro_screen_ui
|
||||
('lbPro_screen_ui', 'lbPro_ui_2.7.tft', '2.7', '0.0');
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 示例数据(可选)
|
||||
-- ============================================================
|
||||
|
||||
@@ -40,12 +40,14 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
||||
.excludePathPatterns("/privacy_policy", "/privacy_policy/**")
|
||||
// 排除固件查询接口(不需要登录)
|
||||
.excludePathPatterns("/firmware/**")
|
||||
// 排除AAT固件版本接口
|
||||
.excludePathPatterns("/aat_version/**")
|
||||
// 排除公共固件
|
||||
.excludePathPatterns("/public_firmware/**")
|
||||
// 排除系统登录页(不需要登录)
|
||||
// .excludePathPatterns("/loading.html", "/admin/login.html")
|
||||
// 排除静态资源
|
||||
.excludePathPatterns("/", "/*.css", "/*.js", "/*.ico", "/static/**", "/assets/**")
|
||||
.excludePathPatterns("/", "/*.html", "/*.css", "/*.js", "/*.ico", "/static/**", "/assets/**")
|
||||
// 排除接口静态资源
|
||||
.excludePathPatterns("/doc.html", "/webjars/**")
|
||||
.excludePathPatterns("/v3/api-docs/swagger-config", "/v3/api-docs/**", "/swagger-resources")
|
||||
|
||||
74
src/main/java/com/corewing/app/entity/AatVersion.java
Normal file
74
src/main/java/com/corewing/app/entity/AatVersion.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package com.corewing.app.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* AAT固件版本实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("app_aat_version")
|
||||
public class AatVersion implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 固件类型: lb_main / lb_signal_master / lbPro_main / lbPro_signal_master / lbPro_screen_ui
|
||||
*/
|
||||
private String firmwareType;
|
||||
|
||||
/**
|
||||
* 固件文件名
|
||||
*/
|
||||
private String firmwareName;
|
||||
|
||||
/**
|
||||
* 固件版本号
|
||||
*/
|
||||
private String firmwareVersion;
|
||||
|
||||
/**
|
||||
* 最低硬件版本
|
||||
*/
|
||||
private String minHardwareVersion;
|
||||
|
||||
/**
|
||||
* 固件下载地址
|
||||
*/
|
||||
private String downloadUrl;
|
||||
|
||||
/**
|
||||
* 固件大小(字节)
|
||||
*/
|
||||
private Long firmwareSize;
|
||||
|
||||
/**
|
||||
* 固件描述
|
||||
*/
|
||||
private String firmwareDescription;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -57,6 +57,11 @@ public class CustomMetaObjectHandler implements MetaObjectHandler {
|
||||
// 兼容 Date 和 LocalDateTime 类型的 createTime 填充
|
||||
fillTimeField(metaObject, CREATE_TIME);
|
||||
} catch (ReflectionException ignored) { }
|
||||
|
||||
try {
|
||||
// 兼容 Date 和 LocalDateTime 类型的 updateTime 填充
|
||||
fillTimeField(metaObject, UPDATE_TIME);
|
||||
} catch (ReflectionException ignored) { }
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
13
src/main/java/com/corewing/app/mapper/AatVersionMapper.java
Normal file
13
src/main/java/com/corewing/app/mapper/AatVersionMapper.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.corewing.app.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.corewing.app.entity.AatVersion;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* AAT固件版本 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface AatVersionMapper extends BaseMapper<AatVersion> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.common.annotation.CommonLog;
|
||||
import com.corewing.app.entity.AatVersion;
|
||||
import com.corewing.app.service.AatVersionService;
|
||||
import com.corewing.app.util.I18nUtil;
|
||||
import com.corewing.app.util.OSSUploadUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AAT固件版本 Controller
|
||||
*/
|
||||
@Api(tags = "AAT固件版本接口")
|
||||
@RestController
|
||||
@RequestMapping("/aat_version")
|
||||
public class AppAatVersionController {
|
||||
|
||||
private final AatVersionService aatVersionService;
|
||||
|
||||
public AppAatVersionController(AatVersionService aatVersionService) {
|
||||
this.aatVersionService = aatVersionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有类型的最新固件版本(返回与原始JSON结构一致)
|
||||
*/
|
||||
@CommonLog("查询所有AAT最新固件版本")
|
||||
@ApiOperation("查询所有AAT最新固件版本")
|
||||
@GetMapping("/latest")
|
||||
public Result<Map<String, Object>> getAllLatestVersions() {
|
||||
return Result.success(aatVersionService.getAllLatestVersions());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据固件类型查询最新版本
|
||||
*/
|
||||
@CommonLog("根据类型查询AAT最新固件版本")
|
||||
@ApiOperation("根据类型查询AAT最新固件版本")
|
||||
@GetMapping("/latest/{firmwareType}")
|
||||
public Result<AatVersion> getLatestByType(@PathVariable String firmwareType) {
|
||||
AatVersion version = aatVersionService.getLatestByType(firmwareType);
|
||||
if (version != null) {
|
||||
return Result.success(version);
|
||||
}
|
||||
return Result.error(I18nUtil.getMessage("firmware.not.found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据固件类型查询版本列表
|
||||
*/
|
||||
@CommonLog("根据类型查询AAT固件版本列表")
|
||||
@ApiOperation("根据类型查询AAT固件版本列表")
|
||||
@GetMapping("/type/{firmwareType}")
|
||||
public Result<List<AatVersion>> listByType(@PathVariable String firmwareType) {
|
||||
QueryWrapper<AatVersion> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("firmware_type", firmwareType);
|
||||
wrapper.orderByDesc("create_time");
|
||||
return Result.success(aatVersionService.list(wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询固件版本列表
|
||||
*/
|
||||
@CommonLog("分页查询AAT固件版本列表")
|
||||
@ApiOperation("分页查询AAT固件版本列表")
|
||||
@GetMapping("/page")
|
||||
public Result<IPage<AatVersion>> page(
|
||||
@RequestParam(defaultValue = "1") Long current,
|
||||
@RequestParam(defaultValue = "10") Long size,
|
||||
@RequestParam(required = false) String firmwareType,
|
||||
@RequestParam(required = false) String firmwareName) {
|
||||
|
||||
Page<AatVersion> page = new Page<>(current, size);
|
||||
QueryWrapper<AatVersion> wrapper = new QueryWrapper<>();
|
||||
|
||||
if (StringUtils.hasText(firmwareType)) {
|
||||
wrapper.eq("firmware_type", firmwareType);
|
||||
}
|
||||
if (StringUtils.hasText(firmwareName)) {
|
||||
wrapper.like("firmware_name", firmwareName);
|
||||
}
|
||||
wrapper.orderByDesc("create_time");
|
||||
|
||||
return Result.success(aatVersionService.page(page, wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有固件版本
|
||||
*/
|
||||
@CommonLog("查询所有AAT固件版本")
|
||||
@ApiOperation("查询所有AAT固件版本")
|
||||
@GetMapping("/list")
|
||||
public Result<List<AatVersion>> list() {
|
||||
return Result.success(aatVersionService.list());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询固件版本
|
||||
*/
|
||||
@CommonLog("根据ID查询AAT固件版本")
|
||||
@ApiOperation("根据ID查询AAT固件版本")
|
||||
@GetMapping("/{id}")
|
||||
public Result<AatVersion> getById(@PathVariable Long id) {
|
||||
AatVersion version = aatVersionService.getById(id);
|
||||
if (version != null) {
|
||||
return Result.success(version);
|
||||
}
|
||||
return Result.error(I18nUtil.getMessage("firmware.not.found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增固件版本
|
||||
*/
|
||||
@CommonLog("新增AAT固件版本")
|
||||
@ApiOperation("新增AAT固件版本")
|
||||
@PostMapping
|
||||
public Result<AatVersion> add(@RequestBody AatVersion aatVersion) {
|
||||
boolean saved = aatVersionService.save(aatVersion);
|
||||
return saved ? Result.success(aatVersion) : Result.error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改固件版本
|
||||
*/
|
||||
@CommonLog("修改AAT固件版本")
|
||||
@ApiOperation("修改AAT固件版本")
|
||||
@PutMapping
|
||||
public Result<Void> update(@RequestBody AatVersion aatVersion) {
|
||||
return Result.isBool(aatVersionService.updateById(aatVersion));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除固件版本
|
||||
*/
|
||||
@CommonLog("删除AAT固件版本")
|
||||
@ApiOperation("删除AAT固件版本")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
AatVersion version = aatVersionService.getById(id);
|
||||
if (version == null) {
|
||||
return Result.error(I18nUtil.getMessage("firmware.not.found"));
|
||||
}
|
||||
// 删除OSS上的文件
|
||||
if (StringUtils.hasText(version.getDownloadUrl())) {
|
||||
String objectName = version.getDownloadUrl().replace("https://oss.corewing.com/", "");
|
||||
OSSUploadUtil.deleteFile(objectName);
|
||||
}
|
||||
return Result.isBool(aatVersionService.removeById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传固件文件
|
||||
*/
|
||||
@CommonLog("上传AAT固件文件")
|
||||
@ApiOperation("上传AAT固件文件")
|
||||
@PostMapping("/upload/{id}")
|
||||
public Result<AatVersion> upload(@PathVariable Long id, @RequestParam("file") MultipartFile file) {
|
||||
AatVersion version = aatVersionService.getById(id);
|
||||
if (version == null) {
|
||||
return Result.error(I18nUtil.getMessage("firmware.not.found"));
|
||||
}
|
||||
try {
|
||||
String objectName = "aat_firmware/" + file.getOriginalFilename();
|
||||
String downloadUrl = OSSUploadUtil.uploadFile(file.getInputStream(), objectName);
|
||||
version.setDownloadUrl(downloadUrl);
|
||||
version.setFirmwareSize(file.getSize());
|
||||
version.setFirmwareName(file.getOriginalFilename());
|
||||
aatVersionService.updateById(version);
|
||||
return Result.success(version);
|
||||
} catch (IOException e) {
|
||||
return Result.error("上传失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.entity.AatVersion;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AAT固件版本 Service 接口
|
||||
*/
|
||||
public interface AatVersionService extends IService<AatVersion> {
|
||||
|
||||
/**
|
||||
* 根据固件类型查询最新版本
|
||||
*
|
||||
* @param firmwareType 固件类型
|
||||
* @return 最新固件版本
|
||||
*/
|
||||
AatVersion getLatestByType(String firmwareType);
|
||||
|
||||
/**
|
||||
* 查询所有类型的最新固件版本(返回与原始JSON结构一致的格式)
|
||||
*
|
||||
* @return 所有类型的最新版本映射
|
||||
*/
|
||||
Map<String, Object> getAllLatestVersions();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.entity.AatVersion;
|
||||
import com.corewing.app.mapper.AatVersionMapper;
|
||||
import com.corewing.app.service.AatVersionService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AAT固件版本 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class AatVersionServiceImpl extends ServiceImpl<AatVersionMapper, AatVersion> implements AatVersionService {
|
||||
|
||||
@Override
|
||||
public AatVersion getLatestByType(String firmwareType) {
|
||||
LambdaQueryWrapper<AatVersion> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(AatVersion::getFirmwareType, firmwareType);
|
||||
wrapper.orderByDesc(AatVersion::getCreateTime);
|
||||
wrapper.last("LIMIT 1");
|
||||
return getOne(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAllLatestVersions() {
|
||||
// 查询所有固件,按类型分组,每组取最新的一条
|
||||
List<AatVersion> allVersions = list(
|
||||
new LambdaQueryWrapper<AatVersion>().orderByDesc(AatVersion::getCreateTime)
|
||||
);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
Map<String, List<AatVersion>> grouped = allVersions.stream()
|
||||
.collect(Collectors.groupingBy(AatVersion::getFirmwareType, LinkedHashMap::new, Collectors.toList()));
|
||||
|
||||
for (Map.Entry<String, List<AatVersion>> entry : grouped.entrySet()) {
|
||||
Map<String, Object> typeInfo = new HashMap<>();
|
||||
List<Map<String, String>> firmwares = new ArrayList<>();
|
||||
// 取该类型下最新的一条
|
||||
AatVersion latest = entry.getValue().get(0);
|
||||
Map<String, String> firmwareInfo = new HashMap<>();
|
||||
firmwareInfo.put("firmware_name", latest.getFirmwareName());
|
||||
firmwareInfo.put("firmware_version", latest.getFirmwareVersion());
|
||||
firmwareInfo.put("min_hardware_version", latest.getMinHardwareVersion());
|
||||
firmwares.add(firmwareInfo);
|
||||
typeInfo.put("firmwares", firmwares);
|
||||
result.put(entry.getKey(), typeInfo);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
295
src/main/resources/static/aat_version.html
Normal file
295
src/main/resources/static/aat_version.html
Normal file
@@ -0,0 +1,295 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AAT 固件版本管理</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f5f7fa; color: #333; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 22px; margin-bottom: 20px; color: #1a1a1a; }
|
||||
.toolbar { display: flex; gap: 10px; margin-bottom: 16px; align-items: center; flex-wrap: wrap; }
|
||||
.toolbar select, .toolbar input { padding: 8px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; }
|
||||
.btn { padding: 8px 16px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all .2s; }
|
||||
.btn-primary { background: #1677ff; color: #fff; }
|
||||
.btn-primary:hover { background: #4096ff; }
|
||||
.btn-danger { background: #ff4d4f; color: #fff; }
|
||||
.btn-danger:hover { background: #ff7875; }
|
||||
.btn-success { background: #52c41a; color: #fff; }
|
||||
.btn-success:hover { background: #73d13d; }
|
||||
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
|
||||
th, td { padding: 12px 14px; text-align: left; border-bottom: 1px solid #f0f0f0; font-size: 14px; }
|
||||
th { background: #fafafa; font-weight: 600; color: #666; }
|
||||
tr:hover { background: #fafafa; }
|
||||
.empty { text-align: center; padding: 40px; color: #999; }
|
||||
|
||||
/* Modal */
|
||||
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.45); z-index: 1000; justify-content: center; align-items: center; }
|
||||
.modal-overlay.active { display: flex; }
|
||||
.modal { background: #fff; border-radius: 10px; padding: 24px; width: 480px; max-width: 90vw; }
|
||||
.modal h2 { font-size: 18px; margin-bottom: 20px; }
|
||||
.form-group { margin-bottom: 14px; }
|
||||
.form-group label { display: block; font-size: 13px; color: #666; margin-bottom: 4px; }
|
||||
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 8px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; }
|
||||
.form-group textarea { height: 60px; resize: vertical; }
|
||||
.modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
||||
.btn-cancel { background: #f0f0f0; color: #333; }
|
||||
.btn-cancel:hover { background: #d9d9d9; }
|
||||
|
||||
.file-upload { position: relative; }
|
||||
.file-upload input[type="file"] { position: absolute; left: 0; top: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
|
||||
.file-info { font-size: 12px; color: #999; margin-top: 4px; }
|
||||
.url-link { color: #1677ff; text-decoration: none; word-break: break-all; }
|
||||
.url-link:hover { text-decoration: underline; }
|
||||
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; background: #e6f4ff; color: #1677ff; }
|
||||
.size { color: #999; font-size: 13px; }
|
||||
|
||||
.toast { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); padding: 10px 24px; border-radius: 8px; color: #fff; font-size: 14px; z-index: 2000; transition: opacity .3s; }
|
||||
.toast-success { background: #52c41a; }
|
||||
.toast-error { background: #ff4d4f; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>AAT 固件版本管理</h1>
|
||||
|
||||
<div class="toolbar">
|
||||
<select id="filterType">
|
||||
<option value="">全部类型</option>
|
||||
<option value="lb_main">lb_main</option>
|
||||
<option value="lb_signal_master">lb_signal_master</option>
|
||||
<option value="lbPro_main">lbPro_main</option>
|
||||
<option value="lbPro_signal_master">lbPro_signal_master</option>
|
||||
<option value="lbPro_screen_ui">lbPro_screen_ui</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="openAddModal()">+ 新增固件</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>固件类型</th>
|
||||
<th>固件名称</th>
|
||||
<th>版本号</th>
|
||||
<th>最低硬件版本</th>
|
||||
<th>文件大小</th>
|
||||
<th>下载地址</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody">
|
||||
<tr><td colspan="8" class="empty">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑 Modal -->
|
||||
<div class="modal-overlay" id="formModal">
|
||||
<div class="modal">
|
||||
<h2 id="modalTitle">新增固件</h2>
|
||||
<input type="hidden" id="formId">
|
||||
<div class="form-group">
|
||||
<label>固件类型</label>
|
||||
<select id="formType">
|
||||
<option value="lb_main">lb_main</option>
|
||||
<option value="lb_signal_master">lb_signal_master</option>
|
||||
<option value="lbPro_main">lbPro_main</option>
|
||||
<option value="lbPro_signal_master">lbPro_signal_master</option>
|
||||
<option value="lbPro_screen_ui">lbPro_screen_ui</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>固件名称</label>
|
||||
<input type="text" id="formName" placeholder="如 lb_main_2.1.bin">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>固件版本号</label>
|
||||
<input type="text" id="formVersion" placeholder="如 2.1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>最低硬件版本</label>
|
||||
<input type="text" id="formMinHw" placeholder="如 1.0" value="0.0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>固件描述</label>
|
||||
<textarea id="formDesc" placeholder="可选"></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-cancel" onclick="closeModal()">取消</button>
|
||||
<button class="btn btn-primary" onclick="submitForm()">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 上传 Modal -->
|
||||
<div class="modal-overlay" id="uploadModal">
|
||||
<div class="modal">
|
||||
<h2>上传固件文件</h2>
|
||||
<input type="hidden" id="uploadId">
|
||||
<div class="form-group">
|
||||
<label>选择固件文件</label>
|
||||
<input type="file" id="uploadFile">
|
||||
<div class="file-info" id="uploadInfo"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-cancel" onclick="closeUploadModal()">取消</button>
|
||||
<button class="btn btn-success" id="uploadBtn" onclick="doUpload()">上传</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API = '/aat_version';
|
||||
|
||||
// Load list
|
||||
function loadList() {
|
||||
const type = document.getElementById('filterType').value;
|
||||
const url = type ? `${API}/type/${type}` : `${API}/list`;
|
||||
fetch(url).then(r => r.json()).then(res => {
|
||||
if (!res.success) { showToast(res.message, 'error'); return; }
|
||||
renderTable(res.data);
|
||||
}).catch(() => showToast('加载失败', 'error'));
|
||||
}
|
||||
|
||||
function renderTable(list) {
|
||||
const tbody = document.getElementById('tableBody');
|
||||
if (!list || list.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="empty">暂无数据</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = list.map(item => `<tr>
|
||||
<td>${item.id}</td>
|
||||
<td><span class="tag">${item.firmwareType}</span></td>
|
||||
<td>${item.firmwareName || '-'}</td>
|
||||
<td>${item.firmwareVersion || '-'}</td>
|
||||
<td>${item.minHardwareVersion || '0.0'}</td>
|
||||
<td class="size">${item.firmwareSize ? formatSize(item.firmwareSize) : '-'}</td>
|
||||
<td>${item.downloadUrl ? '<a class="url-link" href="' + item.downloadUrl + '" target="_blank">下载</a>' : '-'}</td>
|
||||
<td>
|
||||
<button class="btn btn-success btn-sm" onclick="openUpload(${item.id})">上传</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="openEdit(${item.id})">编辑</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="del(${item.id})">删除</button>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / 1048576).toFixed(2) + ' MB';
|
||||
}
|
||||
|
||||
// Filter
|
||||
document.getElementById('filterType').addEventListener('change', loadList);
|
||||
|
||||
// Add
|
||||
function openAddModal() {
|
||||
document.getElementById('modalTitle').textContent = '新增固件';
|
||||
document.getElementById('formId').value = '';
|
||||
document.getElementById('formType').value = 'lb_main';
|
||||
document.getElementById('formName').value = '';
|
||||
document.getElementById('formVersion').value = '';
|
||||
document.getElementById('formMinHw').value = '0.0';
|
||||
document.getElementById('formDesc').value = '';
|
||||
document.getElementById('formModal').classList.add('active');
|
||||
}
|
||||
|
||||
// Edit
|
||||
function openEdit(id) {
|
||||
fetch(`${API}/${id}`).then(r => r.json()).then(res => {
|
||||
if (!res.success) { showToast(res.message, 'error'); return; }
|
||||
const d = res.data;
|
||||
document.getElementById('modalTitle').textContent = '编辑固件';
|
||||
document.getElementById('formId').value = d.id;
|
||||
document.getElementById('formType').value = d.firmwareType;
|
||||
document.getElementById('formName').value = d.firmwareName || '';
|
||||
document.getElementById('formVersion').value = d.firmwareVersion || '';
|
||||
document.getElementById('formMinHw').value = d.minHardwareVersion || '0.0';
|
||||
document.getElementById('formDesc').value = d.firmwareDescription || '';
|
||||
document.getElementById('formModal').classList.add('active');
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal() { document.getElementById('formModal').classList.remove('active'); }
|
||||
|
||||
function submitForm() {
|
||||
const id = document.getElementById('formId').value;
|
||||
const body = {
|
||||
firmwareType: document.getElementById('formType').value,
|
||||
firmwareName: document.getElementById('formName').value,
|
||||
firmwareVersion: document.getElementById('formVersion').value,
|
||||
minHardwareVersion: document.getElementById('formMinHw').value,
|
||||
firmwareDescription: document.getElementById('formDesc').value
|
||||
};
|
||||
if (!body.firmwareName || !body.firmwareVersion) {
|
||||
showToast('请填写固件名称和版本号', 'error'); return;
|
||||
}
|
||||
if (id) body.id = parseInt(id);
|
||||
|
||||
const method = id ? 'PUT' : 'POST';
|
||||
fetch(API, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
||||
.then(r => r.json()).then(res => {
|
||||
if (res.success) { showToast('保存成功'); closeModal(); loadList(); }
|
||||
else showToast(res.message || '保存失败', 'error');
|
||||
}).catch(() => showToast('请求失败', 'error'));
|
||||
}
|
||||
|
||||
// Delete
|
||||
function del(id) {
|
||||
if (!confirm('确定删除该固件版本?')) return;
|
||||
fetch(`${API}/${id}`, { method: 'DELETE' }).then(r => r.json()).then(res => {
|
||||
if (res.success) { showToast('删除成功'); loadList(); }
|
||||
else showToast(res.message || '删除失败', 'error');
|
||||
}).catch(() => showToast('请求失败', 'error'));
|
||||
}
|
||||
|
||||
// Upload
|
||||
function openUpload(id) {
|
||||
document.getElementById('uploadId').value = id;
|
||||
document.getElementById('uploadFile').value = '';
|
||||
document.getElementById('uploadInfo').textContent = '';
|
||||
document.getElementById('uploadBtn').disabled = false;
|
||||
document.getElementById('uploadBtn').textContent = '上传';
|
||||
document.getElementById('uploadModal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeUploadModal() { document.getElementById('uploadModal').classList.remove('active'); }
|
||||
|
||||
function doUpload() {
|
||||
const id = document.getElementById('uploadId').value;
|
||||
const fileInput = document.getElementById('uploadFile');
|
||||
if (!fileInput.files.length) { showToast('请选择文件', 'error'); return; }
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
|
||||
const btn = document.getElementById('uploadBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '上传中...';
|
||||
|
||||
fetch(`${API}/upload/${id}`, { method: 'POST', body: formData })
|
||||
.then(r => r.json()).then(res => {
|
||||
if (res.success) { showToast('上传成功'); closeUploadModal(); loadList(); }
|
||||
else showToast(res.message || '上传失败', 'error');
|
||||
}).catch(() => showToast('上传失败', 'error'))
|
||||
.finally(() => { btn.disabled = false; btn.textContent = '上传'; });
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, type) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'toast ' + (type === 'error' ? 'toast-error' : 'toast-success');
|
||||
el.textContent = msg;
|
||||
document.body.appendChild(el);
|
||||
setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 300); }, 2000);
|
||||
}
|
||||
|
||||
// Init
|
||||
loadList();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user