feat(h5): 新增通用组件 NavBar/Toast/Chip/StarLevel/Skeleton
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
f410a95a3d
commit
82490bc786
|
|
@ -1,3 +1,6 @@
|
|||
<script setup>
|
||||
import Toast from '@/components/Toast.vue'
|
||||
</script>
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="page" mode="out-in">
|
||||
|
|
@ -6,6 +9,7 @@
|
|||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
<Toast />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<script setup>
|
||||
defineProps({ tone: { type: String, default: 'neutral' } })
|
||||
const map = {
|
||||
brand: 'bg-brand/10 text-brand',
|
||||
danger: 'bg-danger/10 text-danger',
|
||||
info: 'bg-info/10 text-info',
|
||||
neutral: 'bg-neutral-100 text-neutral-600',
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<span class="inline-flex items-center px-2 py-0.5 text-xs rounded-full whitespace-nowrap" :class="map[tone]">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
defineProps({ title: String, showBack: { type: Boolean, default: true } })
|
||||
const router = useRouter()
|
||||
</script>
|
||||
<template>
|
||||
<header class="sticky top-0 z-20 h-12 flex items-center bg-white/90 backdrop-blur border-b border-line">
|
||||
<button v-if="showBack" class="w-12 h-12 flex items-center justify-center active:bg-line" @click="router.back()">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M13 4l-6 6 6 6" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div v-else class="w-12 h-12"></div>
|
||||
<h1 class="flex-1 text-center text-base font-semibold truncate px-4">{{ title }}</h1>
|
||||
<div class="w-12 h-12"><slot name="right" /></div>
|
||||
</header>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<div class="animate-pulse bg-neutral-200/60 rounded" />
|
||||
</template>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<script setup>
|
||||
defineProps({ value: { type: Number, default: 0 }, max: { type: Number, default: 3 } })
|
||||
</script>
|
||||
<template>
|
||||
<span class="inline-flex items-center gap-0.5">
|
||||
<svg v-for="i in max" :key="i" width="14" height="14" viewBox="0 0 20 20"
|
||||
:fill="i <= value ? '#2F4F3F' : '#E5E5E5'">
|
||||
<path d="M10 1.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L10 14.9 4.8 17.6l1-5.8L1.5 7.7l5.9-.9z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<script setup>
|
||||
import { useToast } from '@/composables/useToast'
|
||||
const { state } = useToast()
|
||||
</script>
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div class="fixed top-2 left-0 right-0 z-50 flex flex-col items-center gap-2 pointer-events-none">
|
||||
<transition-group name="toast">
|
||||
<div v-for="t in state.list" :key="t.id"
|
||||
class="px-4 py-2 rounded-full text-sm shadow-card"
|
||||
:class="t.type==='error' ? 'bg-danger text-white' : 'bg-neutral-800/90 text-white'">
|
||||
{{ t.msg }}
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
<style>
|
||||
.toast-enter-active,.toast-leave-active{transition:all .25s ease}
|
||||
.toast-enter-from,.toast-leave-to{opacity:0;transform:translateY(-8px)}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { reactive } from 'vue'
|
||||
const state = reactive({ list: [] })
|
||||
let seq = 0
|
||||
export function useToast() {
|
||||
const show = (msg, type = 'info', duration = 2000) => {
|
||||
const id = ++seq
|
||||
state.list.push({ id, msg, type })
|
||||
setTimeout(() => { state.list = state.list.filter(x => x.id !== id) }, duration)
|
||||
}
|
||||
return { state, show }
|
||||
}
|
||||
Loading…
Reference in New Issue