diff --git a/edc-web/app/models.py b/edc-web/app/models.py index 485f087..9c9bf22 100644 --- a/edc-web/app/models.py +++ b/edc-web/app/models.py @@ -192,8 +192,13 @@ def get_test_data(page: int = 1, per_page: int = 20, # data offset = (page - 1) * per_page cur.execute( - f"SELECT t.*, d.serial FROM tb_state_tst t " + f"SELECT t.*, d.serial, " + f"c.coil_num, c.name as coil_name, " + f"sc.simulate_num, sc.name as car_name " + f"FROM tb_state_tst t " f"JOIN dnt_info d ON t.dnt_id = d.id " + f"LEFT JOIN tb_coil_info c ON t.coil_id = c.id " + f"LEFT JOIN tb_simulate_car sc ON t.simulate_car_id = sc.id " f"WHERE {where_clause} " f"ORDER BY t.id DESC LIMIT %s OFFSET %s", params + [per_page, offset], @@ -236,8 +241,13 @@ def get_all_test_data_for_export(serial: str = "", date_from: str = "", where_clause = " AND ".join(where) if where else "1=1" cur.execute( - f"SELECT t.*, d.serial FROM tb_state_tst t " + f"SELECT t.*, d.serial, " + f"c.coil_num, c.name as coil_name, " + f"sc.simulate_num, sc.name as car_name " + f"FROM tb_state_tst t " f"JOIN dnt_info d ON t.dnt_id = d.id " + f"LEFT JOIN tb_coil_info c ON t.coil_id = c.id " + f"LEFT JOIN tb_simulate_car sc ON t.simulate_car_id = sc.id " f"WHERE {where_clause} ORDER BY t.id DESC", params, ) @@ -446,12 +456,24 @@ def get_logs(page: int = 1, per_page: int = 30, # ─── tb_fixture_param ────────────────────────────────────────────── def get_fixture_param(dnt_id: int) -> dict | None: - """获取设备的工装测试参数""" + """获取设备的工装测试参数(含线圈和模拟车辆信息)""" conn = get_conn() try: with conn.cursor() as cur: cur.execute( - "SELECT * FROM tb_fixture_param WHERE dnt_id=%s", (dnt_id,), + "SELECT fp.*, " + "c.coil_num, c.name as coil_name, c.shape as coil_shape, " + "c.length as coil_length, c.width as coil_width, c.radius as coil_radius, " + "c.turns as coil_turns, c.resistance as coil_resistance, " + "c.material as coil_material, " + "sc.simulate_num, sc.name as car_name, sc.shape as car_shape, " + "sc.length as car_length, sc.width as car_width, sc.radius as car_radius, " + "sc.material as car_material " + "FROM tb_fixture_param fp " + "LEFT JOIN tb_coil_info c ON fp.coil_id = c.id " + "LEFT JOIN tb_simulate_car sc ON fp.simulate_car_id = sc.id " + "WHERE fp.dnt_id=%s", + (dnt_id,), ) return cur.fetchone() finally: @@ -467,6 +489,7 @@ def upsert_fixture_param(dnt_id: int, **kwargs): "SELECT id FROM tb_fixture_param WHERE dnt_id=%s", (dnt_id,), ) existing = cur.fetchone() + # 主线参数字段(不含 coil_id/simulate_car_id,后面单独处理) fields = [ "Addr", "DevType", "TestMode", "RestDis", "MinusDis", "SensMin", "SensMax", "FreMin", "FreMax", "PeakMin", "PeakMax", @@ -488,6 +511,17 @@ def upsert_fixture_param(dnt_id: int, **kwargs): f"VALUES (%s, {placeholders})", [dnt_id] + values, ) + # 单独处理线圈/模拟车辆关联(可选,不覆盖已有值) + if "coil_id" in kwargs: + cur.execute( + "UPDATE tb_fixture_param SET coil_id=%s WHERE dnt_id=%s", + (kwargs["coil_id"], dnt_id), + ) + if "simulate_car_id" in kwargs: + cur.execute( + "UPDATE tb_fixture_param SET simulate_car_id=%s WHERE dnt_id=%s", + (kwargs["simulate_car_id"], dnt_id), + ) conn.commit() finally: conn.close() @@ -584,6 +618,172 @@ def delete_vehicle_base_test(test_id: int): conn.close() +# ─── 线圈参数 CRUD ────────────────────────────────────────────────── + +def get_coil_info_list(search: str = "") -> list[dict]: + """获取线圈参数列表""" + conn = get_conn() + try: + with conn.cursor() as cur: + if search: + cur.execute( + "SELECT * FROM tb_coil_info WHERE coil_num LIKE %s OR name LIKE %s " + "ORDER BY id DESC", + (f"%{search}%", f"%{search}%"), + ) + else: + cur.execute("SELECT * FROM tb_coil_info ORDER BY id DESC") + return cur.fetchall() + finally: + conn.close() + + +def get_coil_info_by_id(coil_id: int) -> dict | None: + """获取单个线圈参数""" + conn = get_conn() + try: + with conn.cursor() as cur: + cur.execute("SELECT * FROM tb_coil_info WHERE id=%s", (coil_id,)) + return cur.fetchone() + finally: + conn.close() + + +def create_coil_info(**kwargs) -> int: + """创建线圈参数,返回新 ID""" + conn = get_conn() + try: + with conn.cursor() as cur: + fields = [ + "coil_num", "name", "induct", "shape", "length", "width", + "radius", "turns", "resistance", "material", "remark", + ] + col_names = ", ".join(f"`{f}`" for f in fields) + placeholders = ", ".join(["%s"] * len(fields)) + values = [kwargs.get(f, "" if f in ("coil_num", "name", "shape", "material", "remark") else 0) for f in fields] + cur.execute( + f"INSERT INTO tb_coil_info ({col_names}) VALUES ({placeholders})", + values, + ) + conn.commit() + return cur.lastrowid + finally: + conn.close() + + +def update_coil_info(coil_id: int, **kwargs): + """更新线圈参数""" + conn = get_conn() + try: + with conn.cursor() as cur: + fields = [ + "coil_num", "name", "induct", "shape", "length", "width", + "radius", "turns", "resistance", "material", "remark", + ] + sets = ", ".join(f"`{f}`=%s" for f in fields) + values = [kwargs.get(f, "" if f in ("coil_num", "name", "shape", "material", "remark") else 0) for f in fields] + [coil_id] + cur.execute( + f"UPDATE tb_coil_info SET {sets} WHERE id=%s", values, + ) + conn.commit() + finally: + conn.close() + + +def delete_coil_info(coil_id: int): + """删除线圈参数""" + conn = get_conn() + try: + with conn.cursor() as cur: + cur.execute("DELETE FROM tb_coil_info WHERE id=%s", (coil_id,)) + conn.commit() + finally: + conn.close() + + +# ─── 模拟车辆参数 CRUD ────────────────────────────────────────────── + +def get_simulate_car_list(search: str = "") -> list[dict]: + """获取模拟车辆参数列表""" + conn = get_conn() + try: + with conn.cursor() as cur: + if search: + cur.execute( + "SELECT * FROM tb_simulate_car WHERE simulate_num LIKE %s OR name LIKE %s " + "ORDER BY id DESC", + (f"%{search}%", f"%{search}%"), + ) + else: + cur.execute("SELECT * FROM tb_simulate_car ORDER BY id DESC") + return cur.fetchall() + finally: + conn.close() + + +def get_simulate_car_by_id(car_id: int) -> dict | None: + """获取单个模拟车辆参数""" + conn = get_conn() + try: + with conn.cursor() as cur: + cur.execute("SELECT * FROM tb_simulate_car WHERE id=%s", (car_id,)) + return cur.fetchone() + finally: + conn.close() + + +def create_simulate_car(**kwargs) -> int: + """创建模拟车辆参数,返回新 ID""" + conn = get_conn() + try: + with conn.cursor() as cur: + fields = [ + "simulate_num", "name", "shape", "length", "width", + "radius", "material", "remark", + ] + col_names = ", ".join(f"`{f}`" for f in fields) + placeholders = ", ".join(["%s"] * len(fields)) + values = [kwargs.get(f, "" if f in ("simulate_num", "name", "shape", "material", "remark") else 0) for f in fields] + cur.execute( + f"INSERT INTO tb_simulate_car ({col_names}) VALUES ({placeholders})", + values, + ) + conn.commit() + return cur.lastrowid + finally: + conn.close() + + +def update_simulate_car(car_id: int, **kwargs): + """更新模拟车辆参数""" + conn = get_conn() + try: + with conn.cursor() as cur: + fields = [ + "simulate_num", "name", "shape", "length", "width", + "radius", "material", "remark", + ] + sets = ", ".join(f"`{f}`=%s" for f in fields) + values = [kwargs.get(f, "" if f in ("simulate_num", "name", "shape", "material", "remark") else 0) for f in fields] + [car_id] + cur.execute( + f"UPDATE tb_simulate_car SET {sets} WHERE id=%s", values, + ) + conn.commit() + finally: + conn.close() + + +def delete_simulate_car(car_id: int): + """删除模拟车辆参数""" + conn = get_conn() + try: + with conn.cursor() as cur: + cur.execute("DELETE FROM tb_simulate_car WHERE id=%s", (car_id,)) + conn.commit() + finally: + conn.close() + + # ─── 测试数据删除 ────────────────────────────────────────────── def delete_test_data(serial: str = "", date_from: str = "", diff --git a/edc-web/app/routes/fixture.py b/edc-web/app/routes/fixture.py index f63c1a5..25ad594 100644 --- a/edc-web/app/routes/fixture.py +++ b/edc-web/app/routes/fixture.py @@ -14,6 +14,16 @@ from app.models import ( delete_vehicle_base_test, get_serialnet_by_id, insert_log, + get_coil_info_list, + get_coil_info_by_id, + create_coil_info, + update_coil_info, + delete_coil_info, + get_simulate_car_list, + get_simulate_car_by_id, + create_simulate_car, + update_simulate_car, + delete_simulate_car, ) bp = Blueprint("fixture", __name__) @@ -275,3 +285,181 @@ def api_delete_vehicle_base_test(test_id): return jsonify({"ok": True}) except Exception as e: return jsonify({"ok": False, "error": str(e)}), 500 + + +# ─── 线圈参数页面 ────────────────────────────────────────────────── + +@bp.route("/coil-info") +@login_required +def coil_info_page(): + """线圈参数管理页面""" + return render_template("coil_info.html") + + +@bp.route("/api/coil-info") +@login_required +def api_list_coil_info(): + """列出线圈参数""" + search = request.args.get("search", "") + items = get_coil_info_list(search) + return jsonify(items) + + +@bp.route("/api/coil-info/") +@login_required +def api_get_coil_info(coil_id): + """获取单个线圈参数""" + item = get_coil_info_by_id(coil_id) + if not item: + return jsonify({"error": "不存在"}), 404 + return jsonify(item) + + +@bp.route("/api/coil-info", methods=["POST"]) +@login_required +def api_create_coil_info(): + """创建线圈参数""" + data = request.get_json() + if not data: + return jsonify({"ok": False, "error": "数据为空"}), 400 + try: + coil_id = create_coil_info(**data) + insert_log( + current_user.id, current_user.username, "create", + target="coil_info", + detail=f"创建线圈: {data.get('coil_num','')} {data.get('name','')}", + result="ok", ip=request.remote_addr or "", + ) + return jsonify({"ok": True, "id": coil_id}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}), 500 + + +@bp.route("/api/coil-info/", methods=["PUT"]) +@login_required +def api_update_coil_info(coil_id): + """更新线圈参数""" + data = request.get_json() + if not data: + return jsonify({"ok": False, "error": "数据为空"}), 400 + try: + update_coil_info(coil_id, **data) + insert_log( + current_user.id, current_user.username, "update", + target="coil_info", + detail=f"更新线圈 id={coil_id}: {data.get('coil_num','')} {data.get('name','')}", + result="ok", ip=request.remote_addr or "", + ) + return jsonify({"ok": True}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}), 500 + + +@bp.route("/api/coil-info/", methods=["DELETE"]) +@login_required +def api_delete_coil_info(coil_id): + """删除线圈参数""" + try: + item = get_coil_info_by_id(coil_id) + detail = f"删除线圈 id={coil_id}" + if item: + detail += f": {item.get('coil_num','')} {item.get('name','')}" + delete_coil_info(coil_id) + insert_log( + current_user.id, current_user.username, "delete", + target="coil_info", + detail=detail, + result="ok", ip=request.remote_addr or "", + ) + return jsonify({"ok": True}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}), 500 + + +# ─── 模拟车辆参数页面 ────────────────────────────────────────────── + +@bp.route("/simulate-car") +@login_required +def simulate_car_page(): + """模拟车辆参数管理页面""" + return render_template("simulate_car.html") + + +@bp.route("/api/simulate-car") +@login_required +def api_list_simulate_car(): + """列出模拟车辆参数""" + search = request.args.get("search", "") + items = get_simulate_car_list(search) + return jsonify(items) + + +@bp.route("/api/simulate-car/") +@login_required +def api_get_simulate_car(car_id): + """获取单个模拟车辆参数""" + item = get_simulate_car_by_id(car_id) + if not item: + return jsonify({"error": "不存在"}), 404 + return jsonify(item) + + +@bp.route("/api/simulate-car", methods=["POST"]) +@login_required +def api_create_simulate_car(): + """创建模拟车辆参数""" + data = request.get_json() + if not data: + return jsonify({"ok": False, "error": "数据为空"}), 400 + try: + car_id = create_simulate_car(**data) + insert_log( + current_user.id, current_user.username, "create", + target="simulate_car", + detail=f"创建模拟车辆: {data.get('simulate_num','')} {data.get('name','')}", + result="ok", ip=request.remote_addr or "", + ) + return jsonify({"ok": True, "id": car_id}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}), 500 + + +@bp.route("/api/simulate-car/", methods=["PUT"]) +@login_required +def api_update_simulate_car(car_id): + """更新模拟车辆参数""" + data = request.get_json() + if not data: + return jsonify({"ok": False, "error": "数据为空"}), 400 + try: + update_simulate_car(car_id, **data) + insert_log( + current_user.id, current_user.username, "update", + target="simulate_car", + detail=f"更新模拟车辆 id={car_id}: {data.get('simulate_num','')} {data.get('name','')}", + result="ok", ip=request.remote_addr or "", + ) + return jsonify({"ok": True}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}), 500 + + +@bp.route("/api/simulate-car/", methods=["DELETE"]) +@login_required +def api_delete_simulate_car(car_id): + """删除模拟车辆参数""" + try: + item = get_simulate_car_by_id(car_id) + detail = f"删除模拟车辆 id={car_id}" + if item: + detail += f": {item.get('simulate_num','')} {item.get('name','')}" + delete_simulate_car(car_id) + insert_log( + current_user.id, current_user.username, "delete", + target="simulate_car", + detail=detail, + result="ok", ip=request.remote_addr or "", + ) + return jsonify({"ok": True}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)}), 500 diff --git a/edc-web/app/static/js/coil_info.js b/edc-web/app/static/js/coil_info.js new file mode 100644 index 0000000..c07cb38 --- /dev/null +++ b/edc-web/app/static/js/coil_info.js @@ -0,0 +1,173 @@ +// 线圈参数管理 + +let editId = null; // null=新增, number=编辑 + +// ─── Toast ─────────────────────────────────── + +function toast(msg, isError = false) { + const el = document.getElementById("toast"); + el.textContent = msg; + el.className = "msg-toast " + (isError ? "error" : "") + " show"; + clearTimeout(el._timeout); + el._timeout = setTimeout(() => { el.className = "msg-toast"; }, 3000); +} + +// ─── 列表加载 ──────────────────────────────── + +async function loadList() { + const search = document.getElementById("search-input").value; + try { + const resp = await fetch(`/api/coil-info?search=${encodeURIComponent(search)}`); + const data = await resp.json(); + renderTable(data); + } catch (e) { + console.error("加载失败:", e); + } +} + +function sizeLabel(item) { + if (item.shape === '圆形') return `半径${item.radius || 0}cm`; + if (item.shape === '矩形') return `${item.length || 0}×${item.width || 0}cm`; + return '-'; +} + +function renderTable(data) { + const tbody = document.querySelector("#coil-table tbody"); + if (!data.length) { + tbody.innerHTML = '暂无数据,点右上角「新增」添加'; + return; + } + tbody.innerHTML = data.map(t => ` + + ${esc(t.coil_num)} + ${esc(t.name)} + ${t.induct || '-'} + ${t.shape || '-'} + ${sizeLabel(t)} + ${t.turns || '-'} + ${t.resistance || '-'} + ${esc(t.material || '-')} + ${esc(t.remark || '-')} + + + + + + `).join(""); +} + +// ─── 弹窗 ──────────────────────────────────── + +function openModal(id = null) { + editId = id; + document.getElementById("modal-title").textContent = id ? "编辑线圈参数" : "新增线圈参数"; + document.getElementById("edit-modal").style.display = "flex"; + + if (id) { + fetch(`/api/coil-info/${id}`) + .then(r => r.json()) + .then(data => { + document.getElementById("edit-coil-num").value = data.coil_num || ""; + document.getElementById("edit-name").value = data.name || ""; + document.getElementById("edit-induct").value = data.induct || 0; + document.getElementById("edit-shape").value = data.shape || ""; + document.getElementById("edit-length").value = data.length || 0; + document.getElementById("edit-width").value = data.width || 0; + document.getElementById("edit-radius").value = data.radius || 0; + document.getElementById("edit-turns").value = data.turns || 0; + document.getElementById("edit-resistance").value = data.resistance || 0; + document.getElementById("edit-material").value = data.material || ""; + document.getElementById("edit-remark").value = data.remark || ""; + }); + } else { + document.getElementById("edit-coil-num").value = ""; + document.getElementById("edit-name").value = ""; + document.getElementById("edit-induct").value = "0"; + document.getElementById("edit-shape").value = ""; + document.getElementById("edit-length").value = "0"; + document.getElementById("edit-width").value = "0"; + document.getElementById("edit-radius").value = "0"; + document.getElementById("edit-turns").value = "0"; + document.getElementById("edit-resistance").value = "0"; + document.getElementById("edit-material").value = ""; + document.getElementById("edit-remark").value = ""; + } +} + +function closeModal() { + document.getElementById("edit-modal").style.display = "none"; + editId = null; +} + +// ─── 保存 ──────────────────────────────────── + +async function saveRecord() { + const data = { + coil_num: document.getElementById("edit-coil-num").value.trim(), + name: document.getElementById("edit-name").value.trim(), + induct: parseFloat(document.getElementById("edit-induct").value) || 0, + shape: document.getElementById("edit-shape").value, + length: parseFloat(document.getElementById("edit-length").value) || 0, + width: parseFloat(document.getElementById("edit-width").value) || 0, + radius: parseFloat(document.getElementById("edit-radius").value) || 0, + turns: parseInt(document.getElementById("edit-turns").value) || 0, + resistance: parseFloat(document.getElementById("edit-resistance").value) || 0, + material: document.getElementById("edit-material").value.trim(), + remark: document.getElementById("edit-remark").value.trim(), + }; + + if (!data.coil_num && !data.name) { + toast("请输入线圈编号或名称", true); + return; + } + + try { + let resp; + if (editId) { + resp = await fetch(`/api/coil-info/${editId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); + } else { + resp = await fetch("/api/coil-info", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); + } + const result = await resp.json(); + if (result.ok || resp.ok) { + toast(editId ? "更新成功" : "新增成功"); + closeModal(); + loadList(); + } else { + toast("保存失败: " + (result.error || "未知错误"), true); + } + } catch (e) { + toast("保存失败: " + e.message, true); + } +} + +// ─── 删除 ──────────────────────────────────── + +async function deleteRecord(id, label) { + if (!confirm(`确定要删除「${label}」吗?`)) return; + + try { + const resp = await fetch(`/api/coil-info/${id}`, { method: "DELETE" }); + const result = await resp.json(); + if (result.ok) { + toast("删除成功"); + loadList(); + } else { + toast("删除失败: " + (result.error || "未知错误"), true); + } + } catch (e) { + toast("删除失败: " + e.message, true); + } +} + +function esc(s) { return (s || "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } + +loadList(); diff --git a/edc-web/app/static/js/fixture.js b/edc-web/app/static/js/fixture.js index 1f536d0..37ba3a0 100644 --- a/edc-web/app/static/js/fixture.js +++ b/edc-web/app/static/js/fixture.js @@ -9,6 +9,8 @@ let pollTimers = {}; // record_id → timer (指令响应轮询) async function init() { await loadBaseTests(); + await loadCoilList(); + await loadCarList(); await loadFixtureParam(); } @@ -119,6 +121,66 @@ function onDevTypeChange() { else { selectedBaseTest = null; renderBaseTestTable(); } } +// ─── 线圈列表 ──────────────────────────────── + +let coilList = []; + +async function loadCoilList() { + try { + const resp = await fetch("/api/coil-info"); + coilList = await resp.json(); + populateCoilSelect(); + } catch (e) { console.error("加载线圈列表失败:", e); } +} + +function populateCoilSelect() { + const sel = document.getElementById("coil-select"); + sel.innerHTML = '' + + coilList.map(c => ``).join(""); +} + +function onCoilChange() { + const id = parseInt(document.getElementById("coil-select").value); + const coil = coilList.find(c => c.id === id); + const detail = document.getElementById("coil-detail"); + if (coil) { + const sizeText = coil.shape === '圆形' ? `半径${coil.radius}cm` : `${coil.length}×${coil.width}cm`; + detail.innerHTML = `${coil.shape || '-'} / ${sizeText} / ${coil.turns || 0}圈 / ${coil.resistance || 0}Ω / ${coil.material || ''}`; + } else { + detail.innerHTML = ''; + } +} + +// ─── 模拟车辆列表 ──────────────────────────── + +let carList = []; + +async function loadCarList() { + try { + const resp = await fetch("/api/simulate-car"); + carList = await resp.json(); + populateCarSelect(); + } catch (e) { console.error("加载模拟车辆列表失败:", e); } +} + +function populateCarSelect() { + const sel = document.getElementById("car-select"); + sel.innerHTML = '' + + carList.map(c => ``).join(""); +} + +function onCarChange() { + const id = parseInt(document.getElementById("car-select").value); + const car = carList.find(c => c.id === id); + const detail = document.getElementById("car-detail"); + if (car) { + const sizeText = car.shape === '圆形' ? `半径${car.radius}cm` : `${car.length}×${car.width}cm`; + detail.innerHTML = `${car.shape || '-'} / ${sizeText} / ${car.material || ''}`; + } else { + detail.innerHTML = ''; + } +} + // ─── 从 DB 加载/刷新/保存 ──────────────────── async function loadFixtureParam() { @@ -152,6 +214,17 @@ function fillFormFromParam(param) { document.getElementById("param-far-stay").value = param.FarStay || 0; const matched = baseTests.find(t => t.type_num === param.DevType); if (matched) { selectedBaseTest = matched; renderBaseTestTable(); } + // 设置线圈和模拟车辆选中 + if (param.coil_id) { + document.getElementById("coil-select").value = param.coil_id; + onCoilChange(); + } + if (param.simulate_car_id) { + document.getElementById("car-select").value = param.simulate_car_id; + onCarChange(); + } + // 更新当前关联标签 + updateCurrentLabels(param); } async function refreshParams() { @@ -168,31 +241,55 @@ async function refreshParams() { async function saveToDb() { const data = getFormParams(); + const coilId = parseInt(document.getElementById("coil-select").value) || null; + const carId = parseInt(document.getElementById("car-select").value) || null; + const body = { + Addr: data.addr, DevType: data.dev_type, TestMode: data.test_mode, + RestDis: data.reset_dis, MinusDis: data.minus_dis, + SensMin: data.sens_min, SensMax: data.sens_max, + FreMin: data.fre_min, FreMax: data.fre_max, + PeakMin: data.peak_min, PeakMax: data.peak_max, + FarTol: data.far_tol, NearTol: data.near_tol, + StepTol: data.step_tol, BackForth: data.back_forth, + NearStay: data.near_stay, FarStay: data.far_stay, + }; + if (coilId) body.coil_id = coilId; + if (carId) body.simulate_car_id = carId; try { const resp = await fetch(`/api/fixture/param/${DNT_ID}`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - Addr: data.addr, DevType: data.dev_type, TestMode: data.test_mode, - RestDis: data.reset_dis, MinusDis: data.minus_dis, - SensMin: data.sens_min, SensMax: data.sens_max, - FreMin: data.fre_min, FreMax: data.fre_max, - PeakMin: data.peak_min, PeakMax: data.peak_max, - FarTol: data.far_tol, NearTol: data.near_tol, - StepTol: data.step_tol, BackForth: data.back_forth, - NearStay: data.near_stay, FarStay: data.far_stay, - }), + body: JSON.stringify(body), }); const result = await resp.json(); if (result.ok) { commLog('info', null, '参数已保存到数据库'); toast("已保存到数据库"); + updateCurrentLabels(); } else { toast("保存失败: " + (result.error || ""), true); } } catch (e) { toast("保存失败: " + e.message, true); } } +/** 更新页面上的当前线圈/车辆标签 */ +function updateCurrentLabels(param) { + const coilLabel = document.getElementById("current-coil-label"); + const carLabel = document.getElementById("current-car-label"); + if (param) { + coilLabel.textContent = param.coil_name || param.coil_num || '未设置'; + carLabel.textContent = param.car_name || param.simulate_num || '未设置'; + } else { + // 从 DOM 读取当前选择 + const coilId = parseInt(document.getElementById("coil-select").value); + const carId = parseInt(document.getElementById("car-select").value); + const coil = coilId ? coilList.find(c => c.id === coilId) : null; + const car = carId ? carList.find(c => c.id === carId) : null; + coilLabel.textContent = coil ? (coil.coil_num || coil.name) : '未设置'; + carLabel.textContent = car ? (car.simulate_num || car.name) : '未设置'; + } +} + function getFormParams() { return { addr: parseInt(document.getElementById("param-addr").value) || 1, @@ -324,19 +421,24 @@ async function sendConfig() { startRespPolling(data.record_id, "4B"); // 同时保存到数据库 + const coilId = parseInt(document.getElementById("coil-select").value) || null; + const carId = parseInt(document.getElementById("car-select").value) || null; + const saveBody = { + Addr: params.addr, DevType: params.dev_type, TestMode: params.test_mode, + RestDis: params.reset_dis, MinusDis: params.minus_dis, + SensMin: params.sens_min, SensMax: params.sens_max, + FreMin: params.fre_min, FreMax: params.fre_max, + PeakMin: params.peak_min, PeakMax: params.peak_max, + FarTol: params.far_tol, NearTol: params.near_tol, + StepTol: params.step_tol, BackForth: params.back_forth, + NearStay: params.near_stay, FarStay: params.far_stay, + }; + if (coilId) saveBody.coil_id = coilId; + if (carId) saveBody.simulate_car_id = carId; await fetch(`/api/fixture/param/${DNT_ID}`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - Addr: params.addr, DevType: params.dev_type, TestMode: params.test_mode, - RestDis: params.reset_dis, MinusDis: params.minus_dis, - SensMin: params.sens_min, SensMax: params.sens_max, - FreMin: params.fre_min, FreMax: params.fre_max, - PeakMin: params.peak_min, PeakMax: params.peak_max, - FarTol: params.far_tol, NearTol: params.near_tol, - StepTol: params.step_tol, BackForth: params.back_forth, - NearStay: params.near_stay, FarStay: params.far_stay, - }), + body: JSON.stringify(saveBody), }); } else { toast(`失败: ${data.error}`, true); diff --git a/edc-web/app/static/js/simulate_car.js b/edc-web/app/static/js/simulate_car.js new file mode 100644 index 0000000..41512cb --- /dev/null +++ b/edc-web/app/static/js/simulate_car.js @@ -0,0 +1,151 @@ +// 模拟车辆参数管理 + +let editId = null; + +function toast(msg, isError = false) { + const el = document.getElementById("toast"); + el.textContent = msg; + el.className = "msg-toast " + (isError ? "error" : "") + " show"; + clearTimeout(el._timeout); + el._timeout = setTimeout(() => { el.className = "msg-toast"; }, 3000); +} + +async function loadList() { + const search = document.getElementById("search-input").value; + try { + const resp = await fetch(`/api/simulate-car?search=${encodeURIComponent(search)}`); + const data = await resp.json(); + renderTable(data); + } catch (e) { + console.error("加载失败:", e); + } +} + +function sizeLabel(item) { + if (item.shape === '圆形') return `半径${item.radius || 0}cm`; + if (item.shape === '矩形') return `${item.length || 0}×${item.width || 0}cm`; + return '-'; +} + +function renderTable(data) { + const tbody = document.querySelector("#car-table tbody"); + if (!data.length) { + tbody.innerHTML = '暂无数据,点右上角「新增」添加'; + return; + } + tbody.innerHTML = data.map(t => ` + + ${esc(t.simulate_num)} + ${esc(t.name)} + ${t.shape || '-'} + ${sizeLabel(t)} + ${esc(t.material || '-')} + ${esc(t.remark || '-')} + + + + + + `).join(""); +} + +function openModal(id = null) { + editId = id; + document.getElementById("modal-title").textContent = id ? "编辑模拟车辆参数" : "新增模拟车辆参数"; + document.getElementById("edit-modal").style.display = "flex"; + + if (id) { + fetch(`/api/simulate-car/${id}`) + .then(r => r.json()) + .then(data => { + document.getElementById("edit-simulate-num").value = data.simulate_num || ""; + document.getElementById("edit-name").value = data.name || ""; + document.getElementById("edit-shape").value = data.shape || ""; + document.getElementById("edit-length").value = data.length || 0; + document.getElementById("edit-width").value = data.width || 0; + document.getElementById("edit-radius").value = data.radius || 0; + document.getElementById("edit-material").value = data.material || ""; + document.getElementById("edit-remark").value = data.remark || ""; + }); + } else { + document.getElementById("edit-simulate-num").value = ""; + document.getElementById("edit-name").value = ""; + document.getElementById("edit-shape").value = ""; + document.getElementById("edit-length").value = "0"; + document.getElementById("edit-width").value = "0"; + document.getElementById("edit-radius").value = "0"; + document.getElementById("edit-material").value = ""; + document.getElementById("edit-remark").value = ""; + } +} + +function closeModal() { + document.getElementById("edit-modal").style.display = "none"; + editId = null; +} + +async function saveRecord() { + const data = { + simulate_num: document.getElementById("edit-simulate-num").value.trim(), + name: document.getElementById("edit-name").value.trim(), + shape: document.getElementById("edit-shape").value, + length: parseFloat(document.getElementById("edit-length").value) || 0, + width: parseFloat(document.getElementById("edit-width").value) || 0, + radius: parseFloat(document.getElementById("edit-radius").value) || 0, + material: document.getElementById("edit-material").value.trim(), + remark: document.getElementById("edit-remark").value.trim(), + }; + + if (!data.simulate_num && !data.name) { + toast("请输入模拟编号或名称", true); + return; + } + + try { + let resp; + if (editId) { + resp = await fetch(`/api/simulate-car/${editId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); + } else { + resp = await fetch("/api/simulate-car", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); + } + const result = await resp.json(); + if (result.ok || resp.ok) { + toast(editId ? "更新成功" : "新增成功"); + closeModal(); + loadList(); + } else { + toast("保存失败: " + (result.error || "未知错误"), true); + } + } catch (e) { + toast("保存失败: " + e.message, true); + } +} + +async function deleteRecord(id, label) { + if (!confirm(`确定要删除「${label}」吗?`)) return; + + try { + const resp = await fetch(`/api/simulate-car/${id}`, { method: "DELETE" }); + const result = await resp.json(); + if (result.ok) { + toast("删除成功"); + loadList(); + } else { + toast("删除失败: " + (result.error || "未知错误"), true); + } + } catch (e) { + toast("删除失败: " + e.message, true); + } +} + +function esc(s) { return (s || "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } + +loadList(); diff --git a/edc-web/app/static/js/test_data.js b/edc-web/app/static/js/test_data.js index eec7124..3608c67 100644 --- a/edc-web/app/static/js/test_data.js +++ b/edc-web/app/static/js/test_data.js @@ -20,6 +20,7 @@ const VIEWS = { { key: 'exit_dist', title: '离开距离' }, { key: 'remain_count', title: '剩余次数' }, { key: 'curr_dist', title: '当前距离' }, + { key: 'env', title: '测试环境', render: r => envLabel(r) }, { key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) }, ], }, @@ -44,6 +45,7 @@ const VIEWS = { { key: 'exit_dist', title: '离开距离' }, { key: 'enter_speed', title: '进入速度', render: r => toSpeed(r.enter_speed) }, { key: 'exit_speed', title: '离开速度', render: r => toSpeed(r.exit_speed) }, + { key: 'env', title: '测试环境', render: r => envLabel(r) }, { key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) }, ], }, @@ -63,6 +65,7 @@ const VIEWS = { { key: 'b4_enter_dist', title: '进入高度(mm)' }, { key: 'b4_leave_dist', title: '离开高度(mm)' }, { key: 'relay_out', title: '继电器', render: r => decodeRelay(r.relay_code) }, + { key: 'env', title: '测试环境', render: r => envLabel(r) }, { key: 'create_time', title: '时间', render: r => fmtTime(r.create_time) }, ], }, @@ -106,6 +109,18 @@ function decodeRelay(v) { return RELAY_MAP[parseInt(v)] || `0x${parseInt(v).toString(16).toUpperCase().padStart(2, '0')}`; } +/** 构建测试环境标签 (线圈 + 模拟车辆) */ +function envLabel(r) { + const parts = []; + if (r.coil_num || r.coil_name) { + parts.push('🧵' + (r.coil_num || r.coil_name)); + } + if (r.simulate_num || r.car_name) { + parts.push('🚗' + (r.simulate_num || r.car_name)); + } + return parts.join(' ') || '-'; +} + // ─── 视图切换 ──────────────────────────────────── function switchView(view) { diff --git a/edc-web/app/templates/coil_info.html b/edc-web/app/templates/coil_info.html new file mode 100644 index 0000000..c93c3d3 --- /dev/null +++ b/edc-web/app/templates/coil_info.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} +{% block title %}线圈参数管理 - EDC 工装管理系统{% endblock %} + +{% block content %} +
+ ← 返回设备列表 +

线圈参数管理

+
+ +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + +
线圈编号名称电感量形状尺寸 (cm)圈数电阻 (Ω)材质备注操作
+
+ + + + +
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/edc-web/app/templates/fixture.html b/edc-web/app/templates/fixture.html index 208aa80..113c471 100644 --- a/edc-web/app/templates/fixture.html +++ b/edc-web/app/templates/fixture.html @@ -140,6 +140,42 @@ + + +
+
+

关联线圈参数

+ +
+
+ + 未设置 +
+ +
+
+ + +
+
+

关联模拟车辆参数

+ +
+
+ + 未设置 +
+ +
+
diff --git a/edc-web/app/templates/simulate_car.html b/edc-web/app/templates/simulate_car.html new file mode 100644 index 0000000..c771a6b --- /dev/null +++ b/edc-web/app/templates/simulate_car.html @@ -0,0 +1,90 @@ +{% extends "base.html" %} +{% block title %}模拟车辆参数管理 - EDC 工装管理系统{% endblock %} + +{% block content %} +
+ ← 返回设备列表 +

模拟车辆参数管理

+
+ +
+
+
+ +
+ +
+ + + + + + + + + + + + + + +
模拟编号名称形状尺寸 (cm)材质备注操作
+
+ + + + +
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/edc_server b/edc_server index 844de70..68c6d0b 160000 --- a/edc_server +++ b/edc_server @@ -1 +1 @@ -Subproject commit 844de7001790727568fb27a1e6d5c4491864da9d +Subproject commit 68c6d0bcfe1224a442ae1189d1cc054c44cc2b62