111 lines
2.7 KiB
Vue
111 lines
2.7 KiB
Vue
<template>
|
|
<v-container class="fill-height">
|
|
<v-row align="center" justify="center" no-gutters>
|
|
<v-col cols="12" md="8" sm="10">
|
|
<v-card border class="pa-6" variant="flat">
|
|
<v-card-title class="d-flex align-center ga-3">
|
|
<v-icon :color="color" size="36">{{ icon }}</v-icon>
|
|
<div class="text-h5">{{ title }}</div>
|
|
<div class="text-caption text-medium-emphasis">{{ codeLabel }}</div>
|
|
<v-spacer />
|
|
<img alt="robot" class="robot-icon" height="32" src="@/assets/robot-svgrepo-com.svg" width="32" />
|
|
</v-card-title>
|
|
|
|
<v-card-text v-if="description" class="text-body-1 mt-4">
|
|
<p class="py-3">
|
|
{{ description }}
|
|
</p>
|
|
<v-alert v-if="backendMessage" class="mt-6" :color="color" density="compact" type="warning" variant="tonal">
|
|
{{ backendMessage }}
|
|
</v-alert>
|
|
</v-card-text>
|
|
|
|
<v-card-actions class="mt-6">
|
|
<v-btn v-if="showBack" variant="text" @click="router.back()">返回上一頁</v-btn>
|
|
<v-spacer />
|
|
<v-btn v-if="showHome" color="primary" :to="{ name: 'home' }" variant="flat">回到首頁</v-btn>
|
|
<v-btn v-if="showLogin" class="ml-2" color="primary" :to="{ name: 'login' }" variant="outlined">
|
|
前往登入
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
</v-container>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
type Props = {
|
|
code: string | number
|
|
title: string
|
|
description?: string
|
|
icon?: string
|
|
color?: string
|
|
showHome?: boolean
|
|
showLogin?: boolean
|
|
showBack?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
description: undefined,
|
|
icon: 'mdi-alert-circle-outline',
|
|
color: 'warning',
|
|
showHome: true,
|
|
showLogin: true,
|
|
showBack: true
|
|
})
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const codeLabel = computed(() => {
|
|
const code = props.code
|
|
return typeof code === 'number' ? `HTTP ${code}` : String(code)
|
|
})
|
|
|
|
const backendMessage = computed(() => {
|
|
const raw = route.query.message
|
|
if (typeof raw !== 'string') return ''
|
|
return raw.trim()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
@keyframes breathe {
|
|
0%, 100% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
transform: scale(1.05);
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
|
|
@keyframes blink {
|
|
0%, 45%, 55%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.3;
|
|
}
|
|
}
|
|
|
|
.robot-icon {
|
|
animation: breathe 3s ease-in-out infinite;
|
|
}
|
|
|
|
.robot-icon path:nth-child(2),
|
|
.robot-icon path:nth-child(3) {
|
|
animation: blink 4s ease-in-out infinite;
|
|
}
|
|
|
|
.robot-icon path:nth-child(3) {
|
|
animation-delay: 0.1s;
|
|
}
|
|
</style>
|
|
|