fix del api

This commit is contained in:
2026-08-03 15:53:07 +08:00
parent 5edb0b2d69
commit 1f356af022
3 changed files with 64 additions and 11 deletions
+24 -7
View File
@@ -374,7 +374,8 @@ function requestModelName({
confirmLabel = "确认",
danger = false,
expectedValue = null,
expectedLabel = "输入内容"
expectedLabel = "输入内容",
ignoreCase = false
}) {
if (actionDialogState) closeActionDialog(null);
elements.actionDialogTitle.textContent = title;
@@ -391,7 +392,13 @@ function requestModelName({
elements.actionDialogInput.select();
});
return new Promise((resolve) => {
actionDialogState = { resolve, previousFocus, expectedValue, expectedLabel };
actionDialogState = {
resolve,
previousFocus,
expectedValue,
expectedLabel,
ignoreCase
};
});
}
@@ -400,18 +407,25 @@ elements.actionDialogForm.addEventListener("submit", (event) => {
const value = elements.actionDialogInput.value.trim();
const expectedValue = actionDialogState?.expectedValue;
const expectedLabel = actionDialogState?.expectedLabel || "输入内容";
const ignoreCase = actionDialogState?.ignoreCase === true;
if (!value) {
elements.actionDialogError.textContent = `${expectedLabel}不能为空`;
elements.actionDialogError.hidden = false;
elements.actionDialogInput.focus();
return;
}
if (expectedValue !== null && value !== expectedValue) {
if (expectedValue !== null) {
const expected = String(expectedValue).trim();
const matched = ignoreCase
? value.toLowerCase() === expected.toLowerCase()
: value === expected;
if (!matched) {
elements.actionDialogError.textContent = `${expectedLabel}不匹配,请按提示完整输入`;
elements.actionDialogError.hidden = false;
elements.actionDialogInput.focus();
return;
}
}
closeActionDialog(value);
});
elements.actionDialogCancel.addEventListener("click", () => closeActionDialog(null));
@@ -708,7 +722,8 @@ document.querySelector("#delete-line").addEventListener("click", async () => {
confirmLabel: "删除产线",
danger: true,
expectedValue: line.deviceId,
expectedLabel: "deviceId"
expectedLabel: "deviceId",
ignoreCase: true
});
if (confirmation === null) return;
const result = await runBusy("正在删除产线", () => window.reinloop.deleteProductionLine({
@@ -734,7 +749,8 @@ document.querySelector("#delete-company").addEventListener("click", async () =>
confirmLabel: "删除公司",
danger: true,
expectedValue: company.code,
expectedLabel: "公司编码"
expectedLabel: "公司编码",
ignoreCase: true
});
if (confirmation === null) return;
const result = await runBusy("正在删除公司", () => window.reinloop.deleteCompany({
@@ -797,12 +813,13 @@ elements.licenseList.addEventListener("click", async (event) => {
if (deleteId) {
const confirmation = await requestModelName({
title: "确认删除许可证",
message: "已撤销许可证才能删除。请输入许可证 ID 以确认永久删除。",
message: `已撤销许可证才能删除。请输入许可证 ID 以确认永久删除。\n\n当前许可证 ID${deleteId}`,
value: "",
confirmLabel: "永久删除",
danger: true,
expectedValue: deleteId,
expectedLabel: "许可证ID"
expectedLabel: "许可证ID",
ignoreCase: true
});
if (confirmation === null) return;
button.disabled = true;
+24
View File
@@ -371,6 +371,28 @@ function createApp({
return before - database.fileRecords.length;
}
async function removeDeviceDirectories(deviceId) {
const segments = String(deviceId || "").split("/");
if (segments.length !== 2 || !segments[0] || !segments[1]) return 0;
const [companyCode, lineCode] = segments;
const targets = [
path.join(store.modelsDirectory, companyCode, lineCode),
path.join(store.filesDirectory, "ReinLoop_GUI", companyCode, lineCode)
];
let removed = 0;
for (const directory of targets) {
try {
const stat = await fs.promises.stat(directory);
if (!stat.isDirectory()) continue;
} catch (_error) {
continue;
}
await fs.promises.rm(directory, { recursive: true, force: true });
removed += 1;
}
return removed;
}
function backfillIdentificationFiles(database) {
for (const record of database.fileRecords) {
if (!record.folder.endsWith("/ind_data") || ![".csv", ".json"].includes(path.extname(record.fileName).toLowerCase())) continue;
@@ -549,12 +571,14 @@ function createApp({
!(item.companyId === companyId && item.productionLineId === productionLineId)
);
const deletedDirectories = await removeDeviceDirectories(deviceId);
database.productionLines = database.productionLines.filter((item) => item.id !== productionLineId);
return {
success: true,
deletedProductionLineId: productionLineId,
deletedFiles: deletedFileCount,
deletedDirectories,
deletedLicenses: revokedLicenses,
deletedFeedback,
deletedVolumeRequests: deletedRequests,
+12
View File
@@ -877,6 +877,14 @@ test("deleteProductionLine blocks active licenses and removes revoked data", asy
assert.equal(blocked.errCode, "ACTIVE_LICENSES_PRESENT");
await post({ type: "revokeLicense", licenseId, reason: "产线删除", adminToken: "test-token" });
const orphanModelDir = path.join(dataDirectory, "models", line.productionLine.deviceId);
const orphanFileDir = path.join(dataDirectory, "files", "ReinLoop_GUI", line.productionLine.deviceId);
await fs.promises.mkdir(orphanModelDir, { recursive: true });
await fs.promises.mkdir(orphanFileDir, { recursive: true });
await fs.promises.writeFile(path.join(orphanModelDir, "orphan.bin"), "orphan-model");
await fs.promises.writeFile(path.join(orphanFileDir, "orphan.json"), "orphan-file");
const deleted = await post({
type: "deleteProductionLine",
companyId: company.company.id,
@@ -886,6 +894,10 @@ test("deleteProductionLine blocks active licenses and removes revoked data", asy
assert.equal(deleted.success, true);
assert.ok(deleted.deletedFiles >= 1);
assert.ok(deleted.deletedLicenses >= 1);
assert.ok(deleted.deletedDirectories >= 1);
await assert.rejects(fs.promises.access(orphanModelDir));
await assert.rejects(fs.promises.access(orphanFileDir));
const organizations = await post({ type: "listOrganizations", adminToken: "test-token" });
const listedCompany = organizations.companies.find((item) => item.id === company.company.id);