app: Adapt API

This commit is contained in:
LittleChest 2025-09-27 10:10:17 +08:00
parent be40451a76
commit 56aa260e60

View File

@ -9,31 +9,32 @@
<v-card-text> <v-card-text>
<div class="flex justify-center text-center"> <div class="flex justify-center text-center">
<video <video
ref="video" ref="video"
autoplay autoplay
playsinline playsinline
muted muted
width="320" width="320"
height="240" height="240"
style="border-radius: 8px; background: #000" class="rounded-lg bg-black"
v-show="cameraActive && !photoData" v-show="cameraActive && !photoData"
></video> ></video>
<img <img
v-if="photoData" v-if="photoData"
:src="photoData" :src="photoData"
alt="自拍预览" alt="预览"
width="320" width="320"
height="240" height="240"
style="border-radius: 8px; object-fit: cover" class="rounded-lg object-cover"
/> />
</div> </div>
<div v-if="errorMsg" class="text-red-600 text-center mt-2">
{{ errorMsg }}
</div>
<div class="mt-4 flex gap-3 justify-center"> <div class="mt-4 flex gap-3 justify-center">
<v-btn color="primary" @click="startCamera" v-if="!cameraActive" <v-btn color="primary" @click="takePhoto" :disabled="!cameraActive" v-if="!photoData"
>打开摄像头</v-btn
>
<v-btn color="primary" @click="takePhoto" v-if="cameraActive && !photoData"
>拍照</v-btn >拍照</v-btn
> >
<v-btn color="secondary" @click="retake" v-if="photoData">重拍</v-btn> <v-btn color="secondary" @click="retake" v-if="photoData">重拍</v-btn>
@ -50,15 +51,22 @@
</label> </label>
</div> </div>
<v-switch v-model="saveConsent" inset hide-details color="primary" class="mt-4" label="允许 littlew.top 将此信息与你的账户关联" />
<v-progress-linear <v-progress-linear
:indeterminate="true" :indeterminate="true"
v-if="loading" v-if="loading"
class="mt-4" class="mt-4"
></v-progress-linear> ></v-progress-linear>
<div v-if="responseText" class="mt-4"> <div v-if="debug" class="mt-4">
<div>调试信息</div> <div>调试信息</div>
<pre class="bg-gray-100 p-3 rounded overflow-auto">{{ responseText }}</pre> <pre class="bg-gray-100 p-3 rounded overflow-auto">{{ debug }}</pre>
</div>
<div v-if="age !== null || gender !== null" class="mt-4 text-center">
<div>年龄{{ ageDisplay }}</div>
<div>性别{{ genderDisplay }}</div>
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -78,43 +86,59 @@ export default {
photoData: null, photoData: null,
photoBlob: null, photoBlob: null,
loading: false, loading: false,
responseText: '', debug: '',
errorMsg: '',
saveConsent: true,
age: null,
gender: null,
} }
}, },
methods: { methods: {
async startCamera() { async startCamera() {
this.stream = await navigator.mediaDevices.getUserMedia({ try {
video: { facingMode: 'user' }, this.stream = await navigator.mediaDevices.getUserMedia({
audio: false, video: { facingMode: 'user' },
}) audio: false,
this.cameraActive = true })
await this.$nextTick() this.cameraActive = true
const videoEl = this.$refs.video await this.$nextTick()
videoEl.srcObject = this.stream const videoEl = this.$refs.video
await videoEl.play().catch(() => {}) videoEl.srcObject = this.stream
await videoEl.play().catch(() => {})
} catch (e) {
this.errorMsg = e
}
}, },
takePhoto() { takePhoto() {
const video = this.$refs.video try {
const canvas = document.createElement('canvas') const video = this.$refs.video
canvas.width = video.videoWidth || 320 const canvas = document.createElement('canvas')
canvas.height = video.videoHeight || 240 canvas.width = video.videoWidth || 320
const ctx = canvas.getContext('2d') canvas.height = video.videoHeight || 240
ctx.drawImage(video, 0, 0, canvas.width, canvas.height) const ctx = canvas.getContext('2d')
canvas.toBlob( ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
(blob) => { canvas.toBlob(
this.photoBlob = blob (blob) => {
this.photoData = URL.createObjectURL(blob) this.photoBlob = blob
}, this.photoData = URL.createObjectURL(blob)
'image/jpeg', },
0.9 'image/jpeg',
) 0.9
this.stopCamera() )
this.stopCamera()
} catch (e) {
this.errorMsg = e
}
}, },
retake() { retake() {
if (this.photoData) URL.revokeObjectURL(this.photoData) try {
this.photoData = null if (this.photoData) URL.revokeObjectURL(this.photoData)
this.photoBlob = null this.photoData = null
this.startCamera() this.photoBlob = null
this.startCamera()
} catch (e) {
this.errorMsg = e
}
}, },
stopCamera() { stopCamera() {
if (this.stream) { if (this.stream) {
@ -124,40 +148,69 @@ export default {
this.cameraActive = false this.cameraActive = false
}, },
onFileChange(e) { onFileChange(e) {
const file = e.target.files && e.target.files[0] try {
if (!file) return const file = e.target.files && e.target.files[0]
this.photoBlob = file this.photoBlob = file
this.photoData = URL.createObjectURL(file) this.photoData = URL.createObjectURL(file)
} catch (e) {
this.errorMsg = e
}
}, },
async uploadPhoto() { async uploadPhoto() {
if (!this.photoBlob) return alert('请先拍照或选择图片') if (!this.photoBlob) {
this.errorMsg = '请先拍照或选择图片'
return
}
this.loading = true this.loading = true
this.responseText = '' this.debug = ''
try { try {
const form = new FormData() const form = new FormData()
form.append('face', this.photoBlob, 'selfie.jpg') form.append('face', this.photoBlob, 'selfie.jpg')
const resp = await fetch('https://api.littlew.top/age', { let url = 'https://api.littlew.top/age'
if (this.saveConsent) url += '?save=true'
const resp = await fetch(url, {
method: 'POST', method: 'POST',
body: form, body: form,
}, {
credentials: 'include',
}) })
const text = await resp.text() const text = await resp.text()
try { try {
const obj = JSON.parse(text) const data = JSON.parse(text)
this.responseText = JSON.stringify(obj, null, 2) this.debug = data.raw
this.age = typeof data.age === 'number' ? data.age : -1
this.gender = typeof data.gender === 'number' ? data.gender : -1
} catch (e) { } catch (e) {
this.responseText = text this.debug = text
this.age = -1
this.gender = -1
} }
} catch (e) { } catch (e) {
this.responseText = e.message this.errorMsg = e
} finally { } finally {
this.loading = false this.loading = false
} }
}, },
}, },
computed: {
ageDisplay() {
if (this.age === null) return '未知'
return this.age > -1 ? String(this.age) : '未知'
},
genderDisplay() {
if (this.gender === null) return '未知'
if (this.gender === 0) return 'Female'
if (this.gender === 1) return 'Male'
return '未知'
},
},
beforeUnmount() { beforeUnmount() {
this.stopCamera() this.stopCamera()
}, },
mounted() {
this.startCamera()
}
} }
</script> </script>