체크인 성공 시 /api/spots/{id}/ai-mates 를 호출해 사이좋게 지낼 만한
강아지를 궁합점수·이유와 함께 표시. 로딩 스피너·빈 상태 처리 포함.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,14 @@ export interface Mate {
|
|||||||
breed: string | null
|
breed: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AiMate {
|
||||||
|
dogId: number
|
||||||
|
name: string
|
||||||
|
breed: string | null
|
||||||
|
score: number
|
||||||
|
reason: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Review {
|
export interface Review {
|
||||||
id: number
|
id: number
|
||||||
tag: string | null
|
tag: string | null
|
||||||
@@ -31,6 +39,8 @@ export interface CheckInResult {
|
|||||||
export const spotsApi = {
|
export const spotsApi = {
|
||||||
list: () => http.get<Spot[]>('/api/spots'),
|
list: () => http.get<Spot[]>('/api/spots'),
|
||||||
mates: (spotId: number) => http.get<Mate[]>(`/api/spots/${spotId}/mates`),
|
mates: (spotId: number) => http.get<Mate[]>(`/api/spots/${spotId}/mates`),
|
||||||
|
aiMates: (spotId: number, dogId: number) =>
|
||||||
|
http.get<AiMate[]>(`/api/spots/${spotId}/ai-mates?dogId=${dogId}`),
|
||||||
reviews: (spotId: number) => http.get<Review[]>(`/api/spots/${spotId}/reviews`),
|
reviews: (spotId: number) => http.get<Review[]>(`/api/spots/${spotId}/reviews`),
|
||||||
checkIn: (spotId: number, userId: number, dogId: number) =>
|
checkIn: (spotId: number, userId: number, dogId: number) =>
|
||||||
http.post<CheckInResult>(`/api/spots/${spotId}/checkins`, { userId, dogId }),
|
http.post<CheckInResult>(`/api/spots/${spotId}/checkins`, { userId, dogId }),
|
||||||
|
|||||||
+53
-1
@@ -46,6 +46,36 @@
|
|||||||
:spot-name="currentSpot.name"
|
:spot-name="currentSpot.name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- AI 궁합 추천 (체크인 후, 이 스팟의 강아지 중 사이좋게 지낼 만한 목록) -->
|
||||||
|
<div v-if="aiLoading || aiTried" class="q-mt-lg">
|
||||||
|
<div class="text-subtitle1 text-weight-bold q-mb-sm">
|
||||||
|
<q-icon name="auto_awesome" color="primary" class="q-mr-xs" />AI 궁합 추천
|
||||||
|
</div>
|
||||||
|
<div v-if="aiLoading" class="row items-center q-gutter-sm text-grey-6">
|
||||||
|
<q-spinner-dots size="24px" color="primary" />
|
||||||
|
<span class="text-caption">사이좋게 지낼 강아지를 찾는 중…</span>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<q-card v-for="m in aiMates" :key="m.dogId" flat bordered class="q-mb-sm">
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar color="primary" text-color="white" icon="favorite" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label class="text-weight-medium">
|
||||||
|
{{ m.name }}
|
||||||
|
<q-badge color="primary" class="q-ml-xs">궁합 {{ m.score }}</q-badge>
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label caption>{{ m.breed || '견종 미상' }} · {{ m.reason }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-card>
|
||||||
|
<div v-if="!aiMates.length" class="text-caption text-grey-5">
|
||||||
|
지금 이 스팟엔 잘 맞는 강아지가 없어요.
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 스팟 한줄평 (네이버 지도식 태그형) -->
|
<!-- 스팟 한줄평 (네이버 지도식 태그형) -->
|
||||||
<div class="text-subtitle1 text-weight-bold q-mt-lg q-mb-sm">
|
<div class="text-subtitle1 text-weight-bold q-mt-lg q-mb-sm">
|
||||||
{{ currentSpot ? currentSpot.name : '스팟' }} · 오늘의 한줄평
|
{{ currentSpot ? currentSpot.name : '스팟' }} · 오늘의 한줄평
|
||||||
@@ -94,7 +124,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { spotsApi, type Mate, type Review, type Spot } from '@/api/spots'
|
import { spotsApi, type AiMate, type Mate, type Review, type Spot } from '@/api/spots'
|
||||||
import { matchesApi } from '@/api/matches'
|
import { matchesApi } from '@/api/matches'
|
||||||
import { ApiError } from '@/api/http'
|
import { ApiError } from '@/api/http'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
@@ -120,6 +150,11 @@ const checkingIn = ref(false)
|
|||||||
const matchingDogId = ref<number | null>(null)
|
const matchingDogId = ref<number | null>(null)
|
||||||
const loadError = ref(false)
|
const loadError = ref(false)
|
||||||
|
|
||||||
|
// AI 궁합 추천 (체크인 후 로드)
|
||||||
|
const aiMates = ref<AiMate[]>([])
|
||||||
|
const aiLoading = ref(false)
|
||||||
|
const aiTried = ref(false)
|
||||||
|
|
||||||
onMounted(load)
|
onMounted(load)
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
@@ -142,6 +177,8 @@ async function onSelectSpot(spotId: number) {
|
|||||||
if (!spot) return
|
if (!spot) return
|
||||||
currentSpot.value = spot
|
currentSpot.value = spot
|
||||||
checkedIn.value = false
|
checkedIn.value = false
|
||||||
|
aiMates.value = []
|
||||||
|
aiTried.value = false
|
||||||
await refreshSpot(spotId)
|
await refreshSpot(spotId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +200,7 @@ async function onCheckIn() {
|
|||||||
checkedIn.value = true
|
checkedIn.value = true
|
||||||
await refreshSpot(currentSpot.value.id)
|
await refreshSpot(currentSpot.value.id)
|
||||||
$q.notify({ color: 'primary', message: '체크인 완료! 스팟 채팅이 열렸어요.', icon: 'place', position: 'top' })
|
$q.notify({ color: 'primary', message: '체크인 완료! 스팟 채팅이 열렸어요.', icon: 'place', position: 'top' })
|
||||||
|
loadAiMates() // 체크인 후 AI 궁합 추천 (비동기)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notifyError(e)
|
notifyError(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -170,6 +208,20 @@ async function onCheckIn() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 체크인 후 AI 궁합 추천 로드 — 이 스팟의 강아지 중 내 개와 사이좋게 지낼 만한 목록. */
|
||||||
|
async function loadAiMates() {
|
||||||
|
if (!currentSpot.value || dogs.currentDogId == null) return
|
||||||
|
aiLoading.value = true
|
||||||
|
aiTried.value = true
|
||||||
|
try {
|
||||||
|
aiMates.value = await spotsApi.aiMates(currentSpot.value.id, dogs.currentDogId)
|
||||||
|
} catch {
|
||||||
|
aiMates.value = []
|
||||||
|
} finally {
|
||||||
|
aiLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onMatch(mate: Mate) {
|
async function onMatch(mate: Mate) {
|
||||||
if (dogs.currentDogId == null) {
|
if (dogs.currentDogId == null) {
|
||||||
$q.notify({ color: 'warning', message: '먼저 우리 개를 등록해 주세요.', icon: 'pets', position: 'top' })
|
$q.notify({ color: 'warning', message: '먼저 우리 개를 등록해 주세요.', icon: 'pets', position: 'top' })
|
||||||
|
|||||||
Reference in New Issue
Block a user