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-13 23:01:12 +0700
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
93e15a32716c9576bcdd58b8a10c4befdb88f474
93e15a32
1 parent
c70391a4
test ok
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
244 additions
and
75 deletions
resources/views/ocr/index.blade.php
resources/views/ocr/index.blade.php
View file @
93e15a3
...
...
@@ -25,12 +25,6 @@
background-color
:
rgba
(
25
,
150
,
1
,
0.4
)
!important
;
}
.bbox.focus-highlight
{
/*animation: focusPulse 2s ease-in-out;*/
border-color
:
#ff6b35
!important
;
/*background-color: rgba(255, 107, 53, 0.4) !important;*/
}
@keyframes
focusPulse
{
0
%
{
transform
:
scale
(
1
);
}
50
%
{
transform
:
scale
(
1.05
);
}
...
...
@@ -95,7 +89,7 @@
:class=
"{ active: index === activeIndex }"
:data-field=
"item.field"
:style=
"getBoxStyle(item, index)"
@
click=
"
selectingIndex = index
"
>
@
click=
"
onBoxClick(index)
"
>
<button
v-if=
"item.isManual && item.showDelete"
class=
"delete-btn"
...
...
@@ -104,7 +98,7 @@
<!-- Dropdown OCR -->
<select
v-if=
"selectingIndex !== null && ocrData[selectingIndex]
.isManual
"
<select
v-if=
"selectingIndex !== null && ocrData[selectingIndex]"
:style=
"getSelectStyle(ocrData[selectingIndex])"
v-model=
"ocrData[selectingIndex].field"
@
change=
"applyMapping"
...
...
@@ -133,7 +127,7 @@
<input
v-model=
"formData[field.value]"
@
focus=
"highlightField(field.value)"
@
click=
"onInputClick(field.value)"
{{
--
@
blur=
"removeManualBoxes"
--
}}
@
blur=
"onInputBlur(field.value)"
:readonly=
"field.value === 'customer_name' && !hasCustomerNameXY"
>
</div>
...
...
@@ -174,6 +168,14 @@
},
mounted
()
{
this
.
loadOCRData
();
// 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
();
}
});
},
computed
:
{
fieldData
()
{
...
...
@@ -190,27 +192,33 @@
mapFieldToBox
(
index
,
fieldName
,
text
=
null
)
{
if
(
index
==
null
)
return
;
// Xóa fieldName ở box khác đảm bảo mỗi field chỉ gán cho 1 box duy nhất
this
.
ocrData
.
forEach
((
box
,
i
)
=>
{
if
(
i
!==
index
&&
box
.
field
===
fieldName
)
{
box
.
field
=
null
;
box
.
field_xy
=
null
;
// 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
});
// 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
;
// Nếu box này từng gán field khác thì bỏ reset flag và tọa độ liên quan.
const
prev
=
this
.
ocrData
[
i
ndex
].
field
;
const
prev
=
this
.
ocrData
[
newI
ndex
].
field
;
if
(
prev
&&
prev
!==
fieldName
)
{
if
(
prev
===
'customer_name'
)
{
this
.
hasCustomerNameXY
=
false
;
this
.
customer_name_xy
=
''
;
}
this
.
ocrData
[
i
ndex
].
field
=
null
;
this
.
ocrData
[
i
ndex
].
field_xy
=
null
;
this
.
ocrData
[
newI
ndex
].
field
=
null
;
this
.
ocrData
[
newI
ndex
].
field_xy
=
null
;
}
// Gán field mới
const
bbox
=
this
.
ocrData
[
i
ndex
].
bbox
;
// tọa độ OCR gốc [x1, y1, x2, y2]
const
bbox
=
this
.
ocrData
[
newI
ndex
].
bbox
;
// tọa độ OCR gốc [x1, y1, x2, y2]
const
x1
=
bbox
[
0
];
const
y1
=
bbox
[
1
];
...
...
@@ -219,11 +227,11 @@
const
xyStr
=
`
${
x1
}
,
${
y1
}
,
${
w
}
,
${
h
}
`
;
this
.
ocrData
[
i
ndex
].
field
=
fieldName
;
this
.
ocrData
[
i
ndex
].
field_xy
=
xyStr
;
this
.
ocrData
[
newI
ndex
].
field
=
fieldName
;
this
.
ocrData
[
newI
ndex
].
field_xy
=
xyStr
;
// Set text
this
.
formData
[
fieldName
]
=
(
text
!==
null
?
text
:
(
this
.
ocrData
[
i
ndex
].
text
||
''
)).
trim
();
this
.
formData
[
fieldName
]
=
(
text
!==
null
?
text
:
(
this
.
ocrData
[
newI
ndex
].
text
||
''
)).
trim
();
// KHÔNG set active index (không focus)
...
...
@@ -238,50 +246,59 @@
console
.
log
(
`Assigning field "
${
fieldName
}
" to box at index
${
index
}
with text: "
${
text
}
"`
);
if
(
index
==
null
)
return
;
// 1. Xóa
box cũ của field này (nếu có)
// 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
)
=>
{
return
!
(
i
!==
index
&&
box
.
field
===
fieldName
&&
box
.
isManual
);
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. Nếu box này từng gán field khác thì bỏ
const
prev
=
this
.
ocrData
[
index
]?.
field
;
// 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
[
i
ndex
].
field
=
null
;
this
.
ocrData
[
i
ndex
].
field_xy
=
null
;
this
.
ocrData
[
newI
ndex
].
field
=
null
;
this
.
ocrData
[
newI
ndex
].
field_xy
=
null
;
}
//
3
. Gán field mới
const
bbox
=
this
.
ocrData
[
i
ndex
].
bbox
;
// tọa độ OCR gốc [x1, y1, x2, y2]
//
4
. Gán field mới
const
bbox
=
this
.
ocrData
[
newI
ndex
].
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
[
i
ndex
].
field
=
fieldName
;
this
.
ocrData
[
i
ndex
].
field_xy
=
xyStr
;
this
.
ocrData
[
newI
ndex
].
field
=
fieldName
;
this
.
ocrData
[
newI
ndex
].
field_xy
=
xyStr
;
//
4
. Gán text
const
finalText
=
text
!==
null
?
text
:
(
this
.
ocrData
[
i
ndex
].
text
||
''
);
//
5
. Gán text
const
finalText
=
text
!==
null
?
text
:
(
this
.
ocrData
[
newI
ndex
].
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
[
i
ndex
].
bbox
,
finalText
);
this
.
createManualBoxFromDB
(
fieldName
,
this
.
ocrData
[
newI
ndex
].
bbox
,
finalText
);
// Cập nhật manualBoxData
this
.
manualBoxData
[
fieldName
]
=
{
coords
:
this
.
ocrData
[
i
ndex
].
bbox
,
coords
:
this
.
ocrData
[
newI
ndex
].
bbox
,
text
:
finalText
};
}
//
5
. Active index
this
.
activeIndex
=
i
ndex
;
//
6
. Nếu là customer_name
//
6
. Active index
this
.
activeIndex
=
newI
ndex
;
//
7
. Nếu là customer_name
if
(
fieldName
===
'customer_name'
)
{
this
.
hasCustomerNameXY
=
true
;
this
.
customer_name_xy
=
xyStr
;
...
...
@@ -475,38 +492,49 @@
top
:
`
${
top
}
px`
,
width
:
`
${
width
}
px`
,
height
:
`
${
height
}
px`
,
border
:
item
.
hideBorder
?
'
none
'
:
'2px solid '
+
(
index
===
this
.
activeIndex
?
'#199601'
:
'#ff5252'
),
border
:
item
.
hideBorder
?
'
2px solid #ccc
'
:
'2px solid '
+
(
index
===
this
.
activeIndex
?
'#199601'
:
'#ff5252'
),
boxSizing
:
'border-box'
,
cursor
:
'pointer'
,
zIndex
:
item
.
isManual
?
30
:
10
};
},
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
;
let
isFromDB
=
false
;
// Kiểm tra xem field này có phải từ DB không
if
(
this
.
dataMapping
&&
this
.
dataMapping
[
field
])
{
isFromDB
=
true
;
coords
=
this
.
dataMapping
[
field
].
coords
;
text
=
this
.
dataMapping
[
field
].
text
;
console
.
log
(
`Using dataMapping for field "
${
field
}
":`
,
coords
,
text
);
}
else
{
// 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
);
const
existingBox
=
this
.
ocrData
.
find
(
b
=>
b
.
field
===
field
&&
!
b
.
isDeleted
);
if
(
existingBox
)
{
coords
=
existingBox
.
bbox
;
text
=
existingBox
.
text
;
console
.
log
(
`Using ocrData
for field "
${
field
}
":`
,
coords
,
text
);
console
.
log
(
`Using existing box
for field "
${
field
}
":`
,
coords
,
text
);
}
else
if
(
this
.
manualBoxData
[
field
])
{
// Nếu không có trong ocrData, dùng manualBoxData
// Nếu không có trong ocrData, dùng manualBoxData (tọa độ mới)
coords
=
this
.
manualBoxData
[
field
].
coords
;
text
=
this
.
manualBoxData
[
field
].
text
;
console
.
log
(
`Using manualBoxData for field "
${
field
}
":`
,
coords
,
text
);
console
.
log
(
`Using manualBoxData (new coordinates) for field "
${
field
}
":`
,
coords
,
text
);
}
}
// Nếu có coords thì tạo
manual
box
// 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)
this
.
createManualBoxFromDB
(
field
,
coords
,
text
);
}
else
{
// Hiển thị lại box manual đã quét chọn (có nút xóa)
this
.
showManualBox
(
field
,
coords
,
text
);
}
}
// Tìm lại index của box
vừa tạo
để set active
// Tìm lại index của box để set active
let
idx
=
-
1
;
for
(
let
i
=
this
.
ocrData
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
it
=
this
.
ocrData
[
i
];
...
...
@@ -519,7 +547,8 @@
if
(
idx
!==
-
1
)
{
this
.
activeIndex
=
idx
;
this
.
scrollToBox
(
idx
);
this
.
focusOnBox
(
idx
);
// Reset selectingIndex để không hiển thị dropdown khi highlight từ input
this
.
selectingIndex
=
null
;
}
else
{
this
.
activeIndex
=
null
;
}
...
...
@@ -563,32 +592,82 @@
});
},
// Focus vào box (thêm hiệu ứng nhấp nháy)
focusOnBox
(
index
)
{
if
(
index
<
0
||
index
>=
this
.
ocrData
.
length
)
return
;
const
item
=
this
.
ocrData
[
index
];
if
(
!
item
||
item
.
isDeleted
)
return
;
// Thêm class để tạo hiệu ứng focus
this
.
$nextTick
(()
=>
{
const
boxElement
=
document
.
querySelector
(
`[data-field="
${
item
.
field
}
"]`
);
if
(
boxElement
)
{
boxElement
.
classList
.
add
(
'focus-highlight'
);
}
});
},
// Xử lý khi click vào input
onInputClick
(
fieldName
)
{
// Kiểm tra xem field này có data không
const
fieldValue
=
this
.
formData
[
fieldName
];
if
(
fieldValue
&&
fieldValue
.
trim
())
{
// Nếu có data, highlight và focus vào box tương ứng
// Chỉ khi click vào input mới focus và chuyển trạng thái active
// Nếu có data từ DB, highlight và focus vào box tương ứng
this
.
highlightField
(
fieldName
);
}
},
// 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)
this
.
activeIndex
=
null
;
// Ẩ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)
this
.
ocrData
.
forEach
(
item
=>
{
if
(
item
.
isManual
&&
!
item
.
showDelete
)
{
// Box manual từ DB (không có nút xóa) - ẩn hoàn toàn
item
.
isDeleted
=
true
;
}
});
// Đảm bảo tất cả box OCR đều hiển thị (chỉ ẩn border khi cần thiết)
this
.
ocrData
.
forEach
(
item
=>
{
if
(
!
item
.
isManual
&&
item
.
hideBorder
)
{
// Chỉ ẩn border cho box OCR nằm trong vùng manual
item
.
hideBorder
=
true
;
}
});
},
// Xử lý khi click vào box
onBoxClick
(
index
)
{
const
item
=
this
.
ocrData
[
index
];
// Kiểm tra xem field này có phải từ DB không
const
isFromDB
=
this
.
dataMapping
&&
this
.
dataMapping
[
item
.
field
];
// Kiểm tra xem data có được ghi đè không (so sánh với data gốc từ DB)
const
isDataOverridden
=
item
.
field
&&
isFromDB
&&
this
.
formData
[
item
.
field
]
!==
this
.
dataMapping
[
item
.
field
].
text
;
if
(
item
.
isManual
)
{
// Manual box
if
(
isFromDB
&&
!
isDataOverridden
)
{
// Manual box từ DB chưa ghi đè, KHÔNG cho chọn option
this
.
activeIndex
=
index
;
this
.
selectingIndex
=
null
;
}
else
{
// Manual box từ DB có data ghi đè HOẶC manual box bình thường, CHO PHÉP chọn option
this
.
selectingIndex
=
index
;
}
}
else
if
(
item
.
field
)
{
// Box OCR có field
if
(
isFromDB
&&
!
isDataOverridden
)
{
// Box có field từ DB chưa ghi đè, KHÔNG cho chọn option
this
.
activeIndex
=
index
;
this
.
selectingIndex
=
null
;
}
else
{
// Box có field từ DB đã ghi đè HOẶC field mới, CHO PHÉP chọn option
this
.
selectingIndex
=
index
;
}
}
else
{
// Box OCR thông thường (chưa có field), cho phép hiển thị dropdown
this
.
selectingIndex
=
index
;
}
},
startSelect
(
e
)
{
if
(
this
.
isMappingManually
||
e
.
button
!==
0
)
return
;
this
.
isSelecting
=
true
;
...
...
@@ -673,19 +752,41 @@
,
applyMapping
()
{
const
item
=
this
.
ocrData
[
this
.
selectingIndex
];
if
(
!
item
)
return
;
if
(
item
&&
item
.
isManual
)
{
if
(
item
.
isManual
)
{
// Nếu là manual box, chuyển sang chế độ manual mapping
this
.
manualIndex
=
this
.
selectingIndex
;
this
.
manualField
=
item
.
field
||
""
;
this
.
applyManualMapping
();
return
;
}
// Xử lý box OCR (có thể chưa có field hoặc đã có field)
if
(
item
.
field
)
{
// this.formData[item.field] = item.text;
// this.activeIndex = this.selectingIndex;
this
.
assignFieldToBox
(
this
.
selectingIndex
,
item
.
field
,
item
.
text
);
// Nếu box đã có field, cập nhật lại
const
oldField
=
item
.
field
;
// 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
this
.
manualBoxData
[
item
.
field
]
=
{
coords
:
item
.
bbox
,
text
:
item
.
text
||
''
};
// Xóa dataMapping cũ để tránh focus về tọa độ cũ
if
(
this
.
dataMapping
&&
this
.
dataMapping
[
item
.
field
])
{
delete
this
.
dataMapping
[
item
.
field
];
}
// Set active index
this
.
activeIndex
=
this
.
selectingIndex
;
console
.
log
(
`Updated field "
${
item
.
field
}
" for box at index
${
this
.
selectingIndex
}
with new coordinates`
);
}
this
.
selectingIndex
=
null
;
},
applyManualMapping
()
{
...
...
@@ -723,17 +824,34 @@
});
const
finalText
=
combinedText
.
join
(
" "
);
//
console.log('Combined text:', finalText);
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
);
// 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
this
.
manualBoxData
[
this
.
manualField
]
=
{
coords
:
newBbox
,
text
:
finalText
.
trim
()
};
// Xóa dataMapping cũ để tránh focus về tọa độ cũ
if
(
this
.
dataMapping
&&
this
.
dataMapping
[
this
.
manualField
])
{
delete
this
.
dataMapping
[
this
.
manualField
];
}
// Reset trạng thái chọn
this
.
isMappingManually
=
false
;
this
.
selectBox
.
show
=
false
;
this
.
selectBox
.
showDropdown
=
false
;
this
.
manualField
=
""
;
this
.
manualIndex
=
null
;
},
isBoxInside
(
inner
,
outer
)
{
...
...
@@ -820,7 +938,10 @@
if
(
x1
>=
0
&&
y1
>=
0
&&
x2
>
x1
&&
y2
>
y1
&&
x2
<=
this
.
imageWidth
&&
y2
<=
this
.
imageHeight
)
{
// Tạo box manual
// 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
,
...
...
@@ -839,6 +960,54 @@
}
else
{
console
.
warn
(
'Invalid coordinates for manual box:'
,
coords
);
}
},
// Hiển thị lại box manual đã quét chọn (có nút xóa)
showManualBox
(
fieldName
,
coordinates
,
text
)
{
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
)
{
console
.
log
(
'Cannot show manual box: Image not loaded'
);
return
;
}
// Parse coordinates
let
coords
;
if
(
typeof
coordinates
===
'string'
)
{
coords
=
coordinates
.
split
(
','
).
map
(
Number
);
}
else
if
(
Array
.
isArray
(
coordinates
))
{
coords
=
coordinates
;
}
else
{
console
.
error
(
'Invalid coordinates format:'
,
coordinates
);
return
;
}
const
[
x1
,
y1
,
x2
,
y2
]
=
coords
;
// 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
)
{
// Xóa box cũ có cùng fieldName trước khi hiển thị lại
this
.
ocrData
=
this
.
ocrData
.
filter
(
box
=>
!
(
box
.
field
===
fieldName
&&
box
.
isManual
));
// Hiển thị lại box manual đã quét chọn (có nút xóa)
const
manualBox
=
{
text
:
text
||
''
,
bbox
:
coords
,
field
:
fieldName
,
isManual
:
true
,
showDelete
:
true
,
isDeleted
:
false
,
hideBorder
:
false
};
this
.
ocrData
.
push
(
manualBox
);
console
.
log
(
'Manual box shown successfully:'
,
manualBox
);
// Force re-render
this
.
$forceUpdate
();
}
else
{
console
.
warn
(
'Invalid coordinates for manual box:'
,
coords
);
}
}
}
...
...
Please
register
or
sign in
to post a comment