Files
EmailBill/Web/src/components/Global/ModernEmpty.vue
SunCheng 045158730f
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 4m47s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
refactor: 整理组件目录结构
- TransactionDetail, CategoryBillPopup 移入 Transaction/
- BudgetTypeTabs 移入 Budget/
- GlassBottomNav, ModernEmpty 移入 Global/
- Icon, IconSelector, ClassifySelector 等 8 个通用组件移入 Common/
- 更新所有相关引用路径
2026-02-21 10:10:16 +08:00

414 lines
8.2 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>
<div
class="modern-empty"
:class="[`theme-${theme}`, `size-${size}`]"
>
<div class="empty-content">
<!-- 图标容器 -->
<div
class="icon-container"
:class="{ 'with-animation': animation }"
>
<div class="icon-bg" />
<div class="icon-wrapper">
<!-- 自定义图标插槽 -->
<slot name="icon">
<svg
class="empty-icon"
viewBox="0 0 64 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<component :is="iconPath" />
</svg>
</slot>
</div>
</div>
<!-- 文字内容 -->
<div class="text-content">
<h3
v-if="title"
class="empty-title"
>
{{ title }}
</h3>
<p
v-if="description"
class="empty-description"
>
{{ description }}
</p>
</div>
<!-- 操作按钮 -->
<div
v-if="$slots.action || actionText"
class="empty-action"
>
<slot name="action">
<van-button
v-if="actionText"
type="primary"
round
size="small"
@click="$emit('action-click')"
>
{{ actionText }}
</van-button>
</slot>
</div>
</div>
</div>
</template>
<script setup>
import { computed, h } from 'vue'
const props = defineProps({
// 空状态类型search, data, inbox, calendar, finance, chart
type: {
type: String,
default: 'search'
},
// 主题色blue, green, orange, purple, gray
theme: {
type: String,
default: 'blue'
},
// 标题
title: {
type: String,
default: ''
},
// 描述文字
description: {
type: String,
default: '暂无数据'
},
// 是否显示动画
animation: {
type: Boolean,
default: true
},
// 操作按钮文字
actionText: {
type: String,
default: ''
},
// 尺寸small, medium, large
size: {
type: String,
default: 'medium'
}
})
defineEmits(['action-click'])
// 根据类型选择SVG图标路径
const iconPath = computed(() => {
const icons = {
search: () =>
h('g', [
h('circle', {
cx: '26',
cy: '26',
r: '18',
stroke: 'currentColor',
'stroke-width': '3',
fill: 'none'
}),
h('path', {
d: 'M40 40L54 54',
stroke: 'currentColor',
'stroke-width': '3',
'stroke-linecap': 'round'
})
]),
data: () =>
h('g', [
h('path', {
d: 'M8 48L22 32L36 40L56 16',
stroke: 'currentColor',
'stroke-width': '3',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
fill: 'none'
}),
h('circle', { cx: '8', cy: '48', r: '3', fill: 'currentColor' }),
h('circle', { cx: '22', cy: '32', r: '3', fill: 'currentColor' }),
h('circle', { cx: '36', cy: '40', r: '3', fill: 'currentColor' }),
h('circle', { cx: '56', cy: '16', r: '3', fill: 'currentColor' })
]),
inbox: () =>
h('g', [
h('path', {
d: 'M8 16L32 4L56 16V52C56 54.2 54.2 56 52 56H12C9.8 56 8 54.2 8 52V16Z',
stroke: 'currentColor',
'stroke-width': '3',
fill: 'none'
}),
h('path', {
d: 'M8 32H20L24 40H40L44 32H56',
stroke: 'currentColor',
'stroke-width': '3',
fill: 'none'
})
]),
calendar: () =>
h('g', [
h('rect', {
x: '8',
y: '12',
width: '48',
height: '44',
rx: '4',
stroke: 'currentColor',
'stroke-width': '3',
fill: 'none'
}),
h('path', { d: 'M8 24H56', stroke: 'currentColor', 'stroke-width': '3' }),
h('path', {
d: 'M20 8V16',
stroke: 'currentColor',
'stroke-width': '3',
'stroke-linecap': 'round'
}),
h('path', {
d: 'M44 8V16',
stroke: 'currentColor',
'stroke-width': '3',
'stroke-linecap': 'round'
})
]),
finance: () =>
h('g', [
h('circle', {
cx: '32',
cy: '32',
r: '24',
stroke: 'currentColor',
'stroke-width': '3',
fill: 'none'
}),
h('path', { d: 'M32 16V48', stroke: 'currentColor', 'stroke-width': '3' }),
h('path', {
d: 'M24 22H36C38.2 22 40 23.8 40 26C40 28.2 38.2 30 36 30H28C25.8 30 24 31.8 24 34C24 36.2 25.8 38 28 38H40',
stroke: 'currentColor',
'stroke-width': '3',
fill: 'none'
})
]),
chart: () =>
h('g', [
h('rect', { x: '12', y: '36', width: '8', height: '20', rx: '2', fill: 'currentColor' }),
h('rect', { x: '28', y: '24', width: '8', height: '32', rx: '2', fill: 'currentColor' }),
h('rect', { x: '44', y: '12', width: '8', height: '44', rx: '2', fill: 'currentColor' })
])
}
return icons[props.type] || icons.search
})
</script>
<style scoped lang="scss">
.modern-empty {
width: 100%;
padding: 40px 20px;
display: flex;
justify-content: center;
align-items: center;
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
max-width: 300px;
}
// 图标容器
.icon-container {
position: relative;
width: 120px;
height: 120px;
margin-bottom: 24px;
&.with-animation {
.icon-bg {
animation: pulse 2s ease-in-out infinite;
}
.icon-wrapper {
animation: float 3s ease-in-out infinite;
}
}
.icon-bg {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
opacity: 0.1;
}
.icon-wrapper {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.empty-icon {
width: 56px;
height: 56px;
opacity: 0.6;
}
}
}
// 文字内容
.text-content {
text-align: center;
margin-bottom: 20px;
.empty-title {
font-size: 18px;
font-weight: 600;
color: var(--van-text-color);
margin: 0 0 8px;
line-height: 1.4;
}
.empty-description {
font-size: 14px;
color: var(--van-text-color-2);
margin: 0;
line-height: 1.6;
}
}
// 操作按钮
.empty-action {
margin-top: 4px;
}
// 主题色
&.theme-blue {
.icon-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.empty-icon {
color: #667eea;
}
}
&.theme-green {
.icon-bg {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.empty-icon {
color: #11998e;
}
}
&.theme-orange {
.icon-bg {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.empty-icon {
color: #f5576c;
}
}
&.theme-purple {
.icon-bg {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.empty-icon {
color: #4facfe;
}
}
&.theme-gray {
.icon-bg {
background: linear-gradient(135deg, #bdc3c7 0%, #2c3e50 100%);
}
.empty-icon {
color: #95a5a6;
}
}
}
// 动画
@keyframes pulse {
0%,
100% {
transform: scale(1);
opacity: 0.1;
}
50% {
transform: scale(1.05);
opacity: 0.15;
}
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
// 尺寸变体
.modern-empty.size-small {
padding: 24px 16px;
.icon-container {
width: 80px;
height: 80px;
margin-bottom: 16px;
.empty-icon {
width: 40px;
height: 40px;
}
}
.text-content {
.empty-title {
font-size: 16px;
}
.empty-description {
font-size: 13px;
}
}
}
.modern-empty.size-large {
padding: 60px 20px;
.icon-container {
width: 160px;
height: 160px;
margin-bottom: 32px;
.empty-icon {
width: 72px;
height: 72px;
}
}
.text-content {
.empty-title {
font-size: 20px;
}
.empty-description {
font-size: 15px;
}
}
}
</style>