// 拍照功能
document.getElementById('snapBtn').addEventListener('click', async function() {
const button = this;
const originalText = button.innerHTML;
const originalDisabled = button.disabled;
try {
// 检查是否是"确认明码与暗码内容"测试项
const subcategoryName = "{{ test_data.subcategory_name }}";
let checkRegion = true;
let checkText = true;
let color = null; // 新增颜色选择变量
// 湿敏指示卡检测的特殊处理
if (subcategoryName && subcategoryName.includes('湿敏指示卡')) {
color = await showColorSelectionDialog();
if (!color ) {
alert('请选择湿敏指示卡颜色');
return;
}
}
// 确认明码与暗码内容的处理
if (subcategoryName && subcategoryName.includes('确认明码与暗码内容')) {
// 显示选项弹窗
const result = await showOptionsDialog();
checkRegion = result.checkRegion;
checkText = result.checkText;
}
<!-- 结束-->
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 检测中...';
button.disabled = true;
const area = getScanArea();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
[canvas.width, canvas.height] = [video.videoWidth, video.videoHeight];
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = canvas.toDataURL('image/jpeg', 0.95);
const payload = {
image: imageData,
scan_area: area,
order_id: test_orderId,
model: test_model,
station_name: test_stationName,
subcategory_name: test_subcategoryName,
check_region: checkRegion, // 新增参数
check_text: checkText, // 新增参数
};
// 只有湿敏指示卡检测才添加color参数
if (color) {
payload.color = color;
}
const response = await fetch('/api/save_image', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error || '保存失败');
}
// 开始轮询状态
const taskId = data.task_id;
let retries = 0;
const maxRetries = 30;
const checkResult = async () => {
const res = await fetch(`/api/check_status/${taskId}`);
const result = await res.json();
if (!result.completed) {
if (retries++ < maxRetries) {
setTimeout(checkResult, 2000);
} else {
throw new Error('检测超时');
}
} else {
if (result.status === 'Pass'|| result.status === 'Fail') {
// 跳转到测试列表
const urlParams = new URLSearchParams(window.location.search);
const orderId = {{ test_data.order_id }};
const orderNumber = "{{ test_data.order_number }}";
const line = "{{ test_data.line }}";
const model = "{{ test_data.model }}";
const test_stationId = urlParams.get('test_stationId');
window.location.href = `/station_list/?order_id=${orderId}&order_number=${orderNumber}&line=${line}&model=${model}&station_id=${test_stationId}`;
} else {
alert(`检测异常: ${result.message}`);
}
}
};
// 初始检查
checkResult();
} catch (error) {
alert('操作失败: ' + error.message);
} finally {
button.innerHTML = originalText;
button.disabled = originalDisabled;
}
});@app.route('/api/save_image', methods=['POST'])
def save_image():
"""处理图片上传和检测逻辑"""
logger.info("收到保存图片请求")
data = request.json
# 强制类型校验
if not isinstance(data.get('image'), str):
logger.error("image 必须是 Base64 字符串")
return {"error": "image 必须是 Base64 字符串"}, 400
try:
# 解析数据
image_data = data['image']
area_data = data['scan_area']
order_id = data['order_id']
model_name = data['model']
station_name = unquote(data['station_name'])
subcategory_name = unquote(data['subcategory_name'])
check_region = data.get('check_region', True) # 默认为True
check_text = data.get('check_text', True) # 默认为True
if data['color'] is None:
color = data['color'] # 新增颜色参数
logger.info(f"处理图片: order={order_id}, station={station_name}, subcategory={subcategory_name}")
logger.info(f"检测选项: check_region={check_region}, check_text={check_text}, color={color}")
# 如果是湿敏指示卡检测且未选择颜色
if '湿敏指示卡' in subcategory_name and not color:
return jsonify({
"success": False,
"error": "湿敏指示卡检测必须选择颜色",
"required_color": True
}), 400
# 检查Base64头部是否符合图像格式
if not image_data.startswith(('data:image/jpeg', 'data:image/png')):
logger.error("无效的Base64图像数据")
return jsonify(error="无效的Base64图像数据"), 400
try:
img_bytes = base64.b64decode(image_data.split(',')[1]) # 去除头部 )
# 解码前校验图像完整性
img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR)
if img is None:
logger.error("无效的JPEG图像数据")
return jsonify(error="无效的JPEG图像数据"), 400
# 检查图像是否为单一片段(排除拼图)
if img.shape[0] % 2 != 0 or img.shape[1] % 2 != 0:
logger.warning("图像分辨率异常")
return jsonify(error="图像分辨率异常"), 400
except Exception as e:
logger.error(f"图像处理失败: {str(e)}")
return jsonify(error=f"图像处理失败: {str(e)}"), 500
# 裁剪扫码区域
x = max(0, min(int(area_data['x']), img.shape[1] - 1))
y = max(0, min(int(area_data['y']), img.shape[0] - 1))
w = max(0, min(int(area_data['width']), img.shape[1] - x))
h = max(0, min(int(area_data['height']), img.shape[0] - y))
# x, y, w, h = int(area_data['x']), int(area_data['y']), int(area_data['width']), int(area_data['height'])
logger.info(f"扫描区域: x={x}, y={y}, w={w}, h={h}")
filename = generate_filename(order_id,station_name,subcategory_name)
save_path = os.path.join('uploads', filename)
# with open(save_path, 'wb') as f:
# f.write(img_bytes)
# cv2.rectangle(img, (x, y), (x+ w, y + h), (0, 255, 0), 3)
# roi_img1 = img[y:y + h, x:x + w]
# 保存带标记的图像
cv2.imwrite(save_path, img) #存整图
# cv2.imwrite(save_path, roi_img1)
logger.info(f"图片保存成功: {save_path}")
# 调用检测程序(下一步实现)
# 生成唯一任务ID
task_id = str(uuid4())
# 初始化任务状态
task_status[task_id] = {
'completed': False,
'status': 'Processing',
'message': '检测进行中'
}
logger.info(f"创建检测任务: task_id={task_id}")
# 启动异步线程处理检测
Thread(target=async_detection_task,
args=( task_id, order_id, station_name, subcategory_name,
img, x, y, w, h,filename,model_name,check_region,
check_text,color)).start()
# 返回任务ID
return jsonify({
'success': True,
'task_id': task_id,
'status': 'Processing'
})
except Exception as e:
logger.error(f"保存图片异常: {str(e)}")
logger.error(traceback.format_exc())
return jsonify({"success": False,'status': 'Fail', "error": str(e)}) 2025-08-18 16:25:32,924 - ERROR - 保存图片异常: 'color'
2025-08-18 16:25:32,925 - ERROR - Traceback (most recent call last):
File "D:\ME_project\SMT_ProcessandProduction_FAI\app.py", line 713, in save_image
if data['color'] is None:
KeyError: 'color'
最新发布