136 lines
3.1 KiB
Vue
136 lines
3.1 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="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 { mdiAlertCircleOutline } from '@mdi/js'
|
|
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: mdiAlertCircleOutline,
|
|
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>
|