Files
simple-memo-wx/components/EmptyState.vue
T

185 lines
4.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="empty-state" :class="['type-' + type, { compact: compact }]" :style="wrapperStyle">
<view class="empty-illustration" :style="illustrationStyle">
<!-- 圆形光晕背景 -->
<view class="illustration-halo" :style="haloStyle"></view>
<!-- emoji 图标微信小程序兼容方案 -->
<text class="illustration-emoji">{{ emojiIcon }}</text>
</view>
<view v-if="title" class="empty-title">{{ title }}</view>
<view v-if="desc" class="empty-desc">{{ desc }}</view>
<view v-if="actionText" class="empty-action btn btn--primary btn--pill" @tap="onAction">
<text class="action-text">{{ actionText }}</text>
</view>
</view>
</template>
<script>
import themeMixin from '@/mixins/themeMixin.js'
import { THEME_CHANGE_EVENT } from '@/utils/theme'
// 类型→emoji 映射(微信小程序不支持 inline SVG,使用 emoji 替代)
const EMOJI_MAP = {
'default': '📂',
'no-data': '📂',
'no-network': '📡',
'no-search': '🔍',
'mood': '📅',
'bill': '💰',
'todo': '📋',
'review': '✍️',
'growth': '🏆',
'error': '⚠️'
}
export default {
name: 'EmptyState',
mixins: [themeMixin],
props: {
// 缺省类型:default / no-data / no-network / no-search / mood / bill / todo / review / growth / error
type: { type: String, default: 'default' },
title: { type: String, default: '暂无数据' },
desc: { type: String, default: '' },
actionText: { type: String, default: '' },
compact: { type: Boolean, default: false }
},
computed: {
emojiIcon() {
return EMOJI_MAP[this.type] || EMOJI_MAP['default']
},
wrapperStyle() {
return {
backgroundColor: this.themeConfig.bgColor
}
},
illustrationStyle() {
return {}
},
haloStyle() {
const primary = this.themeConfig.primaryColor
return {
background: `radial-gradient(circle, ${primary}33 0%, ${primary}0D 70%, transparent 100%)`
}
}
},
created() {
// 组件级钩子:EmptyState 不是 pagethemeMixin 的 onLoad/onShow 不会触发
// 这里手动监听主题变更事件,实时同步主题色
if (typeof this.syncThemeConfig === 'function') {
this.syncThemeConfig()
}
if (typeof this.watchThemeChange === 'function') {
this.watchThemeChange()
}
},
beforeUnmount() {
// 组件销毁时清理监听(避免内存泄漏)
if (this.themeChangeHandler) {
uni.$off && uni.$off(THEME_CHANGE_EVENT, this.themeChangeHandler)
}
},
methods: {
onAction() {
this.$emit('action')
}
}
}
</script>
<style lang="scss" scoped>
@import '@/uni.scss';
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
width: 100%;
box-sizing: border-box;
&.compact {
padding: 40rpx 32rpx 32rpx;
}
}
.empty-illustration {
width: 160rpx;
height: 160rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
animation: emptyFloat 4s ease-in-out infinite;
position: relative;
}
/* 圆形光晕背景 */
.illustration-halo {
position: absolute;
width: 120rpx;
height: 120rpx;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* emoji 图标 */
.illustration-emoji {
position: relative;
z-index: 1;
font-size: 50rpx;
line-height: 1;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.compact .empty-illustration {
width: 200rpx;
height: 200rpx;
margin-bottom: 8rpx;
.illustration-halo {
width: 160rpx;
height: 160rpx;
}
.illustration-emoji {
font-size: 72rpx;
}
}
@keyframes emptyFloat {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6rpx); }
}
.empty-title {
font-size: 28rpx;
font-weight: 600;
color: v-bind('themeConfig.textPrimary');
margin-top: 12rpx;
letter-spacing: 0.5rpx;
}
.empty-desc {
font-size: 24rpx;
color: v-bind('themeConfig.textSecondary');
margin-top: 12rpx;
text-align: center;
line-height: 1.6;
max-width: 480rpx;
}
.empty-action {
margin-top: 36rpx;
}
.action-text {
font-size: 28rpx;
letter-spacing: 1rpx;
}
</style>