tien_nemo

test load data

......@@ -20,7 +20,7 @@ class OcrController extends Controller
$request->validate([
'customer_name_text' => 'required|string',
'customer_name_xy' => 'required|string',
'tpl_name' => 'unique:mst_template',
'template_name' => 'unique:mst_template,tpl_name',
]);
// Lưu vào bảng mst_template
......@@ -50,7 +50,7 @@ class OcrController extends Controller
$imgPath = ("image/data_picking_detail_1754967679.jpg");
$templateName = 'nemo_4';
$templateName = 'nemo';
/// Lấy từ request hoặc mặc định
......
......@@ -21,8 +21,20 @@
cursor: pointer;
}
.bbox.active {
/*border-color: #2196F3;*/
background-color: rgb(33 243 132 / 30%);
border-color: #199601 !important;
background-color: rgba(25, 150, 1, 0.4) !important;
}
.bbox.focus-highlight {
animation: focusPulse 2s ease-in-out;
border-color: #ff6b35 !important;
background-color: rgba(255, 107, 53, 0.4) !important;
}
@keyframes focusPulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
select {
position: absolute;
......@@ -120,6 +132,7 @@
<label>@{{ field.label }}</label>
<input v-model="formData[field.value]"
@focus="highlightField(field.value)"
@click="onInputClick(field.value)"
:readonly="field.value === 'customer_name' && !hasCustomerNameXY"
>
</div>
......@@ -167,6 +180,56 @@
}
},
methods: {
// Map field cho box (không set active, chỉ dùng để load data từ DB)
mapFieldToBox(index, fieldName, text = null) {
if (index == null) return;
// Xóa fieldName ở box khác
this.ocrData.forEach((box, i) => {
if (i !== index && box.field === fieldName) {
box.field = null;
box.field_xy = null;
}
});
// Nếu box này từng gán field khác thì bỏ
const prev = this.ocrData[index].field;
if (prev && prev !== fieldName) {
if (prev === 'customer_name') {
this.hasCustomerNameXY = false;
this.customer_name_xy = '';
}
this.ocrData[index].field = null;
this.ocrData[index].field_xy = null;
}
// Gán field mới
const bbox = this.ocrData[index].bbox; // tọa độ OCR gốc [x1, y1, x2, y2]
console.log('1111111111111111',bbox);
const x1 = bbox[0];
const y1 = bbox[1];
const w = bbox[2];
const h = bbox[3];
const xyStr = `${x1},${y1},${w},${h}`;
this.ocrData[index].field = fieldName;
this.ocrData[index].field_xy = xyStr;
// Set text
this.formData[fieldName] = (text !== null ? text : (this.ocrData[index].text || '')).trim();
// KHÔNG set active index (không focus)
// Nếu là customer_name
if (fieldName === 'customer_name') {
this.hasCustomerNameXY = true;
this.customer_name_xy = xyStr;
}
},
// Assign field và set active (dùng khi user tương tác)
assignFieldToBox(index, fieldName, text = null) {
if (index == null) return;
......@@ -206,7 +269,7 @@
// Set text
this.formData[fieldName] = (text !== null ? text : (this.ocrData[index].text || '')).trim();
// Active index
// Active index (focus vào box này)
this.activeIndex = index;
// Nếu là customer_name
......@@ -309,6 +372,77 @@
this.pdfImageUrl = data.pdfImageUrl;
this.formData = data.formData;
this.fieldOptions = data.fieldOptions;
// Tự động map field cho các box OCR dựa trên formData đã load
this.autoMapFieldsFromFormData();
},
// Tự động map field cho các box OCR dựa trên formData đã load từ DB
autoMapFieldsFromFormData() {
// Duyệt qua tất cả các field trong formData
Object.keys(this.formData).forEach(fieldName => {
const fieldValue = this.formData[fieldName];
// Chỉ xử lý các field có giá trị (không phải template_name)
if (fieldValue && fieldValue.trim() && fieldName !== 'template_name') {
// Tìm box OCR phù hợp nhất để map
const bestMatchIndex = this.findBestMatchingBox(fieldName, fieldValue);
if (bestMatchIndex !== -1) {
// Chỉ map field, không set active (không focus)
this.mapFieldToBox(bestMatchIndex, fieldName, fieldValue);
}
}
});
},
// Tìm box OCR phù hợp nhất để map với field
findBestMatchingBox(fieldName, fieldValue) {
let bestMatchIndex = -1;
let bestScore = 0;
this.ocrData.forEach((item, index) => {
if (item.isDeleted) return;
// Nếu box này đã được map field khác, bỏ qua
if (item.field && item.field !== fieldName) return;
// Tính điểm phù hợp dựa trên text
const text = item.text || '';
const score = this.calculateTextSimilarity(text, fieldValue);
if (score > bestScore) {
bestScore = score;
bestMatchIndex = index;
}
});
// Chỉ map nếu điểm phù hợp đủ cao (ví dụ > 0.5)
return bestScore > 0.5 ? bestMatchIndex : -1;
},
// Tính điểm tương đồng giữa 2 text
calculateTextSimilarity(text1, text2) {
if (!text1 || !text2) return 0;
const t1 = text1.toLowerCase().trim();
const t2 = text2.toLowerCase().trim();
// Nếu text giống hệt nhau
if (t1 === t2) return 1.0;
// Nếu một text là subset của text kia
if (t1.includes(t2) || t2.includes(t1)) return 0.8;
// Tính điểm dựa trên số ký tự giống nhau
let commonChars = 0;
const minLength = Math.min(t1.length, t2.length);
for (let i = 0; i < minLength; i++) {
if (t1[i] === t2[i]) commonChars++;
}
return commonChars / Math.max(t1.length, t2.length);
},
onImageLoad() {
const img = this.$refs.pdfImage;
......@@ -348,7 +482,83 @@
break;
}
}
this.activeIndex = idx === -1 ? null : idx;
if (idx !== -1) {
// Set active index (chuyển trạng thái active và màu xanh)
this.activeIndex = idx;
// Scroll đến box tương ứng
this.scrollToBox(idx);
// Focus vào box để người dùng thấy rõ
this.focusOnBox(idx);
} else {
this.activeIndex = null;
}
},
// Scroll đến box tương ứng
scrollToBox(index) {
if (!this.$refs.pdfContainer || index < 0 || index >= this.ocrData.length) return;
const item = this.ocrData[index];
if (!item || item.isDeleted) return;
// Tính vị trí hiển thị của box
const [x1, y1, x2, y2] = item.bbox;
if (!this.imageWidth || !this.imageHeight || !this.$refs.pdfImage) return;
const displayedWidth = this.$refs.pdfImage.clientWidth;
const displayedHeight = this.$refs.pdfImage.clientHeight;
const scaleX = displayedWidth / this.imageWidth;
const scaleY = displayedHeight / this.imageHeight;
const displayX = Math.round(x1 * scaleX);
const displayY = Math.round(y1 * scaleY);
// Scroll đến vị trí box
const container = this.$refs.pdfContainer;
const containerRect = container.getBoundingClientRect();
const scrollTop = container.scrollTop;
const scrollLeft = container.scrollLeft;
// Tính vị trí scroll để box nằm ở giữa viewport
const targetScrollTop = scrollTop + displayY - (containerRect.height / 2);
const targetScrollLeft = scrollLeft + displayX - (containerRect.width / 2);
container.scrollTo({
top: Math.max(0, targetScrollTop),
left: Math.max(0, targetScrollLeft),
behavior: 'smooth'
});
},
// Focus vào box (thêm hiệu ứng nhấp nháy)
focusOnBox(index) {
if (index < 0 || index >= this.ocrData.length) return;
const item = this.ocrData[index];
if (!item || item.isDeleted) return;
// Thêm class để tạo hiệu ứng focus
this.$nextTick(() => {
const boxElement = document.querySelector(`[data-field="${item.field}"]`);
if (boxElement) {
boxElement.classList.add('focus-highlight');
setTimeout(() => {
boxElement.classList.remove('focus-highlight');
}, 2000);
}
});
},
// Xử lý khi click vào input
onInputClick(fieldName) {
// Kiểm tra xem field này có data không
const fieldValue = this.formData[fieldName];
if (fieldValue && fieldValue.trim()) {
// Nếu có data, highlight và focus vào box tương ứng
// Chỉ khi click vào input mới focus và chuyển trạng thái active
this.highlightField(fieldName);
}
},
startSelect(e) {
if (this.isMappingManually || e.button !== 0) return;
......