346 lines
7.6 KiB
Vue
346 lines
7.6 KiB
Vue
|
|
<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>
|