新增产品QC

This commit is contained in:
2026-02-28 13:32:29 +08:00
parent bd3523fd3d
commit 306222c55e
10 changed files with 695 additions and 2 deletions

2
.gitignore vendored
View File

@@ -16,7 +16,7 @@ build/
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
logs
### IntelliJ IDEA ###
.idea
*.iws

View File

@@ -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');
-- ============================================================
-- 示例数据(可选)
-- ============================================================

View File

@@ -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")

View 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;
}

View File

@@ -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

View 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> {
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View 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>