首次提交:初始化项目代码

This commit is contained in:
sunct
2026-07-31 14:42:05 +08:00
commit ecca407485
84 changed files with 24158 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
<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>