tien_nemo

test ok

...@@ -25,12 +25,6 @@ ...@@ -25,12 +25,6 @@
25 background-color: rgba(25, 150, 1, 0.4) !important; 25 background-color: rgba(25, 150, 1, 0.4) !important;
26 } 26 }
27 27
28 - .bbox.focus-highlight {
29 - /*animation: focusPulse 2s ease-in-out;*/
30 - border-color: #ff6b35 !important;
31 - /*background-color: rgba(255, 107, 53, 0.4) !important;*/
32 - }
33 -
34 @keyframes focusPulse { 28 @keyframes focusPulse {
35 0% { transform: scale(1); } 29 0% { transform: scale(1); }
36 50% { transform: scale(1.05); } 30 50% { transform: scale(1.05); }
...@@ -95,7 +89,7 @@ ...@@ -95,7 +89,7 @@
95 :class="{ active: index === activeIndex }" 89 :class="{ active: index === activeIndex }"
96 :data-field="item.field" 90 :data-field="item.field"
97 :style="getBoxStyle(item, index)" 91 :style="getBoxStyle(item, index)"
98 - @click="selectingIndex = index"> 92 + @click="onBoxClick(index)">
99 93
100 <button v-if="item.isManual && item.showDelete" 94 <button v-if="item.isManual && item.showDelete"
101 class="delete-btn" 95 class="delete-btn"
...@@ -104,7 +98,7 @@ ...@@ -104,7 +98,7 @@
104 98
105 99
106 <!-- Dropdown OCR --> 100 <!-- Dropdown OCR -->
107 - <select v-if="selectingIndex !== null && ocrData[selectingIndex].isManual" 101 + <select v-if="selectingIndex !== null && ocrData[selectingIndex]"
108 :style="getSelectStyle(ocrData[selectingIndex])" 102 :style="getSelectStyle(ocrData[selectingIndex])"
109 v-model="ocrData[selectingIndex].field" 103 v-model="ocrData[selectingIndex].field"
110 @change="applyMapping" 104 @change="applyMapping"
...@@ -133,7 +127,7 @@ ...@@ -133,7 +127,7 @@
133 <input v-model="formData[field.value]" 127 <input v-model="formData[field.value]"
134 @focus="highlightField(field.value)" 128 @focus="highlightField(field.value)"
135 @click="onInputClick(field.value)" 129 @click="onInputClick(field.value)"
136 -{{-- @blur="removeManualBoxes"--}} 130 + @blur="onInputBlur(field.value)"
137 :readonly="field.value === 'customer_name' && !hasCustomerNameXY" 131 :readonly="field.value === 'customer_name' && !hasCustomerNameXY"
138 > 132 >
139 </div> 133 </div>
...@@ -174,6 +168,14 @@ ...@@ -174,6 +168,14 @@
174 }, 168 },
175 mounted() { 169 mounted() {
176 this.loadOCRData(); 170 this.loadOCRData();
171 +
172 + // Thêm event listener để xóa focus khi click ra ngoài
173 + document.addEventListener('click', (e) => {
174 + // Nếu click không phải vào input hoặc box
175 + if (!e.target.closest('.left-panel') && !e.target.closest('.bbox')) {
176 + this.removeAllFocus();
177 + }
178 + });
177 }, 179 },
178 computed: { 180 computed: {
179 fieldData() { 181 fieldData() {
...@@ -190,27 +192,33 @@ ...@@ -190,27 +192,33 @@
190 mapFieldToBox(index, fieldName, text = null) { 192 mapFieldToBox(index, fieldName, text = null) {
191 if (index == null) return; 193 if (index == null) return;
192 194
193 - // Xóa fieldName ở box khác đảm bảo mỗi field chỉ gán cho 1 box duy nhất 195 + // Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại
194 - this.ocrData.forEach((box, i) => { 196 + this.ocrData = this.ocrData.filter((box, i) => {
195 - if (i !== index && box.field === fieldName) { 197 + if (i === index) return true; // Giữ lại box hiện tại
196 - box.field = null; 198 + if (box.field === fieldName) {
197 - box.field_xy = null; 199 + // Xóa box có field trùng lặp
200 + return false;
198 } 201 }
202 + return true; // Giữ lại các box khác
199 }); 203 });
200 204
205 + // Cập nhật lại index sau khi filter
206 + const newIndex = this.ocrData.findIndex(box => box.bbox === this.ocrData[index]?.bbox);
207 + if (newIndex === -1) return;
208 +
201 // Nếu box này từng gán field khác thì bỏ reset flag và tọa độ liên quan. 209 // Nếu box này từng gán field khác thì bỏ reset flag và tọa độ liên quan.
202 - const prev = this.ocrData[index].field; 210 + const prev = this.ocrData[newIndex].field;
203 if (prev && prev !== fieldName) { 211 if (prev && prev !== fieldName) {
204 if (prev === 'customer_name') { 212 if (prev === 'customer_name') {
205 this.hasCustomerNameXY = false; 213 this.hasCustomerNameXY = false;
206 this.customer_name_xy = ''; 214 this.customer_name_xy = '';
207 } 215 }
208 - this.ocrData[index].field = null; 216 + this.ocrData[newIndex].field = null;
209 - this.ocrData[index].field_xy = null; 217 + this.ocrData[newIndex].field_xy = null;
210 } 218 }
211 219
212 // Gán field mới 220 // Gán field mới
213 - const bbox = this.ocrData[index].bbox; // tọa độ OCR gốc [x1, y1, x2, y2] 221 + const bbox = this.ocrData[newIndex].bbox; // tọa độ OCR gốc [x1, y1, x2, y2]
214 222
215 const x1 = bbox[0]; 223 const x1 = bbox[0];
216 const y1 = bbox[1]; 224 const y1 = bbox[1];
...@@ -219,11 +227,11 @@ ...@@ -219,11 +227,11 @@
219 227
220 const xyStr = `${x1},${y1},${w},${h}`; 228 const xyStr = `${x1},${y1},${w},${h}`;
221 229
222 - this.ocrData[index].field = fieldName; 230 + this.ocrData[newIndex].field = fieldName;
223 - this.ocrData[index].field_xy = xyStr; 231 + this.ocrData[newIndex].field_xy = xyStr;
224 232
225 // Set text 233 // Set text
226 - this.formData[fieldName] = (text !== null ? text : (this.ocrData[index].text || '')).trim(); 234 + this.formData[fieldName] = (text !== null ? text : (this.ocrData[newIndex].text || '')).trim();
227 235
228 // KHÔNG set active index (không focus) 236 // KHÔNG set active index (không focus)
229 237
...@@ -238,50 +246,59 @@ ...@@ -238,50 +246,59 @@
238 console.log(`Assigning field "${fieldName}" to box at index ${index} with text: "${text}"`); 246 console.log(`Assigning field "${fieldName}" to box at index ${index} with text: "${text}"`);
239 if (index == null) return; 247 if (index == null) return;
240 248
241 - // 1. Xóa box cũ của field này (nếu có) 249 + // 1. Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại
242 this.ocrData = this.ocrData.filter((box, i) => { 250 this.ocrData = this.ocrData.filter((box, i) => {
243 - return !(i !== index && box.field === fieldName && box.isManual); 251 + if (i === index) return true; // Giữ lại box hiện tại
252 + if (box.field === fieldName) {
253 + // Xóa box có field trùng lặp
254 + return false;
255 + }
256 + return true; // Giữ lại các box khác
244 }); 257 });
245 258
246 - // 2. Nếu box này từng gán field khác thì bỏ 259 + // 2. Cập nhật lại index sau khi filter
247 - const prev = this.ocrData[index]?.field; 260 + const newIndex = this.ocrData.findIndex(box => box.bbox === this.ocrData[index]?.bbox);
261 + if (newIndex === -1) return;
262 +
263 + // 3. Nếu box này từng gán field khác thì bỏ
264 + const prev = this.ocrData[newIndex]?.field;
248 if (prev && prev !== fieldName) { 265 if (prev && prev !== fieldName) {
249 if (prev === 'customer_name') { 266 if (prev === 'customer_name') {
250 this.hasCustomerNameXY = false; 267 this.hasCustomerNameXY = false;
251 this.customer_name_xy = ''; 268 this.customer_name_xy = '';
252 } 269 }
253 - this.ocrData[index].field = null; 270 + this.ocrData[newIndex].field = null;
254 - this.ocrData[index].field_xy = null; 271 + this.ocrData[newIndex].field_xy = null;
255 } 272 }
256 273
257 - // 3. Gán field mới 274 + // 4. Gán field mới
258 - const bbox = this.ocrData[index].bbox; // tọa độ OCR gốc [x1, y1, x2, y2] 275 + const bbox = this.ocrData[newIndex].bbox; // tọa độ OCR gốc [x1, y1, x2, y2]
259 console.log('bbox', bbox); 276 console.log('bbox', bbox);
260 const [x1, y1, w, h] = bbox; 277 const [x1, y1, w, h] = bbox;
261 const xyStr = `${x1},${y1},${w},${h}`; 278 const xyStr = `${x1},${y1},${w},${h}`;
262 279
263 - this.ocrData[index].field = fieldName; 280 + this.ocrData[newIndex].field = fieldName;
264 - this.ocrData[index].field_xy = xyStr; 281 + this.ocrData[newIndex].field_xy = xyStr;
265 282
266 - // 4. Gán text 283 + // 5. Gán text
267 - const finalText = text !== null ? text : (this.ocrData[index].text || ''); 284 + const finalText = text !== null ? text : (this.ocrData[newIndex].text || '');
268 this.formData[fieldName] = finalText.trim(); 285 this.formData[fieldName] = finalText.trim();
269 console.log(`formData ${fieldName}`, this.formData[fieldName]); 286 console.log(`formData ${fieldName}`, this.formData[fieldName]);
270 287
271 // Nếu trong manualBoxData tồn tại field này hoặc muốn tạo lại manual box từ ocrData 288 // Nếu trong manualBoxData tồn tại field này hoặc muốn tạo lại manual box từ ocrData
272 if (this.manualBoxData[fieldName]) { 289 if (this.manualBoxData[fieldName]) {
273 // Lấy tọa độ + text từ box hiện tại để tạo manual box mới 290 // Lấy tọa độ + text từ box hiện tại để tạo manual box mới
274 - this.createManualBoxFromDB(fieldName, this.ocrData[index].bbox, finalText); 291 + this.createManualBoxFromDB(fieldName, this.ocrData[newIndex].bbox, finalText);
275 // Cập nhật manualBoxData 292 // Cập nhật manualBoxData
276 this.manualBoxData[fieldName] = { 293 this.manualBoxData[fieldName] = {
277 - coords: this.ocrData[index].bbox, 294 + coords: this.ocrData[newIndex].bbox,
278 text: finalText 295 text: finalText
279 }; 296 };
280 } 297 }
281 298
282 - // 5. Active index 299 + // 6. Active index
283 - this.activeIndex = index; 300 + this.activeIndex = newIndex;
284 - // 6. Nếu là customer_name 301 + // 7. Nếu là customer_name
285 if (fieldName === 'customer_name') { 302 if (fieldName === 'customer_name') {
286 this.hasCustomerNameXY = true; 303 this.hasCustomerNameXY = true;
287 this.customer_name_xy = xyStr; 304 this.customer_name_xy = xyStr;
...@@ -475,38 +492,49 @@ ...@@ -475,38 +492,49 @@
475 top: `${top}px`, 492 top: `${top}px`,
476 width: `${width}px`, 493 width: `${width}px`,
477 height: `${height}px`, 494 height: `${height}px`,
478 - border: item.hideBorder ? 'none' : '2px solid ' + (index === this.activeIndex ? '#199601' : '#ff5252'), 495 + border: item.hideBorder ? '2px solid #ccc' : '2px solid ' + (index === this.activeIndex ? '#199601' : '#ff5252'),
479 boxSizing: 'border-box', 496 boxSizing: 'border-box',
480 cursor: 'pointer', 497 cursor: 'pointer',
481 zIndex: item.isManual ? 30 : 10 498 zIndex: item.isManual ? 30 : 10
482 }; 499 };
483 }, 500 },
484 highlightField(field) { 501 highlightField(field) {
485 - // Xóa tất cả manual box cũ trước khi tạo mới
486 - //this.ocrData = this.ocrData.filter(b => !b.isManual);
487 -
488 let coords, text; 502 let coords, text;
489 - 503 + let isFromDB = false;
490 - // Kiểm tra xem ocrData đã có box nào với field này chưa 504 +
491 - const existingBox = this.ocrData.find(b => b.field === field && !b.isManual); 505 + // Kiểm tra xem field này có phải từ DB không
492 - 506 + if (this.dataMapping && this.dataMapping[field]) {
493 - if (existingBox) { 507 + isFromDB = true;
494 - coords = existingBox.bbox; 508 + coords = this.dataMapping[field].coords;
495 - text = existingBox.text; 509 + text = this.dataMapping[field].text;
496 - console.log(`Using ocrData for field "${field}":`, coords, text); 510 + console.log(`Using dataMapping for field "${field}":`, coords, text);
497 - } else if (this.manualBoxData[field]) { 511 + } else {
498 - // Nếu không có trong ocrData, dùng manualBoxData 512 + // Kiểm tra xem ocrData đã có box nào với field này chưa
499 - coords = this.manualBoxData[field].coords; 513 + const existingBox = this.ocrData.find(b => b.field === field && !b.isDeleted);
500 - text = this.manualBoxData[field].text; 514 + if (existingBox) {
501 - console.log(`Using manualBoxData for field "${field}":`, coords, text); 515 + coords = existingBox.bbox;
516 + text = existingBox.text;
517 + console.log(`Using existing box for field "${field}":`, coords, text);
518 + } else if (this.manualBoxData[field]) {
519 + // Nếu không có trong ocrData, dùng manualBoxData (tọa độ mới)
520 + coords = this.manualBoxData[field].coords;
521 + text = this.manualBoxData[field].text;
522 + console.log(`Using manualBoxData (new coordinates) for field "${field}":`, coords, text);
523 + }
502 } 524 }
503 525
504 - // Nếu có coords thì tạo manual box 526 + // Nếu có coords thì tạo hoặc hiển thị lại box
505 if (coords) { 527 if (coords) {
506 - this.createManualBoxFromDB(field, coords, text); 528 + if (isFromDB) {
529 + // Tạo box manual từ DB (không có nút xóa)
530 + this.createManualBoxFromDB(field, coords, text);
531 + } else {
532 + // Hiển thị lại box manual đã quét chọn (có nút xóa)
533 + this.showManualBox(field, coords, text);
534 + }
507 } 535 }
508 536
509 - // Tìm lại index của box vừa tạo để set active 537 + // Tìm lại index của box để set active
510 let idx = -1; 538 let idx = -1;
511 for (let i = this.ocrData.length - 1; i >= 0; i--) { 539 for (let i = this.ocrData.length - 1; i >= 0; i--) {
512 const it = this.ocrData[i]; 540 const it = this.ocrData[i];
...@@ -519,7 +547,8 @@ ...@@ -519,7 +547,8 @@
519 if (idx !== -1) { 547 if (idx !== -1) {
520 this.activeIndex = idx; 548 this.activeIndex = idx;
521 this.scrollToBox(idx); 549 this.scrollToBox(idx);
522 - this.focusOnBox(idx); 550 + // Reset selectingIndex để không hiển thị dropdown khi highlight từ input
551 + this.selectingIndex = null;
523 } else { 552 } else {
524 this.activeIndex = null; 553 this.activeIndex = null;
525 } 554 }
...@@ -563,32 +592,82 @@ ...@@ -563,32 +592,82 @@
563 }); 592 });
564 }, 593 },
565 594
566 - // Focus vào box (thêm hiệu ứng nhấp nháy)
567 - focusOnBox(index) {
568 - if (index < 0 || index >= this.ocrData.length) return;
569 595
570 - const item = this.ocrData[index];
571 - if (!item || item.isDeleted) return;
572 -
573 - // Thêm class để tạo hiệu ứng focus
574 - this.$nextTick(() => {
575 - const boxElement = document.querySelector(`[data-field="${item.field}"]`);
576 - if (boxElement) {
577 - boxElement.classList.add('focus-highlight');
578 - }
579 - });
580 - },
581 596
582 // Xử lý khi click vào input 597 // Xử lý khi click vào input
583 onInputClick(fieldName) { 598 onInputClick(fieldName) {
584 // Kiểm tra xem field này có data không 599 // Kiểm tra xem field này có data không
585 const fieldValue = this.formData[fieldName]; 600 const fieldValue = this.formData[fieldName];
586 if (fieldValue && fieldValue.trim()) { 601 if (fieldValue && fieldValue.trim()) {
587 - // Nếu có data, highlight và focus vào box tương ứng 602 + // Nếu có data từ DB, highlight và focus vào box tương ứng
588 - // Chỉ khi click vào input mới focus và chuyển trạng thái active
589 this.highlightField(fieldName); 603 this.highlightField(fieldName);
590 } 604 }
591 }, 605 },
606 +
607 + // Xử lý khi click ra ngoài input (blur)
608 + onInputBlur(fieldName) {
609 + // Khi không focus vào input nào, xóa tất cả focus
610 + this.removeAllFocus();
611 + },
612 +
613 + // Xóa tất cả focus
614 + removeAllFocus() {
615 + // Reset active index (chỉ mất màu xanh, không xóa box)
616 + this.activeIndex = null;
617 +
618 + // Ẩn hoàn toàn các box manual được tạo từ DB (không phải quét chọn thủ công)
619 + this.ocrData.forEach(item => {
620 + if (item.isManual && !item.showDelete) {
621 + // Box manual từ DB (không có nút xóa) - ẩn hoàn toàn
622 + item.isDeleted = true;
623 + }
624 + });
625 +
626 + // Đảm bảo tất cả box OCR đều hiển thị (chỉ ẩn border khi cần thiết)
627 + this.ocrData.forEach(item => {
628 + if (!item.isManual && item.hideBorder) {
629 + // Chỉ ẩn border cho box OCR nằm trong vùng manual
630 + item.hideBorder = true;
631 + }
632 + });
633 + },
634 +
635 + // Xử lý khi click vào box
636 + onBoxClick(index) {
637 + const item = this.ocrData[index];
638 +
639 + // Kiểm tra xem field này có phải từ DB không
640 + const isFromDB = this.dataMapping && this.dataMapping[item.field];
641 +
642 + // Kiểm tra xem data có được ghi đè không (so sánh với data gốc từ DB)
643 + const isDataOverridden = item.field && isFromDB &&
644 + this.formData[item.field] !== this.dataMapping[item.field].text;
645 +
646 + if (item.isManual) {
647 + // Manual box
648 + if (isFromDB && !isDataOverridden) {
649 + // Manual box từ DB chưa ghi đè, KHÔNG cho chọn option
650 + this.activeIndex = index;
651 + this.selectingIndex = null;
652 + } else {
653 + // Manual box từ DB có data ghi đè HOẶC manual box bình thường, CHO PHÉP chọn option
654 + this.selectingIndex = index;
655 + }
656 + } else if (item.field) {
657 + // Box OCR có field
658 + if (isFromDB && !isDataOverridden) {
659 + // Box có field từ DB chưa ghi đè, KHÔNG cho chọn option
660 + this.activeIndex = index;
661 + this.selectingIndex = null;
662 + } else {
663 + // Box có field từ DB đã ghi đè HOẶC field mới, CHO PHÉP chọn option
664 + this.selectingIndex = index;
665 + }
666 + } else {
667 + // Box OCR thông thường (chưa có field), cho phép hiển thị dropdown
668 + this.selectingIndex = index;
669 + }
670 + },
592 startSelect(e) { 671 startSelect(e) {
593 if (this.isMappingManually || e.button !== 0) return; 672 if (this.isMappingManually || e.button !== 0) return;
594 this.isSelecting = true; 673 this.isSelecting = true;
...@@ -673,19 +752,41 @@ ...@@ -673,19 +752,41 @@
673 , 752 ,
674 applyMapping() { 753 applyMapping() {
675 const item = this.ocrData[this.selectingIndex]; 754 const item = this.ocrData[this.selectingIndex];
755 + if (!item) return;
676 756
677 - if (item && item.isManual) { 757 + if (item.isManual) {
758 + // Nếu là manual box, chuyển sang chế độ manual mapping
678 this.manualIndex = this.selectingIndex; 759 this.manualIndex = this.selectingIndex;
679 this.manualField = item.field || ""; 760 this.manualField = item.field || "";
680 this.applyManualMapping(); 761 this.applyManualMapping();
681 return; 762 return;
682 } 763 }
683 764
765 + // Xử lý box OCR (có thể chưa có field hoặc đã có field)
684 if (item.field) { 766 if (item.field) {
685 - // this.formData[item.field] = item.text; 767 + // Nếu box đã có field, cập nhật lại
686 - // this.activeIndex = this.selectingIndex; 768 + const oldField = item.field;
687 - this.assignFieldToBox(this.selectingIndex, item.field, item.text); 769 +
770 + // Cập nhật formData để hiển thị trong input
771 + this.formData[item.field] = item.text || '';
772 +
773 + // Cập nhật manualBoxData với tọa độ mới
774 + this.manualBoxData[item.field] = {
775 + coords: item.bbox,
776 + text: item.text || ''
777 + };
778 +
779 + // Xóa dataMapping cũ để tránh focus về tọa độ cũ
780 + if (this.dataMapping && this.dataMapping[item.field]) {
781 + delete this.dataMapping[item.field];
782 + }
783 +
784 + // Set active index
785 + this.activeIndex = this.selectingIndex;
786 +
787 + console.log(`Updated field "${item.field}" for box at index ${this.selectingIndex} with new coordinates`);
688 } 788 }
789 +
689 this.selectingIndex = null; 790 this.selectingIndex = null;
690 }, 791 },
691 applyManualMapping() { 792 applyManualMapping() {
...@@ -723,17 +824,34 @@ ...@@ -723,17 +824,34 @@
723 }); 824 });
724 825
725 const finalText = combinedText.join(" "); 826 const finalText = combinedText.join(" ");
726 - // console.log('Combined text:', finalText); 827 + console.log('Combined text:', finalText);
727 828
728 // Gán field và text cho box manual 829 // Gán field và text cho box manual
729 console.log(`Assigning manual field "${this.manualField}" to box at index ${manualIndex} with text: "${finalText}"`); 830 console.log(`Assigning manual field "${this.manualField}" to box at index ${manualIndex} with text: "${finalText}"`);
730 - // console.log(this.ocrData[manualIndex]); 831 +
731 - this.assignFieldToBox(manualIndex, this.manualField, finalText); 832 + // Gán field trực tiếp cho box manual
833 + this.ocrData[manualIndex].field = this.manualField;
834 +
835 + // Cập nhật formData để hiển thị trong input
836 + this.formData[this.manualField] = finalText.trim();
837 +
838 + // Cập nhật manualBoxData
839 + this.manualBoxData[this.manualField] = {
840 + coords: newBbox,
841 + text: finalText.trim()
842 + };
843 +
844 + // Xóa dataMapping cũ để tránh focus về tọa độ cũ
845 + if (this.dataMapping && this.dataMapping[this.manualField]) {
846 + delete this.dataMapping[this.manualField];
847 + }
732 848
733 // Reset trạng thái chọn 849 // Reset trạng thái chọn
734 this.isMappingManually = false; 850 this.isMappingManually = false;
735 this.selectBox.show = false; 851 this.selectBox.show = false;
736 this.selectBox.showDropdown = false; 852 this.selectBox.showDropdown = false;
853 + this.manualField = "";
854 + this.manualIndex = null;
737 }, 855 },
738 856
739 isBoxInside(inner, outer) { 857 isBoxInside(inner, outer) {
...@@ -820,7 +938,10 @@ ...@@ -820,7 +938,10 @@
820 if (x1 >= 0 && y1 >= 0 && x2 > x1 && y2 > y1 && 938 if (x1 >= 0 && y1 >= 0 && x2 > x1 && y2 > y1 &&
821 x2 <= this.imageWidth && y2 <= this.imageHeight) { 939 x2 <= this.imageWidth && y2 <= this.imageHeight) {
822 940
823 - // Tạo box manual 941 + // Xóa box cũ có cùng fieldName trước khi tạo mới (chỉ xóa manual box)
942 + this.ocrData = this.ocrData.filter(box => !(box.field === fieldName && box.isManual));
943 +
944 + // Tạo box manual từ DB (không có nút xóa)
824 const manualBox = { 945 const manualBox = {
825 text: text || '', 946 text: text || '',
826 bbox: coords, 947 bbox: coords,
...@@ -832,7 +953,55 @@ ...@@ -832,7 +953,55 @@
832 }; 953 };
833 954
834 this.ocrData.push(manualBox); 955 this.ocrData.push(manualBox);
835 - // console.log('Manual box created successfully:', manualBox); 956 + // console.log('Manual box created successfully:', manualBox);
957 +
958 + // Force re-render
959 + this.$forceUpdate();
960 + } else {
961 + console.warn('Invalid coordinates for manual box:', coords);
962 + }
963 + },
964 +
965 + // Hiển thị lại box manual đã quét chọn (có nút xóa)
966 + showManualBox(fieldName, coordinates, text) {
967 + if (!this.imageWidth || !this.imageHeight) {
968 + console.log('Cannot show manual box: Image not loaded');
969 + return;
970 + }
971 +
972 + // Parse coordinates
973 + let coords;
974 + if (typeof coordinates === 'string') {
975 + coords = coordinates.split(',').map(Number);
976 + } else if (Array.isArray(coordinates)) {
977 + coords = coordinates;
978 + } else {
979 + console.error('Invalid coordinates format:', coordinates);
980 + return;
981 + }
982 +
983 + const [x1, y1, x2, y2] = coords;
984 +
985 + // Kiểm tra tọa độ có hợp lệ không
986 + if (x1 >= 0 && y1 >= 0 && x2 > x1 && y2 > y1 &&
987 + x2 <= this.imageWidth && y2 <= this.imageHeight) {
988 +
989 + // Xóa box cũ có cùng fieldName trước khi hiển thị lại
990 + this.ocrData = this.ocrData.filter(box => !(box.field === fieldName && box.isManual));
991 +
992 + // Hiển thị lại box manual đã quét chọn (có nút xóa)
993 + const manualBox = {
994 + text: text || '',
995 + bbox: coords,
996 + field: fieldName,
997 + isManual: true,
998 + showDelete: true,
999 + isDeleted: false,
1000 + hideBorder: false
1001 + };
1002 +
1003 + this.ocrData.push(manualBox);
1004 + console.log('Manual box shown successfully:', manualBox);
836 1005
837 // Force re-render 1006 // Force re-render
838 this.$forceUpdate(); 1007 this.$forceUpdate();
......