tien_nemo

test1

...@@ -65,8 +65,8 @@ ...@@ -65,8 +65,8 @@
65 </div> 65 </div>
66 66
67 <div v-if="item.isManual && item.showDelete" 67 <div v-if="item.isManual && item.showDelete"
68 - class="delete-btn" 68 + class="delete-btn"
69 - @click.stop="deleteBox(index)"> 69 + @click.stop="deleteBox(index)">
70 <img src="{{ asset('icon/btn-delete.png') }}" alt="delete box" style="width: 20px; height: 20px;"> 70 <img src="{{ asset('icon/btn-delete.png') }}" alt="delete box" style="width: 20px; height: 20px;">
71 </div> 71 </div>
72 </div> 72 </div>
...@@ -84,9 +84,12 @@ ...@@ -84,9 +84,12 @@
84 84
85 <!-- Dropdown thủ công --> 85 <!-- Dropdown thủ công -->
86 <select v-if="selectBox.showDropdown" 86 <select v-if="selectBox.showDropdown"
87 - :style="{ left: selectBox.x + 'px', top: (selectBox.y + selectBox.height) + 'px' }" 87 + class="manual-select"
88 + :style="{ left: selectBox.x + 'px', top: (selectBox.y + selectBox.height) + 'px', zIndex: 10000, position: 'absolute' }"
88 v-model="manualField" 89 v-model="manualField"
89 @change="applyManualMapping" 90 @change="applyManualMapping"
91 + @mousedown.stop
92 + @mouseup.stop
90 @click.stop 93 @click.stop
91 > 94 >
92 <option disabled value="">-- Chọn trường dữ liệu --</option> 95 <option disabled value="">-- Chọn trường dữ liệu --</option>
...@@ -127,7 +130,8 @@ ...@@ -127,7 +130,8 @@
127 hasCustomerNameXY: false, 130 hasCustomerNameXY: false,
128 ocrData: [], 131 ocrData: [],
129 selectBox: { show: false, showDropdown: false, x: 0, y: 0, width: 0, height: 0, startX: 0, startY: 0 }, 132 selectBox: { show: false, showDropdown: false, x: 0, y: 0, width: 0, height: 0, startX: 0, startY: 0 },
130 - manualIndex: null 133 + manualIndex: null,
134 + suppressNextDocumentClick: false
131 } 135 }
132 }, 136 },
133 created() { 137 created() {
...@@ -138,14 +142,35 @@ ...@@ -138,14 +142,35 @@
138 }, 142 },
139 mounted() { 143 mounted() {
140 this.loadOCRData(); 144 this.loadOCRData();
141 - //Thêm event listener để xóa focus khi click ra ngoài 145 + // Thêm event listener để xử lý click ra ngoài
142 document.addEventListener('click', (e) => { 146 document.addEventListener('click', (e) => {
143 - if ( 147 + // Bỏ qua click ngay sau khi thả chuột tạo box
144 - !e.target.closest('.right-panel') && 148 + if (this.suppressNextDocumentClick) {
145 - !e.target.closest('.bbox') && 149 + this.suppressNextDocumentClick = false;
146 - !e.target.closest('select') 150 + return;
147 - ) { 151 + }
148 - this.removeAllFocus(); 152 + // Nếu đang có box thủ công mới quét và chưa chọn field
153 + if (this.manualIndex !== null) {
154 + const currentIdx = this.manualIndex;
155 + const currentBox = this.ocrData[currentIdx];
156 + if (currentBox && currentBox.isManual && !currentBox.field && !currentBox.isDeleted) {
157 + const bboxEl = e.target.closest('.bbox');
158 + const manualSelectEl = e.target.closest('.manual-select');
159 + const clickedInsideCurrentBox = bboxEl && String(bboxEl.getAttribute('data-index')) === String(currentIdx);
160 + const clickedInsideManualSelect = !!manualSelectEl;
161 + if (!clickedInsideCurrentBox && !clickedInsideManualSelect) {
162 + // Click ra ngoài box/ select → xóa box thủ công vừa quét
163 + this.deleteBox(currentIdx);
164 + // Đóng dropdown thủ công nếu còn mở
165 + this.selectBox.show = false;
166 + this.selectBox.showDropdown = false;
167 + this.isMappingManually = false;
168 + this.manualField = "";
169 + this.manualIndex = null;
170 + this.activeIndex = null;
171 + this.selectingIndex = null;
172 + }
173 + }
149 } 174 }
150 }); 175 });
151 }, 176 },
...@@ -283,6 +308,7 @@ ...@@ -283,6 +308,7 @@
283 this.manualIndex = null; 308 this.manualIndex = null;
284 } 309 }
285 this.selectingIndex = null; 310 this.selectingIndex = null;
311 + this.activeIndex = null;
286 } 312 }
287 }, 313 },
288 // Xử lý data sau khi image đã load 314 // Xử lý data sau khi image đã load
...@@ -426,6 +452,9 @@ ...@@ -426,6 +452,9 @@
426 if (fieldValue && fieldValue.trim()) { 452 if (fieldValue && fieldValue.trim()) {
427 // Nếu có data từ DB, highlight và focus vào box tương ứng 453 // Nếu có data từ DB, highlight và focus vào box tương ứng
428 this.highlightField(fieldName); 454 this.highlightField(fieldName);
455 +
456 + // Không ẩn nút xóa các box khác để tránh nhầm lẫn
457 + // Chỉ highlight box tương ứng
429 } 458 }
430 }, 459 },
431 // Xóa tất cả focus 460 // Xóa tất cả focus
...@@ -433,20 +462,28 @@ ...@@ -433,20 +462,28 @@
433 // Reset active index (chỉ mất màu xanh, không xóa box) 462 // Reset active index (chỉ mất màu xanh, không xóa box)
434 this.activeIndex = null; 463 this.activeIndex = null;
435 this.selectingIndex = null; 464 this.selectingIndex = null;
436 - // Đảm bảo tất cả box OCR đều hiển thị (chỉ ẩn border khi cần thiết) 465 +
437 - this.ocrData.forEach(item => { 466 + // Không ẩn nút xóa các box để tránh nhầm lẫn
438 - // if (!item.isManual && item.hideBorder) { 467 + // Chỉ reset focus
439 - // item.hideBorder = true; 468 + },
440 - // } 469 +
441 - if (item.isManual) { 470 + // Xóa box quét chọn chưa hoàn thành
442 - if (!item.showDelete) { 471 + removeIncompleteBoxes() {
443 - item.isDeleted = true; 472 + // Xóa box quét chọn chưa hoàn thành (chưa có field)
444 - } else { 473 + this.ocrData = this.ocrData.filter(item => {
445 - item.hideBorder = false; 474 + if (item.isManual && !item.field && item.showDelete) {
446 - } 475 + // Chỉ xóa box manual chưa có field
447 - // Hiển thị lại border cho manual box 476 + return false;
448 } 477 }
478 + return true;
449 }); 479 });
480 +
481 + // Reset trạng thái quét chọn
482 + this.selectBox.show = false;
483 + this.selectBox.showDropdown = false;
484 + this.isMappingManually = false;
485 + this.manualField = "";
486 + this.manualIndex = null;
450 }, 487 },
451 // Xử lý khi click vào box 488 // Xử lý khi click vào box
452 onBoxClick(index) { 489 onBoxClick(index) {
...@@ -459,11 +496,21 @@ ...@@ -459,11 +496,21 @@
459 const isDataOverridden = item.field && isFromDB && 496 const isDataOverridden = item.field && isFromDB &&
460 this.formData[item.field] !== this.dataMapping[item.field].text; 497 this.formData[item.field] !== this.dataMapping[item.field].text;
461 498
499 + // Set active index và hiển thị nút xóa cho box được click
500 + this.activeIndex = index;
501 +
502 + // Hiển thị nút xóa cho box được click (nếu là manual box)
503 + if (item.isManual) {
504 + item.showDelete = true;
505 + }
506 +
507 + // Không ẩn nút xóa các box khác để tránh nhầm lẫn
508 + // Chỉ hiển thị nút xóa cho box được click
509 +
462 if (item.isManual) { 510 if (item.isManual) {
463 // Manual box 511 // Manual box
464 if (isFromDB && !isDataOverridden) { 512 if (isFromDB && !isDataOverridden) {
465 // Manual box từ DB chưa ghi đè, KHÔNG cho chọn option 513 // Manual box từ DB chưa ghi đè, KHÔNG cho chọn option
466 - this.activeIndex = index;
467 this.selectingIndex = null; 514 this.selectingIndex = null;
468 } else { 515 } else {
469 // Manual box từ DB có data ghi đè HOẶC manual box bình thường, CHO PHÉP chọn option 516 // Manual box từ DB có data ghi đè HOẶC manual box bình thường, CHO PHÉP chọn option
...@@ -473,7 +520,6 @@ ...@@ -473,7 +520,6 @@
473 // Box OCR có field 520 // Box OCR có field
474 if (isFromDB && !isDataOverridden) { 521 if (isFromDB && !isDataOverridden) {
475 // Box có field từ DB chưa ghi đè, KHÔNG cho chọn option 522 // Box có field từ DB chưa ghi đè, KHÔNG cho chọn option
476 - this.activeIndex = index;
477 this.selectingIndex = null; 523 this.selectingIndex = null;
478 } else { 524 } else {
479 // Box có field từ DB đã ghi đè HOẶC field mới, CHO PHÉP chọn option 525 // Box có field từ DB đã ghi đè HOẶC field mới, CHO PHÉP chọn option
...@@ -485,7 +531,9 @@ ...@@ -485,7 +531,9 @@
485 } 531 }
486 }, 532 },
487 startSelect(e) { 533 startSelect(e) {
488 - if (this.isMappingManually || e.button !== 0) return; 534 + if (e.button !== 0) return; // Chỉ cho phép left click
535 + // Bỏ qua nếu click trên dropdown thủ công
536 + if (e.target.closest && e.target.closest('.manual-select')) return;
489 this.isSelecting = true; 537 this.isSelecting = true;
490 const rect = this.$refs.pdfContainer.getBoundingClientRect(); 538 const rect = this.$refs.pdfContainer.getBoundingClientRect();
491 this.selectBox.startX = e.clientX - rect.left; 539 this.selectBox.startX = e.clientX - rect.left;
...@@ -550,7 +598,7 @@ ...@@ -550,7 +598,7 @@
550 bbox: origBbox, 598 bbox: origBbox,
551 field: "", 599 field: "",
552 isManual: true, 600 isManual: true,
553 - showDelete: true, 601 + showDelete: true, // Hiển thị nút xóa ngay khi quét chọn
554 isDeleted: false, 602 isDeleted: false,
555 hideBorder: false 603 hideBorder: false
556 }); 604 });
...@@ -559,8 +607,15 @@ ...@@ -559,8 +607,15 @@
559 this.isMappingManually = true; 607 this.isMappingManually = true;
560 this.selectBox.showDropdown = true; 608 this.selectBox.showDropdown = true;
561 609
562 - e.stopPropagation(); 610 + // Set active index cho box mới tạo
563 - e.preventDefault(); 611 + this.activeIndex = this.ocrData.length - 1;
612 +
613 + // Sau khi thả chuột, bỏ qua click document kế tiếp
614 + this.suppressNextDocumentClick = true;
615 +
616 + // Không stopPropagation để event có thể lan truyền bình thường
617 + // e.stopPropagation();
618 + // e.preventDefault();
564 619
565 }, 620 },
566 applyMapping() { 621 applyMapping() {
...@@ -649,6 +704,7 @@ ...@@ -649,6 +704,7 @@
649 const finalText = combinedText.join(" "); 704 const finalText = combinedText.join(" ");
650 705
651 this.ocrData[manualIndex].field = this.manualField; 706 this.ocrData[manualIndex].field = this.manualField;
707 + // this.ocrData[manualIndex].showDelete = true; // Hiển thị nút xóa sau khi hoàn thành mapping
652 708
653 console.log('123',this.ocrData[manualIndex]) 709 console.log('123',this.ocrData[manualIndex])
654 this.formData[this.manualField] = finalText.trim(); 710 this.formData[this.manualField] = finalText.trim();
...@@ -670,6 +726,9 @@ ...@@ -670,6 +726,9 @@
670 this.manualField = ""; 726 this.manualField = "";
671 this.manualIndex = null; 727 this.manualIndex = null;
672 728
729 + // Giữ nguyên nút xóa cho box vừa hoàn thành
730 + // Không ẩn nút xóa các box khác để tránh nhầm lẫn
731 +
673 }, 732 },
674 isBoxInside(inner, outer) { 733 isBoxInside(inner, outer) {
675 // inner: bbox của OCR item [x1, y1, x2, y2] 734 // inner: bbox của OCR item [x1, y1, x2, y2]
...@@ -690,7 +749,7 @@ ...@@ -690,7 +749,7 @@
690 inner[3] < outer[1] || // inner.bottom < outer.top → hoàn toàn phía trên 749 inner[3] < outer[1] || // inner.bottom < outer.top → hoàn toàn phía trên
691 inner[1] > outer[3] // inner.top > outer.bottom → hoàn toàn phía dưới 750 inner[1] > outer[3] // inner.top > outer.bottom → hoàn toàn phía dưới
692 ); 751 );
693 - // console.log(`isBoxInside: inner=${inner}, outer=${outer}, isFullyInside=${isFullyInside}, isOverlapping=${isOverlapping}`); 752 + // console.log(`isBoxInside: inner=${inner}, outer=${outer}, isFullyInside=${isFullyInside}, isOverlapping=${isOverlapping}`);
694 // Trả về true nếu box OCR nằm hoàn toàn trong hoặc giao nhau đáng kể 753 // Trả về true nếu box OCR nằm hoàn toàn trong hoặc giao nhau đáng kể
695 return isFullyInside || isOverlapping; 754 return isFullyInside || isOverlapping;
696 }, 755 },
......