初始化
This commit is contained in:
commit
221c6fc239
|
@ -0,0 +1,177 @@
|
|||
from flask import Flask, render_template, jsonify, request
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import threading
|
||||
import time
|
||||
import subprocess
|
||||
import sys
|
||||
import logging
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 初始化灯光状态
|
||||
lights = {
|
||||
'common': False,
|
||||
'alarm': False,
|
||||
'up': False,
|
||||
'down': False,
|
||||
'emergency_stop': False
|
||||
}
|
||||
|
||||
# 全局变量用于存储串口对象
|
||||
ser = None
|
||||
|
||||
# 用于存储所有接收到的信号
|
||||
all_signals = []
|
||||
|
||||
# 线程控制变量
|
||||
thread_running = False
|
||||
|
||||
def reset_lights():
|
||||
global lights
|
||||
for key in lights:
|
||||
lights[key] = False
|
||||
|
||||
def analyze_signals():
|
||||
global lights, all_signals
|
||||
reset_lights()
|
||||
|
||||
# 首先检查急停条件
|
||||
if len(all_signals) >= 3 and (all_signals[-3:] == ['09', '0b', '08'] or all_signals[-3:] == ['09', '0d', '08']):
|
||||
lights['emergency_stop'] = True
|
||||
print("急停灯亮")
|
||||
lights['common'] = False
|
||||
lights['alarm'] = False
|
||||
lights['up'] = False
|
||||
lights['down'] = False
|
||||
return # 如果急停条件满足,立即返回
|
||||
|
||||
# 然后检查 '08' 信号
|
||||
if all_signals and all_signals[-1] == '08':
|
||||
print("收到08信号, 所有灯熄灭")
|
||||
reset_lights() # 重置所有灯的状态
|
||||
return
|
||||
|
||||
if '09' in all_signals:
|
||||
lights['common'] = True
|
||||
|
||||
if len(all_signals) >= 3 and all_signals[-3:] == ['09', '08', '09']:
|
||||
lights['alarm'] = True
|
||||
if all_signals[-2:] != ['08', '09']:
|
||||
lights['alarm'] = False
|
||||
|
||||
if '0b' in all_signals or (len(all_signals) >= 2 and all_signals[-2:] == ['09', '0b']):
|
||||
lights['up'] = True
|
||||
if all_signals[-1:] != ['0b']:
|
||||
lights['up'] = False
|
||||
|
||||
if '0d' in all_signals or (len(all_signals) >= 2 and all_signals[-2:] == ['09', '0d']):
|
||||
lights['down'] = True
|
||||
if all_signals[-1:] != ['0d']:
|
||||
lights['down'] = False
|
||||
|
||||
def read_serial():
|
||||
global all_signals, thread_running
|
||||
while thread_running:
|
||||
try:
|
||||
if ser and ser.is_open and ser.in_waiting:
|
||||
data = ser.read().hex()
|
||||
print(f"接收到数据: {data}")
|
||||
all_signals.append(data)
|
||||
|
||||
analyze_signals()
|
||||
|
||||
if len(all_signals) > 100:
|
||||
all_signals = all_signals[-100:]
|
||||
|
||||
print(f"当前灯光状态: {lights}")
|
||||
print(f"当前信号数组: {all_signals}")
|
||||
else:
|
||||
time.sleep(0.05)
|
||||
except serial.SerialException as e:
|
||||
print(f"串口错误: {e}")
|
||||
time.sleep(1)
|
||||
|
||||
# 设置日志
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/get_lights_status')
|
||||
def get_lights_status():
|
||||
return jsonify(lights)
|
||||
|
||||
@app.route('/get_signals')
|
||||
def get_signals():
|
||||
return jsonify(all_signals)
|
||||
|
||||
@app.route('/get_ports')
|
||||
def get_ports():
|
||||
try:
|
||||
ports = []
|
||||
if sys.platform.startswith('win'):
|
||||
result = subprocess.check_output(['wmic', 'path', 'Win32_PnPEntity', 'Get', 'DeviceID,Caption'], universal_newlines=True)
|
||||
for line in result.split('\n'):
|
||||
if '(COM' in line:
|
||||
com = line.split('(')[1].split(')')[0]
|
||||
ports.append({'device': com, 'description': line.strip()})
|
||||
elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
|
||||
cmd = 'ls /dev/tty*' if sys.platform.startswith('linux') else 'ls /dev/tty.*'
|
||||
result = subprocess.check_output(cmd, shell=True, universal_newlines=True)
|
||||
for line in result.split('\n'):
|
||||
if line:
|
||||
ports.append({'device': line, 'description': line})
|
||||
else:
|
||||
raise OSError(f"Unsupported operating system: {sys.platform}")
|
||||
|
||||
logger.debug(f"Found ports: {ports}")
|
||||
return jsonify(ports)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting ports: {str(e)}", exc_info=True)
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/set_port', methods=['POST'])
|
||||
def set_port():
|
||||
global ser, thread_running
|
||||
data = request.json
|
||||
port = data.get('port')
|
||||
|
||||
if not port:
|
||||
return jsonify({'success': False, 'message': '未提供串口地址'})
|
||||
|
||||
try:
|
||||
if ser and ser.is_open:
|
||||
thread_running = False
|
||||
time.sleep(0.5)
|
||||
ser.close()
|
||||
|
||||
ser = serial.Serial(port, 9600, timeout=1)
|
||||
thread_running = True
|
||||
threading.Thread(target=read_serial, daemon=True).start()
|
||||
return jsonify({'success': True, 'message': '串口设置成功'})
|
||||
except serial.SerialException as e:
|
||||
return jsonify({'success': False, 'message': f'串口打开失败: {str(e)}'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'未知错误: {str(e)}'})
|
||||
|
||||
@app.route('/test_port', methods=['POST'])
|
||||
def test_port():
|
||||
global ser
|
||||
if not ser or not ser.is_open:
|
||||
return jsonify({'success': False, 'message': '串口未打开'})
|
||||
|
||||
try:
|
||||
ser.write(b'TEST\r\n')
|
||||
response = ser.readline().decode().strip()
|
||||
if response:
|
||||
return jsonify({'success': True, 'message': f'收到响应: {response}'})
|
||||
else:
|
||||
return jsonify({'success': False, 'message': '未收到响应'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'测试失败: {str(e)}'})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5888)
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,68 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.zh = {}));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
|
||||
var fp = typeof window !== "undefined" && window.flatpickr !== undefined
|
||||
? window.flatpickr
|
||||
: {
|
||||
l10ns: {},
|
||||
};
|
||||
var Mandarin = {
|
||||
weekdays: {
|
||||
shorthand: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||
longhand: [
|
||||
"星期日",
|
||||
"星期一",
|
||||
"星期二",
|
||||
"星期三",
|
||||
"星期四",
|
||||
"星期五",
|
||||
"星期六",
|
||||
],
|
||||
},
|
||||
months: {
|
||||
shorthand: [
|
||||
"一月",
|
||||
"二月",
|
||||
"三月",
|
||||
"四月",
|
||||
"五月",
|
||||
"六月",
|
||||
"七月",
|
||||
"八月",
|
||||
"九月",
|
||||
"十月",
|
||||
"十一月",
|
||||
"十二月",
|
||||
],
|
||||
longhand: [
|
||||
"一月",
|
||||
"二月",
|
||||
"三月",
|
||||
"四月",
|
||||
"五月",
|
||||
"六月",
|
||||
"七月",
|
||||
"八月",
|
||||
"九月",
|
||||
"十月",
|
||||
"十一月",
|
||||
"十二月",
|
||||
],
|
||||
},
|
||||
rangeSeparator: " 至 ",
|
||||
weekAbbreviation: "周",
|
||||
scrollTitle: "滚动切换",
|
||||
toggleTitle: "点击切换 12/24 小时时制",
|
||||
};
|
||||
fp.l10ns.zh = Mandarin;
|
||||
var zh = fp.l10ns;
|
||||
|
||||
exports.Mandarin = Mandarin;
|
||||
exports.default = zh;
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
})));
|
|
@ -0,0 +1,357 @@
|
|||
console.log('main.js 文件开始执行');
|
||||
|
||||
let lightHistory = {
|
||||
common: false,
|
||||
alarm: false,
|
||||
up: false,
|
||||
down: false,
|
||||
emergency_stop: false
|
||||
};
|
||||
|
||||
let allTestsPassed = false;
|
||||
|
||||
let currentDeviceType = '5'; // 默认为五孔设备
|
||||
|
||||
function updateLights() {
|
||||
fetch('/get_lights_status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
for (let light in data) {
|
||||
let element5 = document.getElementById(light + '-5');
|
||||
let element4 = document.getElementById(light + '-4');
|
||||
if (element5) {
|
||||
updateLightStatus(element5, data[light]);
|
||||
}
|
||||
if (element4) {
|
||||
updateLightStatus(element4, data[light]);
|
||||
}
|
||||
|
||||
// 更新灯光历史和检查列表
|
||||
if (data[light] && !lightHistory[light]) {
|
||||
lightHistory[light] = true;
|
||||
updateCheckList(light);
|
||||
}
|
||||
}
|
||||
checkAllTestsPassed();
|
||||
});
|
||||
}
|
||||
|
||||
function updateLightStatus(element, isOn) {
|
||||
if (isOn) {
|
||||
element.classList.add('on');
|
||||
element.classList.remove('off');
|
||||
} else {
|
||||
element.classList.add('off');
|
||||
element.classList.remove('on');
|
||||
}
|
||||
}
|
||||
|
||||
function updateCheckList(light) {
|
||||
const listItem = document.querySelector(`#statusCheckList${currentDeviceType} li[data-light="${light}"]`);
|
||||
if (listItem) {
|
||||
const checkMark = listItem.querySelector('.badge');
|
||||
checkMark.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
function checkAllTestsPassed() {
|
||||
allTestsPassed = Array.from(document.querySelectorAll(`#statusCheckList${currentDeviceType} .badge`))
|
||||
.every(badge => badge.style.display === 'inline-block');
|
||||
|
||||
// 更新测试结果
|
||||
updateTestResult();
|
||||
}
|
||||
|
||||
function resetCheckList() {
|
||||
lightHistory = {
|
||||
common: false,
|
||||
alarm: false,
|
||||
up: false,
|
||||
down: false,
|
||||
emergency_stop: false
|
||||
};
|
||||
allTestsPassed = false;
|
||||
|
||||
const deviceType = document.getElementById('deviceType').value;
|
||||
const checkList = document.getElementById(`statusCheckList${deviceType}`);
|
||||
if (checkList) {
|
||||
const badges = checkList.querySelectorAll('.badge');
|
||||
badges.forEach(badge => {
|
||||
badge.style.display = 'none';
|
||||
});
|
||||
}
|
||||
updateTestResult();
|
||||
}
|
||||
|
||||
function updateTestResult() {
|
||||
const rows = document.querySelectorAll('#resultTable tbody tr');
|
||||
rows.forEach(row => {
|
||||
const checkbox = row.querySelector('.form-check-input');
|
||||
const label = row.querySelector('.form-check-label');
|
||||
const pendingSpan = label.querySelector('.pending');
|
||||
const passSpan = label.querySelector('.pass');
|
||||
const failSpan = label.querySelector('.fail');
|
||||
|
||||
if (allTestsPassed) {
|
||||
checkbox.disabled = false;
|
||||
checkbox.checked = true;
|
||||
pendingSpan.style.display = 'none';
|
||||
passSpan.style.display = 'inline';
|
||||
failSpan.style.display = 'none';
|
||||
row.classList.remove('pending-row');
|
||||
row.classList.add('pass-row');
|
||||
row.classList.remove('fail-row');
|
||||
} else {
|
||||
checkbox.disabled = true;
|
||||
checkbox.checked = false;
|
||||
pendingSpan.style.display = 'inline';
|
||||
passSpan.style.display = 'none';
|
||||
failSpan.style.display = 'none';
|
||||
row.classList.add('pending-row');
|
||||
row.classList.remove('pass-row');
|
||||
row.classList.remove('fail-row');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addNewRow(button) {
|
||||
const table = document.getElementById('resultTable').getElementsByTagName('tbody')[0];
|
||||
const currentRow = button.closest('tr');
|
||||
const serialNumberInput = currentRow.querySelector('.serial-number');
|
||||
|
||||
if (serialNumberInput.value.trim() === '') {
|
||||
alert('请输入设备序列号');
|
||||
return;
|
||||
}
|
||||
|
||||
serialNumberInput.disabled = true;
|
||||
button.style.display = 'none';
|
||||
|
||||
const newRow = table.insertRow(table.rows.length);
|
||||
const rowIndex = table.rows.length - 1;
|
||||
|
||||
const cell1 = newRow.insertCell(0);
|
||||
const cell2 = newRow.insertCell(1);
|
||||
const cell3 = newRow.insertCell(2);
|
||||
|
||||
cell1.innerHTML = '<input type="text" class="form-control serial-number" placeholder="输入设备序列号">';
|
||||
cell2.innerHTML = `
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="result-${rowIndex}" ${allTestsPassed ? 'checked' : 'disabled'}>
|
||||
<label class="form-check-label" for="result-${rowIndex}">
|
||||
<span class="pending" ${allTestsPassed ? 'style="display: none;"' : ''}>待测试</span>
|
||||
<span class="pass" ${allTestsPassed ? '' : 'style="display: none;"'}>合格</span>
|
||||
<span class="fail" style="display: none;">不合格</span>
|
||||
</label>
|
||||
</div>`;
|
||||
cell3.innerHTML = '<button class="btn btn-primary add-row-btn" onclick="addNewRow(this)">增加下一条</button>';
|
||||
|
||||
updateTestResult();
|
||||
}
|
||||
|
||||
function updateResultStyle(row) {
|
||||
const checkbox = row.cells[1].querySelector('.form-check-input');
|
||||
const serialNumberInput = row.cells[0].querySelector('.serial-number');
|
||||
const label = checkbox.nextElementSibling;
|
||||
const pendingSpan = label.querySelector('.pending');
|
||||
const passSpan = label.querySelector('.pass');
|
||||
const failSpan = label.querySelector('.fail');
|
||||
|
||||
checkbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
row.classList.remove('fail-row');
|
||||
row.classList.add('pass-row');
|
||||
pendingSpan.style.display = 'none';
|
||||
passSpan.style.display = 'inline';
|
||||
failSpan.style.display = 'none';
|
||||
} else {
|
||||
row.classList.remove('pass-row');
|
||||
row.classList.add('fail-row');
|
||||
pendingSpan.style.display = 'none';
|
||||
passSpan.style.display = 'none';
|
||||
failSpan.style.display = 'inline';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportToExcel() {
|
||||
const table = document.getElementById('resultTable');
|
||||
const deviceType = document.getElementById('deviceType').value;
|
||||
const tester = document.getElementById('tester').value;
|
||||
const orderNumber = document.getElementById('orderNumber').value;
|
||||
const testDate = document.getElementById('testDate').value;
|
||||
|
||||
const data = [];
|
||||
|
||||
// 添加测试信息
|
||||
data.push(['设备型号', deviceType === '5' ? '五孔' : '四孔']);
|
||||
data.push(['检测人员', tester]);
|
||||
data.push(['订单编号', orderNumber]);
|
||||
data.push(['测试日期', testDate]);
|
||||
data.push([]); // 空行
|
||||
|
||||
const headers = ['设备序列号', '测试结果'];
|
||||
data.push(headers);
|
||||
|
||||
for (let i = 1; i < table.rows.length; i++) {
|
||||
const row = table.rows[i];
|
||||
const serialNumber = row.cells[0].querySelector('input').value;
|
||||
const testResult = row.cells[1].querySelector('input').checked ? '合格' : '不合格';
|
||||
data.push([serialNumber, testResult]);
|
||||
}
|
||||
|
||||
const wb = XLSX.utils.book_new();
|
||||
const ws = XLSX.utils.aoa_to_sheet(data);
|
||||
|
||||
const colWidth = [{ wch: 20 }, { wch: 20 }];
|
||||
ws['!cols'] = colWidth;
|
||||
|
||||
XLSX.utils.book_append_sheet(wb, ws, "测试结果");
|
||||
|
||||
XLSX.writeFile(wb, `测试结果_${orderNumber}_${testDate}.xlsx`);
|
||||
}
|
||||
|
||||
function startTest() {
|
||||
disableInfoInputs();
|
||||
|
||||
const deviceTypeSelect = document.getElementById('deviceType');
|
||||
if (deviceTypeSelect) {
|
||||
deviceTypeSelect.disabled = false;
|
||||
}
|
||||
|
||||
const firstSerialInput = document.getElementById('firstSerialNumber');
|
||||
if (firstSerialInput) {
|
||||
firstSerialInput.focus();
|
||||
firstSerialInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
|
||||
function disableInfoInputs() {
|
||||
const infoInputs = document.querySelectorAll('.info-input:not(#deviceType)');
|
||||
infoInputs.forEach(input => {
|
||||
input.disabled = true;
|
||||
});
|
||||
|
||||
const startTestBtn = document.getElementById('startTestBtn');
|
||||
if (startTestBtn) {
|
||||
startTestBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function setupDeviceTypeListener() {
|
||||
const deviceTypeSelect = document.getElementById('deviceType');
|
||||
if (deviceTypeSelect) {
|
||||
deviceTypeSelect.addEventListener('change', function() {
|
||||
currentDeviceType = this.value;
|
||||
updateDeviceTypeDisplay();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateDeviceTypeDisplay() {
|
||||
const deviceType = document.getElementById('deviceType').value;
|
||||
const fiveHoleLights = document.getElementById('fiveHoleLights');
|
||||
const fourHoleLights = document.getElementById('fourHoleLights');
|
||||
const statusCheckList5 = document.getElementById('statusCheckList5');
|
||||
const statusCheckList4 = document.getElementById('statusCheckList4');
|
||||
|
||||
if (deviceType === '5') {
|
||||
fiveHoleLights.style.display = 'flex';
|
||||
fourHoleLights.style.display = 'none';
|
||||
statusCheckList5.style.display = 'block';
|
||||
statusCheckList4.style.display = 'none';
|
||||
} else if (deviceType === '4') {
|
||||
fiveHoleLights.style.display = 'none';
|
||||
fourHoleLights.style.display = 'flex';
|
||||
statusCheckList5.style.display = 'none';
|
||||
statusCheckList4.style.display = 'block';
|
||||
} else {
|
||||
fiveHoleLights.style.display = 'none';
|
||||
fourHoleLights.style.display = 'none';
|
||||
statusCheckList5.style.display = 'none';
|
||||
statusCheckList4.style.display = 'none';
|
||||
}
|
||||
|
||||
resetCheckList();
|
||||
}
|
||||
|
||||
function setupPortSelection() {
|
||||
const selectPortBtn = document.getElementById('selectPortBtn');
|
||||
const confirmPortBtn = document.getElementById('confirmPortBtn');
|
||||
const portSelect = document.getElementById('portSelect');
|
||||
|
||||
selectPortBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await fetch('/get_ports');
|
||||
const ports = await response.json();
|
||||
|
||||
portSelect.innerHTML = '';
|
||||
if (ports.length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = '没有找到可用的串口';
|
||||
portSelect.appendChild(option);
|
||||
confirmPortBtn.disabled = true;
|
||||
} else {
|
||||
ports.forEach(port => {
|
||||
const option = document.createElement('option');
|
||||
option.value = port.device;
|
||||
option.textContent = `${port.device} - ${port.description}`;
|
||||
portSelect.appendChild(option);
|
||||
});
|
||||
confirmPortBtn.disabled = false;
|
||||
}
|
||||
|
||||
const portModal = new bootstrap.Modal(document.getElementById('portModal'));
|
||||
portModal.show();
|
||||
} catch (error) {
|
||||
console.error('获取串口列表失败:', error);
|
||||
alert('获取串口列表失败,请重试。');
|
||||
}
|
||||
});
|
||||
|
||||
confirmPortBtn.addEventListener('click', async () => {
|
||||
const selectedPort = portSelect.value;
|
||||
if (selectedPort) {
|
||||
try {
|
||||
const response = await fetch('/set_port', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ port: selectedPort }),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('串口设置成功');
|
||||
bootstrap.Modal.getInstance(document.getElementById('portModal')).hide();
|
||||
} else {
|
||||
alert('串口设置失败: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置串口失败:', error);
|
||||
alert('设置串口失败,请重试。');
|
||||
}
|
||||
} else {
|
||||
alert('请选择一个串口');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setupDeviceTypeListener();
|
||||
setupPortSelection();
|
||||
|
||||
const startTestBtn = document.getElementById('startTestBtn');
|
||||
if (startTestBtn) {
|
||||
startTestBtn.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
startTest();
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化设备类型显示
|
||||
updateDeviceTypeDisplay();
|
||||
|
||||
// 每100毫秒更新一次灯光状态
|
||||
setInterval(updateLights, 100);
|
||||
});
|
|
@ -0,0 +1,283 @@
|
|||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.card {
|
||||
height: 100%;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.form-select, .form-control {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.light-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.light-item {
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.light {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid #ccc; /* 统一边框颜色 */
|
||||
}
|
||||
|
||||
.light.off {
|
||||
background-color: #e0e0e0; /* 淡灰色 */
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.light.common.on {
|
||||
background-color: #007bff; /* 蓝色 */
|
||||
box-shadow: 0 0 20px 5px rgba(0, 123, 255, 0.5);
|
||||
}
|
||||
|
||||
.light.alarm.on {
|
||||
background-color: #007bff; /* 蓝色 */
|
||||
box-shadow: 0 0 20px 5px rgba(0, 123, 255, 0.5);
|
||||
}
|
||||
|
||||
.light.emergency_stop.on {
|
||||
background-color: #007bff; /* 蓝色 */
|
||||
box-shadow: 0 0 20px 5px rgba(0, 123, 255, 0.5);
|
||||
}
|
||||
|
||||
.light.up.on {
|
||||
background-color: #007bff; /* 蓝色 */
|
||||
box-shadow: 0 0 20px 5px rgba(0, 123, 255, 0.5);
|
||||
}
|
||||
|
||||
.light.down.on {
|
||||
background-color: #007bff; /* 蓝色 */
|
||||
box-shadow: 0 0 20px 5px rgba(0, 123, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Flatpickr 样式覆盖 */
|
||||
.flatpickr-input {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected {
|
||||
background: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected:hover {
|
||||
background: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
/* 测试结果表格样式 */
|
||||
#resultTable {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 12px;
|
||||
}
|
||||
|
||||
#resultTable thead th {
|
||||
border: none;
|
||||
background-color: #f8f9fa;
|
||||
color: #495057;
|
||||
font-weight: bold;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
#resultTable tbody tr {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#resultTable tbody tr:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
#resultTable td {
|
||||
vertical-align: middle;
|
||||
border: none;
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#resultTable .form-control,
|
||||
#resultTable .form-select {
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
/* 开关样式 */
|
||||
.form-check-input {
|
||||
width: 3em !important;
|
||||
height: 1.5em !important;
|
||||
margin-top: 0.25em;
|
||||
vertical-align: top;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.85%29'/%3e%3c/svg%3e");
|
||||
background-position: left center;
|
||||
border-radius: 2em;
|
||||
transition: background-position .15s ease-in-out;
|
||||
}
|
||||
|
||||
.form-check-input:checked {
|
||||
background-color: #198754;
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
.form-check-label {
|
||||
padding-left: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-check-label .pass,
|
||||
.form-check-label .fail {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-check-label .pass {
|
||||
color: #198754;
|
||||
}
|
||||
|
||||
.form-check-label .fail {
|
||||
color: #dc3545;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pass-row {
|
||||
background-color: rgba(25, 135, 84, 0.1) !important;
|
||||
}
|
||||
|
||||
.fail-row {
|
||||
background-color: rgba(220, 53, 69, 0.1) !important;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
#resultTable .btn-primary {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#resultTable .btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,123,255,0.3);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
border-color: #28a745;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #218838;
|
||||
border-color: #1e7e34;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(40, 167, 69, 0.3);
|
||||
}
|
||||
|
||||
.serial-number:disabled {
|
||||
background-color: #e9ecef;
|
||||
opacity: 1;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.add-row-btn {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.add-row-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* 将 #lightCheckList 替换为 #statusCheckList */
|
||||
#statusCheckList {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#statusCheckList .list-group-item {
|
||||
padding: 0.75rem 1rem;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
#statusCheckList .badge {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#statusCheckList .badge.bg-success {
|
||||
background-color: #28a745 !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #495057;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.col-md-8 {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.col-md-4 {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.col-md-8, .col-md-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加或修改以下样式 */
|
||||
.form-check-input:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.form-check-label .pending {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.form-check-label .pass {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.form-check-label .fail {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.pending-row {
|
||||
background-color: rgba(108, 117, 125, 0.1) !important;
|
||||
}
|
||||
|
||||
.pass-row {
|
||||
background-color: rgba(40, 167, 69, 0.1) !important;
|
||||
}
|
||||
|
||||
.fail-row {
|
||||
background-color: rgba(220, 53, 69, 0.1) !important;
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>设备测试监控</title>
|
||||
<!-- 引入 Bootstrap CSS -->
|
||||
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- 引入 Flatpickr CSS -->
|
||||
<link href="../static/css/flatpickr.min.css" rel="stylesheet">
|
||||
<!-- 引入自定义 CSS -->
|
||||
<link href="../static/styles.css" rel="stylesheet">
|
||||
<!-- 在 <head> 部分添加 Font Awesome 图标库 -->
|
||||
<link href="../static/css/all.min.css" rel="stylesheet">
|
||||
|
||||
<script src="../static/js/xlsx.full.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">设备测试监控</h1>
|
||||
|
||||
<!-- 信息区域 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">测试信息</h5>
|
||||
<div class="row">
|
||||
<!-- 现有的输入字段 -->
|
||||
<div class="col-md-3">
|
||||
<label for="deviceType" class="form-label">设备型号</label>
|
||||
<select class="form-select info-input" id="deviceType">
|
||||
<option selected>请选择...</option>
|
||||
<option value="4">四孔</option>
|
||||
<option value="5">五孔</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="tester" class="form-label">检测人员</label>
|
||||
<select class="form-select info-input" id="tester">
|
||||
<option selected>请选择...</option>
|
||||
<option value="邱东财">邱东财</option>
|
||||
<option value="陈翠芹">陈翠芹</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="orderNumber" class="form-label">订单编号</label>
|
||||
<input type="text" class="form-control info-input" id="orderNumber">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="testDate" class="form-label">测试日期</label>
|
||||
<input type="text" class="form-control flatpickr info-input" id="testDate">
|
||||
</div>
|
||||
<!-- 添加开始测试按钮 -->
|
||||
<div class="col-md-3 mb-3 mt-3">
|
||||
<button id="startTestBtn" class="btn btn-primary ">开始测试</button>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3 mt-3">
|
||||
<button id="selectPortBtn" class="btn btn-secondary">选择串口</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加模态框 -->
|
||||
<div class="modal fade" id="portModal" tabindex="-1" aria-labelledby="portModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="portModalLabel">选择串口</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<select class="form-select" id="portSelect">
|
||||
<!-- 串口选项将通过 JavaScript 动态添加 -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="confirmPortBtn">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4" style="height:300px;">
|
||||
<!-- 灯光状态监控 -->
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center mb-4">灯光状态监控</h5>
|
||||
<!-- 五孔灯光状态 -->
|
||||
<div id="fiveHoleLights" class="light-container">
|
||||
<div class="light-item">
|
||||
<div class="light common off" id="common-5"></div>
|
||||
<p>公共灯</p>
|
||||
</div>
|
||||
<div class="light-item">
|
||||
<div class="light alarm off" id="alarm-5"></div>
|
||||
<p>警铃灯</p>
|
||||
</div>
|
||||
<div class="light-item">
|
||||
<div class="light up off" id="up-5"></div>
|
||||
<p>上行灯</p>
|
||||
</div>
|
||||
<div class="light-item">
|
||||
<div class="light down off" id="down-5"></div>
|
||||
<p>下行灯</p>
|
||||
</div>
|
||||
<div class="light-item">
|
||||
<div class="light emergency_stop off" id="emergency_stop-5"></div>
|
||||
<p>急停灯</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 四孔灯光状态 -->
|
||||
<div id="fourHoleLights" class="light-container" style="display: none;">
|
||||
<div class="light-item">
|
||||
<div class="light common off" id="common-4"></div>
|
||||
<p>公共灯</p>
|
||||
</div>
|
||||
<div class="light-item">
|
||||
<div class="light emergency_stop off" id="emergency_stop-4"></div>
|
||||
<p>急停灯</p>
|
||||
</div>
|
||||
<div class="light-item">
|
||||
<div class="light up off" id="up-4"></div>
|
||||
<p>上行灯</p>
|
||||
</div>
|
||||
<div class="light-item">
|
||||
<div class="light down off" id="down-4"></div>
|
||||
<p>下行灯</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检测状态记录 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center mb-4">检测状态记录</h5>
|
||||
<ul class="list-group" id="statusCheckList5" style="display: none;">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="common">
|
||||
公共
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="alarm">
|
||||
警铃
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="up">
|
||||
上行
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="down">
|
||||
下行
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="emergency_stop">
|
||||
急停
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list-group" id="statusCheckList4" style="display: none;">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="common">
|
||||
公共
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="up">
|
||||
上行
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="down">
|
||||
下行
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-light="emergency_stop">
|
||||
急停
|
||||
<span class="badge bg-success rounded-pill" style="display: none;">✓</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 测试结果记录模块 -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center mb-4">测试结果记录</h5>
|
||||
<table class="table" id="resultTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>设备序列号</th>
|
||||
<th>测试结果</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text" class="form-control serial-number" id="firstSerialNumber" placeholder="输入设备序列号"></td>
|
||||
<td>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="result-0" disabled>
|
||||
<label class="form-check-label" for="result-0">
|
||||
<span class="pending">待测试</span>
|
||||
<span class="pass" style="display: none;">合格</span>
|
||||
<span class="fail" style="display: none;">不合格</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td><button class="btn btn-primary add-row-btn" onclick="addNewRow(this)">增加下一条</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-end mt-3">
|
||||
<button class="btn btn-success" onclick="exportToExcel()">
|
||||
<i class="fas fa-file-excel me-2"></i>导出结果到Excel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 引入 Bootstrap JS 和 Popper.js -->
|
||||
<script src="../static/js/popper.min.js"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- 引入 Flatpickr JS -->
|
||||
<script src="../static/js/flatpickr.js"></script>
|
||||
<!-- 引入 Flatpickr 中文语言包 -->
|
||||
<script src="../static/js/zh.js"></script>
|
||||
<script src="../static/main.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
flatpickr("#testDate", {
|
||||
dateFormat: "Y-m-d",
|
||||
locale: "zh",
|
||||
disableMobile: "true"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue