tien_nemo

test load data

......@@ -17,13 +17,11 @@ class OcrController extends Controller
public function store(Request $request)
{
// dd($request->all());
$request->validate([
'customer_name_text' => 'required|string',
'customer_name_xy' => 'required|string',
'template_name' => 'required|string|unique:mst_template,tpl_name',
'fields' => 'required|array',
'fields.*.name' => 'required|string',
'fields.*.xy' => 'required|string',
]);
try {
......@@ -35,11 +33,11 @@ class OcrController extends Controller
]);
// Lưu các field khác vào dt_template
foreach ($request->fields as $field) {
foreach ($request->fields as $field => $value) {
DtTemplate::create([
'tpl_id' => $mst->id,
'field_name' => $field['name'],
'field_xy' => $field['xy'],
'field_name' => $field,
'field_xy' => is_array($value['coords']) ? implode(',', $value['coords']) : $value['coords'],
]);
}
......@@ -61,7 +59,7 @@ class OcrController extends Controller
{
try {
// Lấy template name từ request hoặc mặc định
$templateName = $request->get('template_name', 'nemo');
$templateName = $request->get('template_name', 'A');
// Giả sử file OCR JSON & ảnh nằm trong storage/app/public/image/
$jsonPath = public_path("image/data_picking_detail_1754967679.json");
......@@ -77,6 +75,7 @@ class OcrController extends Controller
}
$formData = [];
$dataMapping = [];
if ($templateName) {
$mst = MstTemplate::where('tpl_name', $templateName)->first();
......@@ -94,6 +93,11 @@ class OcrController extends Controller
// field_name => text
$formData[$detail->field_name] = $text;
$dataMapping[$detail->field_name] = [
'text' => $text,
'coords' => $coords
];
}
} else {
$formData = [
......@@ -112,6 +116,7 @@ class OcrController extends Controller
'ocrData' => $ocrData,
'pdfImageUrl' => $imgPath,
'formData' => $formData,
'dataMapping' => $dataMapping,
'fieldOptions' => [
[ 'value' => 'template_name', 'label' => 'Tên Mẫu PDF' ],
[ 'value' => 'customer_name', 'label' => 'Tên khách hàng' ],
......
......@@ -26,8 +26,8 @@
}
.bbox.focus-highlight {
animation: focusPulse 2s ease-in-out;
/*border-color: #ff6b35 !important;*/
/*animation: focusPulse 2s ease-in-out;*/
border-color: #ff6b35 !important;
/*background-color: rgba(255, 107, 53, 0.4) !important;*/
}
......@@ -133,12 +133,11 @@
<input v-model="formData[field.value]"
@focus="highlightField(field.value)"
@click="onInputClick(field.value)"
{{-- @blur="removeManualBoxes"--}}
:readonly="field.value === 'customer_name' && !hasCustomerNameXY"
>
</div>
<button @click="saveTemplate">💾Save</button>
{{-- <button @click="debugCoordinates" style="margin-left: 10px; background: #6c757d;">🐛Debug</button>--}}
{{-- <button @click="testManualBox" style="margin-left: 10px; background: #28a745;">🧪Test Box</button>--}}
</div>
</div>
......@@ -156,6 +155,7 @@
activeIndex: null,
manualField: "",
formData: {},
manualBoxData: {},
fieldOptions: [],
customer_name_xy: '',
hasCustomerNameXY: false,
......@@ -182,6 +182,10 @@
}
},
methods: {
removeManualBoxes() {
this.ocrData = this.ocrData.filter(b => !b.isManual);
this.activeIndex = null;
},
// Map field cho box (không set active, chỉ dùng để load data từ DB)
mapFieldToBox(index, fieldName, text = null) {
if (index == null) return;
......@@ -230,21 +234,17 @@
}
},
// Assign field và set active (dùng khi user tương tác)
assignFieldToBox(index, fieldName, text = null) {
console.log(`Assigning field "${fieldName}" to box at index ${index} with text: "${text}"`);
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;
}
// 1. Xóa box cũ của field này (nếu có)
this.ocrData = this.ocrData.filter((box, i) => {
return !(i !== index && box.field === fieldName && box.isManual);
});
// Nếu box này từng gán field khác thì bỏ
const prev = this.ocrData[index].field;
// 2. 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;
......@@ -254,27 +254,34 @@
this.ocrData[index].field_xy = null;
}
// Gán field mới
// 3. Gán field mới
const bbox = this.ocrData[index].bbox; // tọa độ OCR gốc [x1, y1, x2, y2]
console.log('2222222222222222',bbox);
const x1 = bbox[0];
const y1 = bbox[1];
const w = bbox[2];
const h = bbox[3];
console.log('bbox', bbox);
const [x1, y1, w, h] = bbox;
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();
// 4. Gán text
const finalText = text !== null ? text : (this.ocrData[index].text || '');
this.formData[fieldName] = finalText.trim();
console.log(`formData ${fieldName}`, this.formData[fieldName]);
// Nếu trong manualBoxData tồn tại field này hoặc muốn tạo lại manual box từ ocrData
if (this.manualBoxData[fieldName]) {
// Lấy tọa độ + text từ box hiện tại để tạo manual box mới
this.createManualBoxFromDB(fieldName, this.ocrData[index].bbox, finalText);
// Cập nhật manualBoxData
this.manualBoxData[fieldName] = {
coords: this.ocrData[index].bbox,
text: finalText
};
}
// Active index (focus vào box này)
// 5. Active index
this.activeIndex = index;
// Nếu là customer_name
// 6. Nếu là customer_name
if (fieldName === 'customer_name') {
this.hasCustomerNameXY = true;
this.customer_name_xy = xyStr;
......@@ -284,29 +291,47 @@
async saveTemplate() {
if (!this.hasCustomerNameXY) {
alert("Bạn phải map customer_name (quét/select) trước khi lưu.");
return;
let customer_name = null;
let customer_coords = null;
let fields = [];
if (this.manualBoxData.customer_name) {
// Lấy từ manualBoxData nếu có
customer_name = this.manualBoxData.customer_name.text;
customer_coords = this.manualBoxData.customer_name.coords.join(',');
fields = this.manualBoxData;
} else {
// Không có → tìm trong ocrData
const found = this.ocrData.find(item => item.field === 'customer_name');
if (found) {
customer_name = found.text;
customer_coords = found.field_xy;
}
// build fields array: lấy những box có field gán, map unique by field name -> sử dụng field_xy
const fieldsByName = {};
this.ocrData.forEach(box => {
if (box.field && !box.isDeleted) {
// chỉ giữ 1 bản ghi cuối cùng cho mỗi field (box gần nhất)
fieldsByName[box.field] = {
name: box.field,
xy: box.field_xy || ''
text: box.field,
coords: box.field_xy || ''
};
}
});
// convert to array
const fields = Object.values(fieldsByName);
// // convert to array
fields = (fieldsByName);
}
console.log('fields:', fields);
if (!customer_coords) {
alert("Bạn phải map customer_name (quét/select) trước khi lưu.");
return;
}
const payload = {
customer_name_text: this.formData.customer_name || '',
customer_name_text: customer_name || '',
template_name: this.formData.template_name || this.formData.customer_name,
customer_name_xy: this.customer_name_xy,
customer_name_xy: customer_coords || [],
fields: fields
};
// console.log(fields);
......@@ -375,6 +400,7 @@
this.pdfImageUrl = data.pdfImageUrl;
this.formData = data.formData;
this.fieldOptions = data.fieldOptions;
this.dataMapping = data.dataMapping;
// Đợi image load xong trước khi xử lý
if (this.$refs.pdfImage && this.$refs.pdfImage.complete) {
this.processLoadedData();
......@@ -394,7 +420,6 @@
// Tự động map field cho các box OCR dựa trên formData đã load
this.autoMapFieldsFromFormData();
// Kiểm tra và sửa lại tọa độ của các box manual
// this.validateManualBoxes();
// Force re-render để đảm bảo các box được vẽ
this.$nextTick(() => {
......@@ -404,20 +429,13 @@
// 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];
this.manualBoxData = {}; // reset
Object.keys(this.dataMapping).forEach(fieldName => {
const { text, coords } = this.dataMapping[fieldName];
// Ví dụ: tạo box từ dữ liệu
//this.createManualBoxFromDB(fieldName, coords, text);
// 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);
}
}
this.manualBoxData[fieldName] = { text, coords };
});
},
......@@ -512,9 +530,55 @@
};
},
// highlightField(field) {
// let idx = -1;
// for (let i = this.ocrData.length - 1; i >= 0; i--) {
// const it = this.ocrData[i];
// if (!it.isDeleted && it.field === field) {
// idx = i;
// break;
// }
// }
//
// 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;
// }
// },
highlightField(field) {
// Xóa tất cả manual box cũ trước khi tạo mới
// this.ocrData = this.ocrData.filter(b => !b.isManual);
let coords, text;
// Kiểm tra xem ocrData đã có box nào với field này chưa
const existingBox = this.ocrData.find(b => b.field === field && !b.isManual);
if (existingBox) {
coords = existingBox.bbox;
text = existingBox.text;
console.log(`Using ocrData for field "${field}":`, coords, text);
} else if (this.manualBoxData[field]) {
// Nếu không có trong ocrData, dùng manualBoxData
coords = this.manualBoxData[field].coords;
text = this.manualBoxData[field].text;
console.log(`Using manualBoxData for field "${field}":`, coords, text);
}
// Nếu có coords thì tạo manual box
if (coords) {
this.createManualBoxFromDB(field, coords, text);
}
// Tìm lại index của box vừa tạo để set active
let idx = -1;
console.log(`Highlighting field: ${field}`);
for (let i = this.ocrData.length - 1; i >= 0; i--) {
const it = this.ocrData[i];
if (!it.isDeleted && it.field === field) {
......@@ -522,32 +586,27 @@
break;
}
}
console.log('ssss', idx, this.ocrData[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];
console.log(`Scrolling to box at index ${index}:`, item);
if (!item || item.isDeleted) return;
// Tính vị trí hiển thị của box
// const [x1, y1, x2, y2] = item.bbox;
const [x1, y1, x2, y2] = item.field_xy.split(',').map(Number);
console.log(`Box coordinates for scrolling: [${x1}, ${y1}, ${x2}, ${y2}]`);
const [x1, y1, x2, y2] = item.bbox;
//const [x1, y1, x2, y2] = item.field_xy.split(',').map(Number);
if (!this.imageWidth || !this.imageHeight || !this.$refs.pdfImage) return;
const displayedWidth = this.$refs.pdfImage.clientWidth;
......@@ -587,9 +646,6 @@
const boxElement = document.querySelector(`[data-field="${item.field}"]`);
if (boxElement) {
boxElement.classList.add('focus-highlight');
setTimeout(() => {
boxElement.classList.remove('focus-highlight');
}, 2000);
}
});
},
......@@ -683,6 +739,7 @@
e.stopPropagation();
e.preventDefault();
}
,
applyMapping() {
......@@ -707,9 +764,6 @@
const manualIndex = this.manualIndex;
const newBbox = this.ocrData[manualIndex].bbox;
console.log(`33333 ${manualIndex} with field "${this.manualField}" new box ${newBbox}`);
// console.log('Applying manual mapping for field:', this.manualField);
// console.log('Manual bbox:', newBbox);
let combinedText = [];
let foundItems = [];
......@@ -725,8 +779,6 @@
}
});
// console.log('Found OCR items in manual area:', foundItems);
// Sắp xếp các item theo vị trí (từ trái sang phải, từ trên xuống dưới)
foundItems.sort((a, b) => {
// Ưu tiên theo Y trước (hàng), sau đó theo X (cột)
......@@ -745,6 +797,8 @@
// console.log('Combined text:', finalText);
// Gán field và text cho box manual
console.log(`Assigning manual field "${this.manualField}" to box at index ${manualIndex} with text: "${finalText}"`);
// console.log(this.ocrData[manualIndex]);
this.assignFieldToBox(manualIndex, this.manualField, finalText);
// Reset trạng thái chọn
......@@ -794,18 +848,7 @@
const startIndex = Math.floor(startRatio * text.length);
const endIndex = Math.ceil(endRatio * text.length);
const partialText = text.substring(startIndex, endIndex).trim();
// console.log('Partial text calculation:', {
// originalText: text,
// bbox: bbox,
// selectBbox: selectBbox,
// startRatio, endRatio,
// startIndex, endIndex,
// partialText
// });
return partialText;
return text.substring(startIndex, endIndex).trim();
},
getSelectStyle(item) {
if (!this.imageWidth) return { position: 'absolute' };
......@@ -824,133 +867,6 @@
};
},
// Debug method để kiểm tra tọa độ
debugCoordinates() {
console.log('=== DEBUG COORDINATES ===');
console.log('Image dimensions:', {
natural: { width: this.imageWidth, height: this.imageHeight },
displayed: {
width: this.$refs.pdfImage?.clientWidth,
height: this.$refs.pdfImage?.clientHeight
}
});
console.log('Scale factors:', {
scaleX: this.$refs.pdfImage ? this.$refs.pdfImage.clientWidth / this.imageWidth : 'N/A',
scaleY: this.$refs.pdfImage ? this.$refs.pdfImage.clientHeight / this.imageHeight : 'N/A'
});
console.log('OCR Data with coordinates:');
this.ocrData.forEach((item, index) => {
if (!item.isDeleted) {
console.log(`Item ${index}:`, {
text: item.text,
bbox: item.bbox,
field: item.field,
isManual: item.isManual
});
}
});
console.log('=== END DEBUG ===');
},
// Kiểm tra và sửa lại tọa độ của các box manual
validateManualBoxes() {
if (!this.imageWidth || !this.imageHeight) {
console.log('validateManualBoxes: Image not loaded yet');
return;
}
this.ocrData.forEach((item, index) => {
if (item.isManual && !item.isDeleted) {
const [x1, y1, x2, y2] = item.bbox;
const isValid = (
x1 >= 0 && y1 >= 0 &&
x2 > x1 && y2 > y1 &&
x2 <= this.imageWidth && y2 <= this.imageHeight
);
if (!isValid) {
// Thử sửa lại tọa độ nếu có thể
this.fixManualBoxCoordinates(item, index);
}
}
});
console.log('=== END VALIDATION ===');
// Force re-render để đảm bảo các box được vẽ đúng
this.$nextTick(() => {
this.$forceUpdate();
});
},
// Sửa lại tọa độ của box manual nếu bị lỗi
fixManualBoxCoordinates(item, index) {
console.log(`Attempting to fix manual box ${index}:`, item);
// Nếu tọa độ âm, đặt về 0
let [x1, y1, x2, y2] = item.bbox;
if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0;
if (x2 <= x1) x2 = x1 + 100; // Tạo width mặc định
if (y2 <= y1) y2 = y1 + 50; // Tạo height mặc định
// Đảm bảo không vượt quá image bounds
if (x2 > this.imageWidth) x2 = this.imageWidth;
if (y2 > this.imageHeight) y2 = this.imageHeight;
const fixedBbox = [x1, y1, x2, y2];
console.log(`Fixed bbox for manual box ${index}:`, {
original: item.bbox,
fixed: fixedBbox
});
// Cập nhật tọa độ
this.$set(this.ocrData[index], 'bbox', fixedBbox);
},
// Test method để tạo box manual test
testManualBox() {
if (!this.imageWidth || !this.imageHeight) {
alert('Image chưa được load. Vui lòng đợi image load xong.');
return;
}
// Tạo một box manual test ở giữa image
const centerX = Math.round(this.imageWidth / 2);
const centerY = Math.round(this.imageHeight / 2);
const boxSize = 100;
const testBbox = [
centerX - boxSize/2, // x1
centerY - boxSize/2, // y1
centerX + boxSize/2, // x2
centerY + boxSize/2 // y2
];
console.log('Creating test manual box:', {
bbox: testBbox,
imageDimensions: { width: this.imageWidth, height: this.imageHeight }
});
// Thêm box test
this.ocrData.push({
text: "TEST BOX",
bbox: testBbox,
field: "test_field",
isManual: true,
showDelete: true,
isDeleted: false,
hideBorder: false
});
// Force re-render
this.$forceUpdate();
},
// Tạo box manual từ tọa độ trong DB
createManualBoxFromDB(fieldName, coordinates, text) {
if (!this.imageWidth || !this.imageHeight) {
......@@ -971,13 +887,6 @@
const [x1, y1, x2, y2] = coords;
console.log('Creating manual box from DB:', {
fieldName,
coordinates,
parsed: coords,
imageDimensions: { width: this.imageWidth, height: this.imageHeight }
});
// Kiểm tra tọa độ có hợp lệ không
if (x1 >= 0 && y1 >= 0 && x2 > x1 && y2 > y1 &&
x2 <= this.imageWidth && y2 <= this.imageHeight) {
......@@ -988,13 +897,13 @@
bbox: coords,
field: fieldName,
isManual: true,
showDelete: true,
showDelete: false,
isDeleted: false,
hideBorder: false
hideBorder: true
};
this.ocrData.push(manualBox);
console.log('Manual box created successfully:', manualBox);
// console.log('Manual box created successfully:', manualBox);
// Force re-render
this.$forceUpdate();
......