Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Satini_pvduc
/
ocrpdf
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Authored by
tien_nemo
2025-08-18 16:31:43 +0700
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
a3c898f31e27cf730e6272b912db8051ff3549ce
a3c898f3
1 parent
d5d6a6b7
format code
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
8 additions
and
138 deletions
resources/views/ocr/index.blade.php
resources/views/ocr/index.blade.php
View file @
a3c898f
...
...
@@ -62,7 +62,6 @@
<body>
<meta
name=
"csrf-token"
content=
"{{ csrf_token() }}"
>
<div
id=
"app"
>
<!-- Right: PDF viewer + select tool -->
<div
class=
"right-panel"
>
<div
class=
"pdf-container"
ref=
"pdfContainer"
@
mousedown=
"startSelect"
...
...
@@ -120,17 +119,15 @@
</div>
</div>
<!-- Left: Form inputs -->
<div
class=
"left-panel"
>
<div
v-for=
"field in fieldOptions"
:key=
"field.value"
class=
"form-group"
>
<label>
@{{ field.label }}
</label>
<input
v-model=
"formData[field.value]"
@
focus=
"highlightField(field.value)"
@
click=
"onInputClick(field.value)"
@
blur=
"
onInputBlur(field.value
)"
@
blur=
"
removeAllFocus(
)"
>
{{-- :readonly="field.value === 'customer_name'
&&
!hasCustomerNameXY"--}}
</div>
<button
@
click=
"saveTemplate"
>
💾Save
</button>
</div>
...
...
@@ -160,7 +157,6 @@
}
},
created
()
{
// Chỉ tạo formData cho các field cần mapping
this
.
fieldOptions
.
forEach
(
f
=>
{
this
.
$set
(
this
.
formData
,
f
.
value
,
""
);
...
...
@@ -171,7 +167,6 @@
// Thêm event listener để xóa focus khi click ra ngoài
document
.
addEventListener
(
'click'
,
(
e
)
=>
{
// Nếu click không phải vào input hoặc box
if
(
!
e
.
target
.
closest
(
'.left-panel'
)
&&
!
e
.
target
.
closest
(
'.bbox'
))
{
this
.
removeAllFocus
();
}
...
...
@@ -191,12 +186,11 @@
// Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại
this
.
ocrData
=
this
.
ocrData
.
filter
((
box
,
i
)
=>
{
if
(
i
===
index
)
return
true
;
// Giữ lại box hiện tại
if
(
i
===
index
)
return
true
;
if
(
box
.
field
===
fieldName
)
{
// Xóa box có field trùng lặp
return
false
;
}
return
true
;
// Giữ lại các box khác
return
true
;
});
// Cập nhật lại index sau khi filter
...
...
@@ -214,8 +208,7 @@
this
.
ocrData
[
newIndex
].
field_xy
=
null
;
}
// Gán field mới
const
bbox
=
this
.
ocrData
[
newIndex
].
bbox
;
// tọa độ OCR gốc [x1, y1, x2, y2]
const
bbox
=
this
.
ocrData
[
newIndex
].
bbox
;
const
x1
=
bbox
[
0
];
const
y1
=
bbox
[
1
];
...
...
@@ -223,86 +216,17 @@
const
h
=
bbox
[
3
];
const
xyStr
=
`
${
x1
}
,
${
y1
}
,
${
w
}
,
${
h
}
`
;
this
.
ocrData
[
newIndex
].
field
=
fieldName
;
this
.
ocrData
[
newIndex
].
field_xy
=
xyStr
;
// Set text
this
.
formData
[
fieldName
]
=
(
text
!==
null
?
text
:
(
this
.
ocrData
[
newIndex
].
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
;
}
},
assignFieldToBox
(
index
,
fieldName
,
text
=
null
)
{
console
.
log
(
`Assigning field "
${
fieldName
}
" to box at index
${
index
}
with text: "
${
text
}
"`
);
if
(
index
==
null
)
return
;
// 1. Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại
this
.
ocrData
=
this
.
ocrData
.
filter
((
box
,
i
)
=>
{
if
(
i
===
index
)
return
true
;
// Giữ lại box hiện tại
if
(
box
.
field
===
fieldName
)
{
// Xóa box có field trùng lặp
return
false
;
}
return
true
;
// Giữ lại các box khác
});
// 2. Cập nhật lại index sau khi filter
const
newIndex
=
this
.
ocrData
.
findIndex
(
box
=>
box
.
bbox
===
this
.
ocrData
[
index
]?.
bbox
);
if
(
newIndex
===
-
1
)
return
;
// 3. Nếu box này từng gán field khác thì bỏ
const
prev
=
this
.
ocrData
[
newIndex
]?.
field
;
if
(
prev
&&
prev
!==
fieldName
)
{
if
(
prev
===
'customer_name'
)
{
this
.
hasCustomerNameXY
=
false
;
this
.
customer_name_xy
=
''
;
}
this
.
ocrData
[
newIndex
].
field
=
null
;
this
.
ocrData
[
newIndex
].
field_xy
=
null
;
}
// 4. Gán field mới
const
bbox
=
this
.
ocrData
[
newIndex
].
bbox
;
// tọa độ OCR gốc [x1, y1, x2, y2]
console
.
log
(
'bbox'
,
bbox
);
const
[
x1
,
y1
,
w
,
h
]
=
bbox
;
const
xyStr
=
`
${
x1
}
,
${
y1
}
,
${
w
}
,
${
h
}
`
;
this
.
ocrData
[
newIndex
].
field
=
fieldName
;
this
.
ocrData
[
newIndex
].
field_xy
=
xyStr
;
// 5. Gán text
const
finalText
=
text
!==
null
?
text
:
(
this
.
ocrData
[
newIndex
].
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
[
newIndex
].
bbox
,
finalText
);
// Cập nhật manualBoxData
this
.
manualBoxData
[
fieldName
]
=
{
coords
:
this
.
ocrData
[
newIndex
].
bbox
,
text
:
finalText
};
}
// 6. Active index
this
.
activeIndex
=
newIndex
;
// 7. Nếu là customer_name
if
(
fieldName
===
'customer_name'
)
{
this
.
hasCustomerNameXY
=
true
;
this
.
customer_name_xy
=
xyStr
;
}
},
async
saveTemplate
()
{
let
customer_name
=
null
;
...
...
@@ -315,7 +239,6 @@
fields
=
this
.
manualBoxData
;
console
.
log
(
'Using manualBoxData for customer_name:'
,
customer_name
,
customer_coords
);
}
else
{
// Không có → tìm trong ocrData
const
found
=
this
.
ocrData
.
find
(
item
=>
item
.
field
===
'customer_name'
);
if
(
found
)
{
customer_name
=
found
.
text
;
...
...
@@ -325,20 +248,17 @@
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
]
=
{
text
:
box
.
field
,
coords
:
box
.
field_xy
||
''
};
}
});
// // convert to array
fields
=
(
fieldsByName
);
console
.
log
(
'Using ocrData for customer_name:'
,
customer_name
,
customer_coords
);
}
// console.log('fields:', fields);
if
(
!
customer_coords
||
!
customer_name
)
{
alert
(
"Bạn phải map customer_name (quét/select) trước khi lưu."
);
return
;
...
...
@@ -350,7 +270,7 @@
customer_name_xy
:
customer_coords
||
[],
fields
:
fields
};
// console.log(fields);
try
{
const
res
=
await
fetch
(
'/ocr/save-template'
,
{
method
:
'POST'
,
...
...
@@ -377,7 +297,6 @@
const
item
=
this
.
ocrData
[
index
];
if
(
item
.
isManual
)
{
const
manualBbox
=
item
.
bbox
;
// Hiện lại border các box OCR gốc nằm trong vùng thủ công
this
.
ocrData
.
forEach
(
o
=>
{
if
(
!
o
.
isManual
&&
this
.
isBoxInside
(
o
.
bbox
,
manualBbox
))
{
...
...
@@ -415,17 +334,8 @@
this
.
ocrData
=
data
.
ocrData
;
this
.
pdfImageUrl
=
data
.
pdfImageUrl
;
this
.
fieldOptions
=
data
.
fieldOptions
;
// this.formData = data.formData;
this
.
dataMapping
=
data
.
dataMapping
;
this
.
is_template
=
data
.
is_template
;
// Đợi image load xong trước khi xử lý
// if (this.$refs.pdfImage && this.$refs.pdfImage.complete) {
// // this.processLoadedData();
// } else {
// console.log('Image not loaded yet, waiting for onImageLoad');
// // Image sẽ được xử lý trong onImageLoad
// }
}
catch
(
error
)
{
console
.
error
(
'Error in loadOCRData:'
,
error
);
...
...
@@ -434,16 +344,13 @@
// Xử lý data sau khi image đã load
processLoadedData
()
{
// Tự động map field cho các box OCR dựa trên formData đã load
this
.
autoMapFieldsFromFormData
();
// Force re-render để đảm bảo các box được vẽ
this
.
$nextTick
(()
=>
{
this
.
$forceUpdate
();
});
},
// Tự động map field cho các box OCR dựa trên formData đã load từ DB
autoMapFieldsFromFormData
()
{
this
.
manualBoxData
=
{};
// reset
...
...
@@ -452,7 +359,6 @@
}
const
tolerance
=
20
;
// ngưỡng chenh lech toa do y cho cùng 1 hàng
Object
.
keys
(
this
.
dataMapping
).
forEach
(
fieldName
=>
{
let
{
coords
,
text
}
=
this
.
dataMapping
[
fieldName
];
let
foundItems
=
this
.
ocrData
...
...
@@ -470,13 +376,10 @@
const
combinedText
=
foundItems
.
map
(
f
=>
f
.
text
.
trim
());
const
finalText
=
combinedText
.
join
(
" "
);
// Lưu vào dataMapping
this
.
dataMapping
[
fieldName
]
=
{
text
:
finalText
,
coords
:
coords
};
// Nếu là template thì gán vào formData
if
(
this
.
is_template
)
{
this
.
formData
[
fieldName
]
=
finalText
&&
finalText
.
trim
()
!==
""
?
finalText
:
text
;
}
...
...
@@ -493,7 +396,6 @@
const
img
=
this
.
$refs
.
pdfImage
;
this
.
imageWidth
=
img
.
naturalWidth
;
this
.
imageHeight
=
img
.
naturalHeight
;
// Nếu đã có data, xử lý ngay
if
(
this
.
ocrData
&&
this
.
ocrData
.
length
>
0
)
{
console
.
log
(
'Image loaded and data exists, processing now'
);
this
.
processLoadedData
();
...
...
@@ -555,7 +457,6 @@
}
}
// Nếu có coords thì tạo hoặc hiển thị lại box
if
(
coords
)
{
if
(
isFromDB
)
{
// Tạo box manual từ DB (không có nút xóa)
...
...
@@ -586,8 +487,6 @@
}
},
// Scroll đến box tương ứng
scrollToBox
(
index
)
{
if
(
!
this
.
$refs
.
pdfContainer
||
index
<
0
||
index
>=
this
.
ocrData
.
length
)
return
;
...
...
@@ -623,8 +522,6 @@
});
},
// Xử lý khi click vào input
onInputClick
(
fieldName
)
{
// Kiểm tra xem field này có data không
...
...
@@ -635,12 +532,6 @@
}
},
// Xử lý khi click ra ngoài input (blur)
onInputBlur
(
fieldName
)
{
// Khi không focus vào input nào, xóa tất cả focus
this
.
removeAllFocus
();
},
// Xóa tất cả focus
removeAllFocus
()
{
// Reset active index (chỉ mất màu xanh, không xóa box)
...
...
@@ -779,8 +670,7 @@
e
.
stopPropagation
();
e
.
preventDefault
();
}
,
},
applyMapping
()
{
const
item
=
this
.
ocrData
[
this
.
selectingIndex
];
if
(
!
item
)
return
;
...
...
@@ -790,8 +680,6 @@
box
.
field
=
''
;
}
});
console
.
log
(
`mapping box
${
this
.
selectingIndex
}
with field "
${
item
.
field
}
" item.isManual:
${
item
.
isManual
}
`
);
if
(
item
.
isManual
)
{
// Nếu là manual box, chuyển sang chế độ manual mapping
this
.
manualIndex
=
this
.
selectingIndex
;
...
...
@@ -802,12 +690,9 @@
// Xử lý box OCR (có thể chưa có field hoặc đã có field)
if
(
item
.
field
)
{
// Nếu box đã có field, cập nhật lại
const
oldField
=
item
.
field
;
console
.
log
(
`Updating box OCR at index
${
this
.
selectingIndex
}
from field "
${
oldField
}
" to "
${
item
.
field
}
" "
${
item
.
bbox
}
"`
);
// Cập nhật formData để hiển thị trong input
this
.
formData
[
item
.
field
]
=
item
.
text
||
''
;
// Cập nhật manualBoxData với tọa độ mới (box OCR không có nút xóa)
this
.
manualBoxData
[
item
.
field
]
=
{
coords
:
item
.
bbox
,
...
...
@@ -819,8 +704,6 @@
if
(
this
.
dataMapping
&&
this
.
dataMapping
[
item
.
field
])
{
delete
this
.
dataMapping
[
item
.
field
];
}
// Set active index
this
.
activeIndex
=
this
.
selectingIndex
;
}
...
...
@@ -871,13 +754,8 @@
const
finalText
=
combinedText
.
join
(
" "
);
// Gán field trực tiếp cho box manual
this
.
ocrData
[
manualIndex
].
field
=
this
.
manualField
;
// Cập nhật formData để hiển thị trong input
this
.
formData
[
this
.
manualField
]
=
finalText
.
trim
();
// Cập nhật manualBoxData (box quét chọn có nút xóa)
this
.
manualBoxData
[
this
.
manualField
]
=
{
coords
:
newBbox
,
text
:
finalText
.
trim
(),
...
...
@@ -926,11 +804,7 @@
getPartialText
(
text
,
bbox
,
selectBbox
)
{
const
[
x1
,
y1
,
x2
,
y2
]
=
bbox
;
const
[
sx1
,
sy1
,
sx2
,
sy2
]
=
selectBbox
;
// Chiều rộng box OCR
const
boxWidth
=
x2
-
x1
;
const
boxHeight
=
y2
-
y1
;
// Vị trí start và end tương đối trong text
let
startRatio
=
Math
.
max
(
0
,
(
sx1
-
x1
)
/
boxWidth
);
let
endRatio
=
Math
.
min
(
1
,
(
sx2
-
x1
)
/
boxWidth
);
...
...
@@ -982,8 +856,6 @@
// Xóa box cũ có cùng fieldName trước khi tạo mới (chỉ xóa manual box)
this
.
ocrData
=
this
.
ocrData
.
filter
(
box
=>
!
(
box
.
field
===
fieldName
&&
box
.
isManual
));
// Tạo box manual từ DB (không có nút xóa)
const
manualBox
=
{
text
:
text
||
''
,
bbox
:
coords
,
...
...
@@ -1045,8 +917,6 @@
};
this
.
ocrData
.
push
(
manualBox
);
console
.
log
(
`Manual box shown successfully:
${
isFromOCR
?
'from OCR (no delete btn)'
:
'from manual selection (with delete btn)'
}
`
);
// Force re-render
this
.
$forceUpdate
();
}
else
{
...
...
Please
register
or
sign in
to post a comment