tien_nemo

format code

...@@ -62,7 +62,6 @@ ...@@ -62,7 +62,6 @@
62 <body> 62 <body>
63 <meta name="csrf-token" content="{{ csrf_token() }}"> 63 <meta name="csrf-token" content="{{ csrf_token() }}">
64 <div id="app"> 64 <div id="app">
65 - <!-- Right: PDF viewer + select tool -->
66 <div class="right-panel" > 65 <div class="right-panel" >
67 <div class="pdf-container" ref="pdfContainer" 66 <div class="pdf-container" ref="pdfContainer"
68 @mousedown="startSelect" 67 @mousedown="startSelect"
...@@ -120,17 +119,15 @@ ...@@ -120,17 +119,15 @@
120 </div> 119 </div>
121 </div> 120 </div>
122 121
123 - <!-- Left: Form inputs -->
124 <div class="left-panel"> 122 <div class="left-panel">
125 <div v-for="field in fieldOptions" :key="field.value" class="form-group"> 123 <div v-for="field in fieldOptions" :key="field.value" class="form-group">
126 <label>@{{ field.label }}</label> 124 <label>@{{ field.label }}</label>
127 <input v-model="formData[field.value]" 125 <input v-model="formData[field.value]"
128 @focus="highlightField(field.value)" 126 @focus="highlightField(field.value)"
129 @click="onInputClick(field.value)" 127 @click="onInputClick(field.value)"
130 - @blur="onInputBlur(field.value)" 128 + @blur="removeAllFocus()"
131 129
132 > 130 >
133 -{{-- :readonly="field.value === 'customer_name' && !hasCustomerNameXY"--}}
134 </div> 131 </div>
135 <button @click="saveTemplate">💾Save</button> 132 <button @click="saveTemplate">💾Save</button>
136 </div> 133 </div>
...@@ -160,7 +157,6 @@ ...@@ -160,7 +157,6 @@
160 } 157 }
161 }, 158 },
162 created() { 159 created() {
163 - // Chỉ tạo formData cho các field cần mapping
164 this.fieldOptions 160 this.fieldOptions
165 .forEach(f => { 161 .forEach(f => {
166 this.$set(this.formData, f.value, ""); 162 this.$set(this.formData, f.value, "");
...@@ -171,7 +167,6 @@ ...@@ -171,7 +167,6 @@
171 167
172 // Thêm event listener để xóa focus khi click ra ngoài 168 // Thêm event listener để xóa focus khi click ra ngoài
173 document.addEventListener('click', (e) => { 169 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')) { 170 if (!e.target.closest('.left-panel') && !e.target.closest('.bbox')) {
176 this.removeAllFocus(); 171 this.removeAllFocus();
177 } 172 }
...@@ -191,12 +186,11 @@ ...@@ -191,12 +186,11 @@
191 186
192 // Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại 187 // Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại
193 this.ocrData = this.ocrData.filter((box, i) => { 188 this.ocrData = this.ocrData.filter((box, i) => {
194 - if (i === index) return true; // Giữ lại box hiện tại 189 + if (i === index) return true;
195 if (box.field === fieldName) { 190 if (box.field === fieldName) {
196 - // Xóa box có field trùng lặp
197 return false; 191 return false;
198 } 192 }
199 - return true; // Giữ lại các box khác 193 + return true;
200 }); 194 });
201 195
202 // Cập nhật lại index sau khi filter 196 // Cập nhật lại index sau khi filter
...@@ -214,8 +208,7 @@ ...@@ -214,8 +208,7 @@
214 this.ocrData[newIndex].field_xy = null; 208 this.ocrData[newIndex].field_xy = null;
215 } 209 }
216 210
217 - // Gán field mới 211 + const bbox = this.ocrData[newIndex].bbox;
218 - const bbox = this.ocrData[newIndex].bbox; // tọa độ OCR gốc [x1, y1, x2, y2]
219 212
220 const x1 = bbox[0]; 213 const x1 = bbox[0];
221 const y1 = bbox[1]; 214 const y1 = bbox[1];
...@@ -223,86 +216,17 @@ ...@@ -223,86 +216,17 @@
223 const h = bbox[3]; 216 const h = bbox[3];
224 217
225 const xyStr = `${x1},${y1},${w},${h}`; 218 const xyStr = `${x1},${y1},${w},${h}`;
226 -
227 this.ocrData[newIndex].field = fieldName; 219 this.ocrData[newIndex].field = fieldName;
228 this.ocrData[newIndex].field_xy = xyStr; 220 this.ocrData[newIndex].field_xy = xyStr;
229 221
230 - // Set text
231 this.formData[fieldName] = (text !== null ? text : (this.ocrData[newIndex].text || '')).trim(); 222 this.formData[fieldName] = (text !== null ? text : (this.ocrData[newIndex].text || '')).trim();
232 223
233 - // KHÔNG set active index (không focus)
234 -
235 - // Nếu là customer_name
236 if (fieldName === 'customer_name') { 224 if (fieldName === 'customer_name') {
237 this.hasCustomerNameXY = true; 225 this.hasCustomerNameXY = true;
238 this.customer_name_xy = xyStr; 226 this.customer_name_xy = xyStr;
239 } 227 }
240 }, 228 },
241 229
242 - assignFieldToBox(index, fieldName, text = null) {
243 - console.log(`Assigning field "${fieldName}" to box at index ${index} with text: "${text}"`);
244 - if (index == null) return;
245 -
246 - // 1. Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại
247 - this.ocrData = this.ocrData.filter((box, i) => {
248 - if (i === index) return true; // Giữ lại box hiện tại
249 - if (box.field === fieldName) {
250 - // Xóa box có field trùng lặp
251 - return false;
252 - }
253 - return true; // Giữ lại các box khác
254 - });
255 -
256 - // 2. Cập nhật lại index sau khi filter
257 - const newIndex = this.ocrData.findIndex(box => box.bbox === this.ocrData[index]?.bbox);
258 - if (newIndex === -1) return;
259 -
260 - // 3. Nếu box này từng gán field khác thì bỏ
261 - const prev = this.ocrData[newIndex]?.field;
262 - if (prev && prev !== fieldName) {
263 - if (prev === 'customer_name') {
264 - this.hasCustomerNameXY = false;
265 - this.customer_name_xy = '';
266 - }
267 - this.ocrData[newIndex].field = null;
268 - this.ocrData[newIndex].field_xy = null;
269 - }
270 -
271 - // 4. Gán field mới
272 - const bbox = this.ocrData[newIndex].bbox; // tọa độ OCR gốc [x1, y1, x2, y2]
273 - console.log('bbox', bbox);
274 - const [x1, y1, w, h] = bbox;
275 - const xyStr = `${x1},${y1},${w},${h}`;
276 -
277 - this.ocrData[newIndex].field = fieldName;
278 - this.ocrData[newIndex].field_xy = xyStr;
279 -
280 - // 5. Gán text
281 - const finalText = text !== null ? text : (this.ocrData[newIndex].text || '');
282 - this.formData[fieldName] = finalText.trim();
283 - console.log(`formData ${fieldName}`, this.formData[fieldName]);
284 -
285 - // Nếu trong manualBoxData tồn tại field này hoặc muốn tạo lại manual box từ ocrData
286 - if (this.manualBoxData[fieldName]) {
287 - // Lấy tọa độ + text từ box hiện tại để tạo manual box mới
288 - this.createManualBoxFromDB(fieldName, this.ocrData[newIndex].bbox, finalText);
289 - // Cập nhật manualBoxData
290 - this.manualBoxData[fieldName] = {
291 - coords: this.ocrData[newIndex].bbox,
292 - text: finalText
293 - };
294 - }
295 -
296 - // 6. Active index
297 - this.activeIndex = newIndex;
298 - // 7. Nếu là customer_name
299 - if (fieldName === 'customer_name') {
300 - this.hasCustomerNameXY = true;
301 - this.customer_name_xy = xyStr;
302 - }
303 - },
304 -
305 -
306 async saveTemplate() { 230 async saveTemplate() {
307 231
308 let customer_name = null; 232 let customer_name = null;
...@@ -315,7 +239,6 @@ ...@@ -315,7 +239,6 @@
315 fields = this.manualBoxData; 239 fields = this.manualBoxData;
316 console.log('Using manualBoxData for customer_name:', customer_name, customer_coords); 240 console.log('Using manualBoxData for customer_name:', customer_name, customer_coords);
317 } else { 241 } else {
318 - // Không có → tìm trong ocrData
319 const found = this.ocrData.find(item => item.field === 'customer_name'); 242 const found = this.ocrData.find(item => item.field === 'customer_name');
320 if (found) { 243 if (found) {
321 customer_name = found.text; 244 customer_name = found.text;
...@@ -325,20 +248,17 @@ ...@@ -325,20 +248,17 @@
325 const fieldsByName = {}; 248 const fieldsByName = {};
326 this.ocrData.forEach(box => { 249 this.ocrData.forEach(box => {
327 if (box.field && !box.isDeleted) { 250 if (box.field && !box.isDeleted) {
328 - // chỉ giữ 1 bản ghi cuối cùng cho mỗi field (box gần nhất)
329 fieldsByName[box.field] = { 251 fieldsByName[box.field] = {
330 text: box.field, 252 text: box.field,
331 coords: box.field_xy || '' 253 coords: box.field_xy || ''
332 }; 254 };
333 } 255 }
334 }); 256 });
335 - // // convert to array 257 +
336 fields = (fieldsByName); 258 fields = (fieldsByName);
337 console.log('Using ocrData for customer_name:', customer_name, customer_coords); 259 console.log('Using ocrData for customer_name:', customer_name, customer_coords);
338 } 260 }
339 261
340 - // console.log('fields:', fields);
341 -
342 if (!customer_coords || !customer_name) { 262 if (!customer_coords || !customer_name) {
343 alert("Bạn phải map customer_name (quét/select) trước khi lưu."); 263 alert("Bạn phải map customer_name (quét/select) trước khi lưu.");
344 return; 264 return;
...@@ -350,7 +270,7 @@ ...@@ -350,7 +270,7 @@
350 customer_name_xy: customer_coords || [], 270 customer_name_xy: customer_coords || [],
351 fields: fields 271 fields: fields
352 }; 272 };
353 - // console.log(fields); 273 +
354 try { 274 try {
355 const res = await fetch('/ocr/save-template', { 275 const res = await fetch('/ocr/save-template', {
356 method: 'POST', 276 method: 'POST',
...@@ -377,7 +297,6 @@ ...@@ -377,7 +297,6 @@
377 const item = this.ocrData[index]; 297 const item = this.ocrData[index];
378 if (item.isManual) { 298 if (item.isManual) {
379 const manualBbox = item.bbox; 299 const manualBbox = item.bbox;
380 -
381 // Hiện lại border các box OCR gốc nằm trong vùng thủ công 300 // Hiện lại border các box OCR gốc nằm trong vùng thủ công
382 this.ocrData.forEach(o => { 301 this.ocrData.forEach(o => {
383 if (!o.isManual && this.isBoxInside(o.bbox, manualBbox)) { 302 if (!o.isManual && this.isBoxInside(o.bbox, manualBbox)) {
...@@ -415,17 +334,8 @@ ...@@ -415,17 +334,8 @@
415 this.ocrData = data.ocrData; 334 this.ocrData = data.ocrData;
416 this.pdfImageUrl = data.pdfImageUrl; 335 this.pdfImageUrl = data.pdfImageUrl;
417 this.fieldOptions = data.fieldOptions; 336 this.fieldOptions = data.fieldOptions;
418 - // this.formData = data.formData;
419 this.dataMapping = data.dataMapping; 337 this.dataMapping = data.dataMapping;
420 this.is_template = data.is_template; 338 this.is_template = data.is_template;
421 - // Đợi image load xong trước khi xử lý
422 - // if (this.$refs.pdfImage && this.$refs.pdfImage.complete) {
423 - // // this.processLoadedData();
424 - // } else {
425 - // console.log('Image not loaded yet, waiting for onImageLoad');
426 - // // Image sẽ được xử lý trong onImageLoad
427 - // }
428 -
429 339
430 } catch (error) { 340 } catch (error) {
431 console.error('Error in loadOCRData:', error); 341 console.error('Error in loadOCRData:', error);
...@@ -434,16 +344,13 @@ ...@@ -434,16 +344,13 @@
434 344
435 // Xử lý data sau khi image đã load 345 // Xử lý data sau khi image đã load
436 processLoadedData() { 346 processLoadedData() {
437 - // Tự động map field cho các box OCR dựa trên formData đã load
438 this.autoMapFieldsFromFormData(); 347 this.autoMapFieldsFromFormData();
439 -
440 // Force re-render để đảm bảo các box được vẽ 348 // Force re-render để đảm bảo các box được vẽ
441 this.$nextTick(() => { 349 this.$nextTick(() => {
442 this.$forceUpdate(); 350 this.$forceUpdate();
443 }); 351 });
444 }, 352 },
445 353
446 - // Tự động map field cho các box OCR dựa trên formData đã load từ DB
447 autoMapFieldsFromFormData() { 354 autoMapFieldsFromFormData() {
448 this.manualBoxData = {}; // reset 355 this.manualBoxData = {}; // reset
449 356
...@@ -452,7 +359,6 @@ ...@@ -452,7 +359,6 @@
452 } 359 }
453 360
454 const tolerance = 20; // ngưỡng chenh lech toa do y cho cùng 1 hàng 361 const tolerance = 20; // ngưỡng chenh lech toa do y cho cùng 1 hàng
455 -
456 Object.keys(this.dataMapping).forEach(fieldName => { 362 Object.keys(this.dataMapping).forEach(fieldName => {
457 let { coords, text } = this.dataMapping[fieldName]; 363 let { coords, text } = this.dataMapping[fieldName];
458 let foundItems = this.ocrData 364 let foundItems = this.ocrData
...@@ -470,13 +376,10 @@ ...@@ -470,13 +376,10 @@
470 376
471 const combinedText = foundItems.map(f => f.text.trim()); 377 const combinedText = foundItems.map(f => f.text.trim());
472 const finalText = combinedText.join(" "); 378 const finalText = combinedText.join(" ");
473 -
474 - // Lưu vào dataMapping
475 this.dataMapping[fieldName] = { 379 this.dataMapping[fieldName] = {
476 text: finalText, 380 text: finalText,
477 coords: coords 381 coords: coords
478 }; 382 };
479 - // Nếu là template thì gán vào formData
480 if (this.is_template) { 383 if (this.is_template) {
481 this.formData[fieldName] = finalText && finalText.trim() !== "" ? finalText : text; 384 this.formData[fieldName] = finalText && finalText.trim() !== "" ? finalText : text;
482 } 385 }
...@@ -493,7 +396,6 @@ ...@@ -493,7 +396,6 @@
493 const img = this.$refs.pdfImage; 396 const img = this.$refs.pdfImage;
494 this.imageWidth = img.naturalWidth; 397 this.imageWidth = img.naturalWidth;
495 this.imageHeight = img.naturalHeight; 398 this.imageHeight = img.naturalHeight;
496 - // Nếu đã có data, xử lý ngay
497 if (this.ocrData && this.ocrData.length > 0) { 399 if (this.ocrData && this.ocrData.length > 0) {
498 console.log('Image loaded and data exists, processing now'); 400 console.log('Image loaded and data exists, processing now');
499 this.processLoadedData(); 401 this.processLoadedData();
...@@ -555,7 +457,6 @@ ...@@ -555,7 +457,6 @@
555 } 457 }
556 } 458 }
557 459
558 - // Nếu có coords thì tạo hoặc hiển thị lại box
559 if (coords) { 460 if (coords) {
560 if (isFromDB) { 461 if (isFromDB) {
561 // Tạo box manual từ DB (không có nút xóa) 462 // Tạo box manual từ DB (không có nút xóa)
...@@ -586,8 +487,6 @@ ...@@ -586,8 +487,6 @@
586 } 487 }
587 }, 488 },
588 489
589 -
590 - // Scroll đến box tương ứng
591 scrollToBox(index) { 490 scrollToBox(index) {
592 if (!this.$refs.pdfContainer || index < 0 || index >= this.ocrData.length) return; 491 if (!this.$refs.pdfContainer || index < 0 || index >= this.ocrData.length) return;
593 492
...@@ -623,8 +522,6 @@ ...@@ -623,8 +522,6 @@
623 }); 522 });
624 }, 523 },
625 524
626 -
627 -
628 // Xử lý khi click vào input 525 // Xử lý khi click vào input
629 onInputClick(fieldName) { 526 onInputClick(fieldName) {
630 // Kiểm tra xem field này có data không 527 // Kiểm tra xem field này có data không
...@@ -635,12 +532,6 @@ ...@@ -635,12 +532,6 @@
635 } 532 }
636 }, 533 },
637 534
638 - // Xử lý khi click ra ngoài input (blur)
639 - onInputBlur(fieldName) {
640 - // Khi không focus vào input nào, xóa tất cả focus
641 - this.removeAllFocus();
642 - },
643 -
644 // Xóa tất cả focus 535 // Xóa tất cả focus
645 removeAllFocus() { 536 removeAllFocus() {
646 // Reset active index (chỉ mất màu xanh, không xóa box) 537 // Reset active index (chỉ mất màu xanh, không xóa box)
...@@ -779,8 +670,7 @@ ...@@ -779,8 +670,7 @@
779 e.stopPropagation(); 670 e.stopPropagation();
780 e.preventDefault(); 671 e.preventDefault();
781 672
782 - } 673 + },
783 - ,
784 applyMapping() { 674 applyMapping() {
785 const item = this.ocrData[this.selectingIndex]; 675 const item = this.ocrData[this.selectingIndex];
786 if (!item) return; 676 if (!item) return;
...@@ -790,8 +680,6 @@ ...@@ -790,8 +680,6 @@
790 box.field = ''; 680 box.field = '';
791 } 681 }
792 }); 682 });
793 -
794 - console.log(`mapping box ${this.selectingIndex} with field "${item.field}" item.isManual: ${item.isManual}`);
795 if (item.isManual) { 683 if (item.isManual) {
796 // Nếu là manual box, chuyển sang chế độ manual mapping 684 // Nếu là manual box, chuyển sang chế độ manual mapping
797 this.manualIndex = this.selectingIndex; 685 this.manualIndex = this.selectingIndex;
...@@ -802,12 +690,9 @@ ...@@ -802,12 +690,9 @@
802 690
803 // Xử lý box OCR (có thể chưa có field hoặc đã có field) 691 // Xử lý box OCR (có thể chưa có field hoặc đã có field)
804 if (item.field) { 692 if (item.field) {
805 - // Nếu box đã có field, cập nhật lại 693 +
806 - const oldField = item.field;
807 - console.log(`Updating box OCR at index ${this.selectingIndex} from field "${oldField}" to "${item.field}" "${item.bbox}"`);
808 // Cập nhật formData để hiển thị trong input 694 // Cập nhật formData để hiển thị trong input
809 this.formData[item.field] = item.text || ''; 695 this.formData[item.field] = item.text || '';
810 -
811 // Cập nhật manualBoxData với tọa độ mới (box OCR không có nút xóa) 696 // Cập nhật manualBoxData với tọa độ mới (box OCR không có nút xóa)
812 this.manualBoxData[item.field] = { 697 this.manualBoxData[item.field] = {
813 coords: item.bbox, 698 coords: item.bbox,
...@@ -819,8 +704,6 @@ ...@@ -819,8 +704,6 @@
819 if (this.dataMapping && this.dataMapping[item.field]) { 704 if (this.dataMapping && this.dataMapping[item.field]) {
820 delete this.dataMapping[item.field]; 705 delete this.dataMapping[item.field];
821 } 706 }
822 -
823 - // Set active index
824 this.activeIndex = this.selectingIndex; 707 this.activeIndex = this.selectingIndex;
825 } 708 }
826 709
...@@ -871,13 +754,8 @@ ...@@ -871,13 +754,8 @@
871 754
872 const finalText = combinedText.join(" "); 755 const finalText = combinedText.join(" ");
873 756
874 - // Gán field trực tiếp cho box manual
875 this.ocrData[manualIndex].field = this.manualField; 757 this.ocrData[manualIndex].field = this.manualField;
876 -
877 - // Cập nhật formData để hiển thị trong input
878 this.formData[this.manualField] = finalText.trim(); 758 this.formData[this.manualField] = finalText.trim();
879 -
880 - // Cập nhật manualBoxData (box quét chọn có nút xóa)
881 this.manualBoxData[this.manualField] = { 759 this.manualBoxData[this.manualField] = {
882 coords: newBbox, 760 coords: newBbox,
883 text: finalText.trim(), 761 text: finalText.trim(),
...@@ -926,11 +804,7 @@ ...@@ -926,11 +804,7 @@
926 getPartialText(text, bbox, selectBbox) { 804 getPartialText(text, bbox, selectBbox) {
927 const [x1, y1, x2, y2] = bbox; 805 const [x1, y1, x2, y2] = bbox;
928 const [sx1, sy1, sx2, sy2] = selectBbox; 806 const [sx1, sy1, sx2, sy2] = selectBbox;
929 - // Chiều rộng box OCR
930 const boxWidth = x2 - x1; 807 const boxWidth = x2 - x1;
931 - const boxHeight = y2 - y1;
932 -
933 - // Vị trí start và end tương đối trong text
934 let startRatio = Math.max(0, (sx1 - x1) / boxWidth); 808 let startRatio = Math.max(0, (sx1 - x1) / boxWidth);
935 let endRatio = Math.min(1, (sx2 - x1) / boxWidth); 809 let endRatio = Math.min(1, (sx2 - x1) / boxWidth);
936 810
...@@ -982,8 +856,6 @@ ...@@ -982,8 +856,6 @@
982 856
983 // Xóa box cũ có cùng fieldName trước khi tạo mới (chỉ xóa manual box) 857 // Xóa box cũ có cùng fieldName trước khi tạo mới (chỉ xóa manual box)
984 this.ocrData = this.ocrData.filter(box => !(box.field === fieldName && box.isManual)); 858 this.ocrData = this.ocrData.filter(box => !(box.field === fieldName && box.isManual));
985 -
986 - // Tạo box manual từ DB (không có nút xóa)
987 const manualBox = { 859 const manualBox = {
988 text: text || '', 860 text: text || '',
989 bbox: coords, 861 bbox: coords,
...@@ -1045,8 +917,6 @@ ...@@ -1045,8 +917,6 @@
1045 }; 917 };
1046 918
1047 this.ocrData.push(manualBox); 919 this.ocrData.push(manualBox);
1048 - console.log(`Manual box shown successfully: ${isFromOCR ? 'from OCR (no delete btn)' : 'from manual selection (with delete btn)'}`);
1049 -
1050 // Force re-render 920 // Force re-render
1051 this.$forceUpdate(); 921 this.$forceUpdate();
1052 } else { 922 } else {
......