diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css
index adc0517..45c1421 100644
--- a/frontend/src/styles/main.css
+++ b/frontend/src/styles/main.css
@@ -728,6 +728,50 @@ button:disabled,
height: 28px;
}
+.page-header--skeleton {
+ pointer-events: none;
+}
+
+.skeleton-detail-section {
+ pointer-events: none;
+}
+
+.skeleton-detail-section .detail-section__body {
+ gap: 14px;
+}
+
+.skeleton-row-list li {
+ min-height: 43px;
+}
+
+.skeleton-appearance-row {
+ display: grid;
+ grid-template-columns: max-content minmax(0, 1fr);
+ gap: 12px;
+}
+
+.skeleton-summary {
+ display: grid;
+ gap: 6px;
+ width: 100%;
+}
+
+.skeleton-summary div {
+ display: grid;
+ grid-template-columns: 72px minmax(0, 1fr);
+ gap: 8px;
+}
+
+.skeleton-form-stack {
+ display: grid;
+ gap: 14px;
+}
+
+.skeleton-auth-state {
+ display: grid;
+ gap: 12px;
+}
+
@keyframes shimmer {
to {
background-position: -200% 0;
diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue
index 6f478ac..a806b27 100644
--- a/frontend/src/views/AdminView.vue
+++ b/frontend/src/views/AdminView.vue
@@ -1,6 +1,7 @@
- 加载中
+
Habitat Detail
diff --git a/frontend/src/views/HabitatList.vue b/frontend/src/views/HabitatList.vue
index 7d4225d..67477a9 100644
--- a/frontend/src/views/HabitatList.vue
+++ b/frontend/src/views/HabitatList.vue
@@ -4,11 +4,12 @@ import EditMeta from '../components/EditMeta.vue';
import EntityChips from '../components/EntityChips.vue';
import EntityCard from '../components/EntityCard.vue';
import PageHeader from '../components/PageHeader.vue';
-import StatusMessage from '../components/StatusMessage.vue';
+import Skeleton from '../components/Skeleton.vue';
import { api, type Habitat } from '../services/api';
const habitats = ref([]);
const loading = ref(true);
+const skeletonCardCount = 6;
onMounted(async () => {
habitats.value = await api.habitats();
@@ -22,7 +23,21 @@ onMounted(async () => {
Habitats
- 加载中
+
diff --git a/frontend/src/views/ItemDetail.vue b/frontend/src/views/ItemDetail.vue
index bc446f0..8e13a5e 100644
--- a/frontend/src/views/ItemDetail.vue
+++ b/frontend/src/views/ItemDetail.vue
@@ -5,7 +5,7 @@ import DetailSection from '../components/DetailSection.vue';
import EditMeta from '../components/EditMeta.vue';
import EntityChips from '../components/EntityChips.vue';
import PageHeader from '../components/PageHeader.vue';
-import StatusMessage from '../components/StatusMessage.vue';
+import Skeleton from '../components/Skeleton.vue';
import { api, type ItemDetail } from '../services/api';
const route = useRoute();
@@ -29,7 +29,58 @@ onMounted(async () => {
- 加载中
+
Item Detail
diff --git a/frontend/src/views/PokemonDetail.vue b/frontend/src/views/PokemonDetail.vue
index d3b59ed..338839a 100644
--- a/frontend/src/views/PokemonDetail.vue
+++ b/frontend/src/views/PokemonDetail.vue
@@ -5,7 +5,7 @@ import DetailSection from '../components/DetailSection.vue';
import EditMeta from '../components/EditMeta.vue';
import EntityChips from '../components/EntityChips.vue';
import PageHeader from '../components/PageHeader.vue';
-import StatusMessage from '../components/StatusMessage.vue';
+import Skeleton from '../components/Skeleton.vue';
import { api, type PokemonDetail } from '../services/api';
const route = useRoute();
@@ -81,7 +81,62 @@ onMounted(async () => {
- 加载中
+
Pokédex Detail
diff --git a/frontend/src/views/PokemonList.vue b/frontend/src/views/PokemonList.vue
index 2efdf36..4937df3 100644
--- a/frontend/src/views/PokemonList.vue
+++ b/frontend/src/views/PokemonList.vue
@@ -5,7 +5,7 @@ import EntityChips from '../components/EntityChips.vue';
import EntityCard from '../components/EntityCard.vue';
import FilterPanel from '../components/FilterPanel.vue';
import PageHeader from '../components/PageHeader.vue';
-import StatusMessage from '../components/StatusMessage.vue';
+import Skeleton from '../components/Skeleton.vue';
import TagsSelect from '../components/TagsSelect.vue';
import { api, type Options, type Pokemon } from '../services/api';
@@ -18,6 +18,8 @@ const skillIds = ref([]);
const skillMode = ref<'any' | 'all'>('any');
const favoriteThingIds = ref([]);
const favoriteThingMode = ref<'any' | 'all'>('any');
+const filterSkeletonWidths = ['52px', '92px', '48px', '72px'];
+const skeletonCardCount = 6;
const query = computed(() => ({
search: search.value,
@@ -88,8 +90,33 @@ watch(query, loadPokemon);
+
+
+
- 加载中
+
{
- 加载中
+
Recipe Detail
diff --git a/frontend/src/views/VerifyEmailView.vue b/frontend/src/views/VerifyEmailView.vue
index 3eb2bb7..26234d4 100644
--- a/frontend/src/views/VerifyEmailView.vue
+++ b/frontend/src/views/VerifyEmailView.vue
@@ -2,6 +2,7 @@
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import PageHeader from '../components/PageHeader.vue';
+import Skeleton from '../components/Skeleton.vue';
import StatusMessage from '../components/StatusMessage.vue';
import { api } from '../services/api';
@@ -37,11 +38,15 @@ onMounted(async () => {
Trainer Pass
- 正在验证邮箱
+
+
+
+
+
{{ message }}
{{ errorMessage }}
- 去登录
+ 去登录