Merge branch 'dev'
This commit is contained in:
@@ -10,6 +10,7 @@ android {
|
|||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':capacitor-app')
|
implementation project(':capacitor-app')
|
||||||
|
implementation project(':capacitor-camera')
|
||||||
implementation project(':capacitor-preferences')
|
implementation project(':capacitor-preferences')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,4 +38,8 @@
|
|||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<!-- 영수증 OCR: 카메라 촬영 / 갤러리 선택 -->
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -5,5 +5,8 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/
|
|||||||
include ':capacitor-app'
|
include ':capacitor-app'
|
||||||
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
|
include ':capacitor-camera'
|
||||||
|
project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
|
||||||
|
|
||||||
include ':capacitor-preferences'
|
include ':capacitor-preferences'
|
||||||
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||||
|
|||||||
Generated
+10
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^8.3.4",
|
"@capacitor/android": "^8.3.4",
|
||||||
"@capacitor/app": "^8.1.0",
|
"@capacitor/app": "^8.1.0",
|
||||||
|
"@capacitor/camera": "^8.2.0",
|
||||||
"@capacitor/cli": "^8.3.4",
|
"@capacitor/cli": "^8.3.4",
|
||||||
"@capacitor/core": "^8.3.4",
|
"@capacitor/core": "^8.3.4",
|
||||||
"@capacitor/preferences": "^8.0.1",
|
"@capacitor/preferences": "^8.0.1",
|
||||||
@@ -542,6 +543,15 @@
|
|||||||
"@capacitor/core": ">=8.0.0"
|
"@capacitor/core": ">=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor/camera": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-hYfrT6xpL936qoEkIpJzSnb0fQCaTkOux1cXzGBfH8QLOGqr6gSLiWZlZz/fqMPmMKJMNRBqlTQkj5fuMhVZog==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capacitor/cli": {
|
"node_modules/@capacitor/cli": {
|
||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-8.3.4.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^8.3.4",
|
"@capacitor/android": "^8.3.4",
|
||||||
"@capacitor/app": "^8.1.0",
|
"@capacitor/app": "^8.1.0",
|
||||||
|
"@capacitor/camera": "^8.2.0",
|
||||||
"@capacitor/cli": "^8.3.4",
|
"@capacitor/cli": "^8.3.4",
|
||||||
"@capacitor/core": "^8.3.4",
|
"@capacitor/core": "^8.3.4",
|
||||||
"@capacitor/preferences": "^8.0.1",
|
"@capacitor/preferences": "^8.0.1",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { computed, onMounted, reactive, ref } from 'vue'
|
|||||||
import { accountApi } from '@/api/accountApi'
|
import { accountApi } from '@/api/accountApi'
|
||||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||||
import { scanReceipt } from '@/utils/receiptOcr'
|
import { scanReceipt } from '@/utils/receiptOcr'
|
||||||
|
import { Capacitor } from '@capacitor/core'
|
||||||
|
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera'
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const year = ref(now.getFullYear())
|
const year = ref(now.getFullYear())
|
||||||
@@ -59,21 +61,42 @@ const receiptInput = ref(null)
|
|||||||
const ocrRunning = ref(false)
|
const ocrRunning = ref(false)
|
||||||
const ocrProgress = ref(0)
|
const ocrProgress = ref(0)
|
||||||
const ocrResult = ref(null) // { amount, date, store }
|
const ocrResult = ref(null) // { amount, date, store }
|
||||||
function pickReceipt() {
|
async function pickReceipt() {
|
||||||
ocrResult.value = null
|
ocrResult.value = null
|
||||||
receiptInput.value?.click()
|
// 네이티브(앱): 카메라/갤러리 선택 프롬프트, 웹: 파일 선택
|
||||||
|
if (Capacitor.isNativePlatform()) {
|
||||||
|
try {
|
||||||
|
const photo = await Camera.getPhoto({
|
||||||
|
source: CameraSource.Prompt,
|
||||||
|
resultType: CameraResultType.DataUrl,
|
||||||
|
quality: 70,
|
||||||
|
correctOrientation: true,
|
||||||
|
promptLabelHeader: '영수증',
|
||||||
|
promptLabelPhoto: '갤러리에서 선택',
|
||||||
|
promptLabelPicture: '카메라로 촬영',
|
||||||
|
promptLabelCancel: '취소',
|
||||||
|
})
|
||||||
|
if (photo?.dataUrl) await runOcr(photo.dataUrl)
|
||||||
|
} catch {
|
||||||
|
// 사용자가 취소 → 무시
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
receiptInput.value?.click()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async function onReceiptFile(e) {
|
async function onReceiptFile(e) {
|
||||||
const file = e.target.files?.[0]
|
const file = e.target.files?.[0]
|
||||||
e.target.value = '' // 같은 파일 재선택 허용
|
e.target.value = '' // 같은 파일 재선택 허용
|
||||||
if (!file) return
|
if (file) await runOcr(file)
|
||||||
|
}
|
||||||
|
async function runOcr(image) {
|
||||||
ocrRunning.value = true
|
ocrRunning.value = true
|
||||||
ocrProgress.value = 0
|
ocrProgress.value = 0
|
||||||
ocrResult.value = null
|
ocrResult.value = null
|
||||||
formError.value = null
|
formError.value = null
|
||||||
try {
|
try {
|
||||||
const r = await scanReceipt(file, (p) => (ocrProgress.value = p))
|
const r = await scanReceipt(image, (p) => (ocrProgress.value = p))
|
||||||
// 추출값이 있을 때만 폼에 채움 (기존 입력 보존하되, 비어있으면 채움)
|
// 추출값이 있을 때만 폼에 채움 (메모는 비어있을 때만)
|
||||||
if (r.amount) form.amount = r.amount
|
if (r.amount) form.amount = r.amount
|
||||||
if (r.date) form.entryDate = r.date
|
if (r.date) form.entryDate = r.date
|
||||||
if (r.store && !form.memo) form.memo = r.store
|
if (r.store && !form.memo) form.memo = r.store
|
||||||
|
|||||||
Reference in New Issue
Block a user