feat(h5): 首页大类+种类浏览

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-04-27 08:56:07 +08:00
parent eebd6825b9
commit a136c3db85
3 changed files with 102 additions and 2 deletions

View File

@ -0,0 +1,9 @@
<script setup>
defineProps({ value: String, count: Number })
</script>
<template>
<div class="relative px-4 py-3 rounded-card bg-white shadow-card active:bg-surface-warm cursor-pointer">
<div class="font-medium truncate">{{ value }}</div>
<div class="absolute top-2 right-3 text-xs text-muted tnum">{{ count }}</div>
</div>
</template>

View File

@ -0,0 +1,10 @@
<script setup>
defineProps({ label: String, value: String, active: Boolean })
</script>
<template>
<div class="aspect-square rounded-card shadow-card flex flex-col items-center justify-center transition active:scale-[0.98] cursor-pointer"
:class="active ? 'bg-brand text-white' : 'bg-white text-neutral-800'">
<div class="text-3xl font-semibold">{{ label.slice(0,1) }}</div>
<div class="mt-2 text-sm">{{ label }}</div>
</div>
</template>

View File

@ -1,2 +1,83 @@
<script setup>defineOptions({ name: 'Home' })</script> <script setup>
<template><div class="p-4">Home 占位</div></template> import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import MajorCategoryCard from '@/components/MajorCategoryCard.vue'
import CategoryCard from '@/components/CategoryCard.vue'
import Skeleton from '@/components/Skeleton.vue'
import { useAuthStore } from '@/store/auth'
import { useUiStore } from '@/store/ui'
import { fetchCategoriesByMajor } from '@/api/material'
defineOptions({ name: 'Home' })
const router = useRouter()
const auth = useAuthStore()
const ui = useUiStore()
const majors = [
{ value: 'architecture', label: '建筑' },
{ value: 'landscape', label: '景观' },
{ value: 'equipment', label: '设备' },
{ value: 'decoration', label: '装修' },
]
const selected = ref(ui.selectedMajor)
const categories = ref([])
const loading = ref(false)
const loadCategories = async (major) => {
if (!major) { categories.value = []; return }
loading.value = true
try { categories.value = await fetchCategoriesByMajor(major) }
finally { loading.value = false }
}
const onSelect = (v) => {
selected.value = selected.value === v ? '' : v
ui.setMajor(selected.value)
}
watch(selected, loadCategories, { immediate: true })
onMounted(async () => {
if (!auth.user) { try { await auth.loadUser() } catch {} }
})
const goCategory = (c) => router.push({ name: 'CategoryDetail', params: { major: selected.value, category: c.value } })
const onLogout = () => { auth.logout(); router.replace('/login') }
</script>
<template>
<div class="min-h-screen">
<header class="h-12 px-4 flex items-center justify-between bg-white border-b border-line">
<div class="text-sm text-muted">你好<span class="text-neutral-800 font-medium">{{ auth.user?.username || '' }}</span></div>
<button class="text-sm text-muted active:text-danger" @click="onLogout">退出</button>
</header>
<section class="p-4 grid grid-cols-2 gap-3">
<MajorCategoryCard v-for="m in majors" :key="m.value"
:label="m.label" :value="m.value" :active="selected === m.value"
@click="onSelect(m.value)" />
</section>
<section class="px-4 pb-6">
<transition name="fade">
<div v-if="selected">
<div class="text-xs text-muted mb-2">细分种类</div>
<div v-if="loading" class="grid grid-cols-2 gap-3">
<Skeleton v-for="n in 4" :key="n" class="h-14" />
</div>
<div v-else-if="categories.length" class="grid grid-cols-2 gap-3">
<CategoryCard v-for="c in categories" :key="c.value" :value="c.value" :count="c.count" @click="goCategory(c)" />
</div>
<div v-else class="py-10 text-center text-sm text-muted">该大类暂无已审核材料</div>
</div>
<div v-else class="py-10 text-center text-sm text-muted">点击上方分类查看细分种类</div>
</transition>
</section>
</div>
</template>
<style scoped>
.fade-enter-active,.fade-leave-active{transition:all .2s ease}
.fade-enter-from,.fade-leave-to{opacity:0;transform:translateY(-4px)}
</style>