feat:添加empty组件

master
黄伟杰 2 days ago
parent 55796a6a41
commit c2151b7c2f

@ -0,0 +1,97 @@
<template>
<view class="app-empty-state">
<view class="empty-icon-wrap">
<view class="empty-icon-bg empty-icon-bg-left"></view>
<view class="empty-icon-bg empty-icon-bg-right"></view>
<view class="empty-icon-core">
<uni-icons :type="icon" size="38" color="#2d5a87" />
</view>
</view>
<text class="empty-title">{{ title }}</text>
<text v-if="desc" class="empty-desc">{{ desc }}</text>
</view>
</template>
<script setup>
defineProps({
icon: {
type: String,
default: 'locked-filled'
},
title: {
type: String,
required: true
},
desc: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.app-empty-state {
background: #ffffff;
border-radius: 28rpx;
padding: 72rpx 40rpx;
text-align: center;
box-shadow: 0 12rpx 36rpx rgba(26, 58, 92, 0.08);
}
.empty-icon-wrap {
position: relative;
width: 176rpx;
height: 176rpx;
margin: 0 auto 28rpx;
}
.empty-icon-bg {
position: absolute;
top: 50%;
width: 78rpx;
height: 78rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, rgba(45, 90, 135, 0.08) 0%, rgba(45, 90, 135, 0.18) 100%);
}
.empty-icon-bg-left {
left: 10rpx;
transform: translateY(-50%) rotate(-18deg);
}
.empty-icon-bg-right {
right: 10rpx;
transform: translateY(-50%) rotate(18deg);
}
.empty-icon-core {
position: absolute;
top: 50%;
left: 50%;
width: 132rpx;
height: 132rpx;
border-radius: 36rpx;
background: linear-gradient(180deg, #f7fbff 0%, #eaf3fb 100%);
border: 2rpx solid rgba(45, 90, 135, 0.1);
display: flex;
align-items: center;
justify-content: center;
transform: translate(-50%, -50%);
box-shadow: inset 0 2rpx 0 rgba(255, 255, 255, 0.8);
}
.empty-title {
display: block;
font-size: 36rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 16rpx;
}
.empty-desc {
display: block;
font-size: 28rpx;
color: #7a8795;
line-height: 1.5;
}
</style>

@ -25,10 +25,12 @@
<scroll-view scroll-y class="content-scroll" :scroll-top="scrollTop" scroll-with-animation @scroll="onScroll">
<view class="content-inner" :class="{ 'safe-bottom': safeBottom }">
<view v-if="filteredModules.length === 0" class="empty-state">
<text class="empty-title">暂无可用菜单</text>
<text class="empty-desc">当前账号未返回 {{ title }} 相关权限菜单</text>
</view>
<AppEmptyState
v-if="filteredModules.length === 0"
:icon="emptyStateIcon"
:title="emptyStateTitle"
:desc="emptyStateDesc"
/>
<view v-for="(module, moduleIndex) in filteredModules" :key="module.id || `${pagePath}-${moduleIndex}`" class="module-section">
<view class="module-header">
@ -85,6 +87,7 @@
<script setup>
import { computed, ref } from 'vue'
import AppEmptyState from '@/components/common/AppEmptyState.vue'
import useUserStore from '@/store/modules/user'
import { buildPageModules, findTabMenuByPage, getMenuSymbol, getModuleColor, resolveMenuUrl } from '@/utils/permissionMenu'
@ -148,6 +151,26 @@ const filteredModules = computed(() => {
.filter((module) => module.groups.length > 0 || String(module.name || '').toLowerCase().includes(keyword))
})
const hasMenuPermission = computed(() => modules.value.length > 0)
const isSearchEmpty = computed(() => {
return Boolean(menuSearchKeyword.value.trim()) && hasMenuPermission.value && filteredModules.value.length === 0
})
const emptyStateTitle = computed(() => {
return isSearchEmpty.value ? '未找到匹配菜单' : '请先配置菜单权限'
})
const emptyStateDesc = computed(() => {
return isSearchEmpty.value
? `未搜索到与“${menuSearchKeyword.value.trim()}”相关的菜单项,请调整关键词后重试`
: `当前账号还未配置${props.title}相关菜单权限,请联系管理员完成配置`
})
const emptyStateIcon = computed(() => {
return isSearchEmpty.value ? 'search' : 'locked-filled'
})
function clearMenuSearch() {
menuSearchKeyword.value = ''
}
@ -400,28 +423,6 @@ function handleClick(menu) {
margin: 24rpx 0 0;
}
.empty-state {
background: #ffffff;
border-radius: 28rpx;
padding: 80rpx 32rpx;
text-align: center;
box-shadow: 0 10rpx 30rpx rgba(26, 58, 92, 0.06);
}
.empty-title {
display: block;
font-size: 34rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 16rpx;
}
.empty-desc {
display: block;
font-size: 28rpx;
color: #7a8795;
}
.go-top-btn {
position: fixed;
bottom: 140rpx;

@ -5,8 +5,13 @@
<BannerSection />
<view class="content-section">
<NavSection />
<StatsSection v-if="currentMode === 'production'" @mode-change="onModeChange" />
<QualitySection v-else @mode-change="onModeChange" />
<AppEmptyState
v-if="showEmptyState"
title="请先配置菜单权限"
desc="当前账号还未配置首页菜单权限,请联系管理员完成配置"
/>
<StatsSection v-if="showStats" @mode-change="onModeChange" />
<QualitySection v-if="showQuality" @mode-change="onModeChange" />
</view>
</scroll-view>
@ -21,9 +26,12 @@
<script setup>
import { onMounted, onUnmounted, ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
import { onLocaleChange, offLocaleChange, setNavigationTitle } from '@/locales'
import useUserStore from '@/store/modules/user'
import NavBar from '@/components/common/NavBar.vue'
import TabBar from '@/components/common/TabBar.vue'
import AppEmptyState from '@/components/common/AppEmptyState.vue'
import BannerSection from '@/components/dashboard/BannerSection.vue'
import NavSection from '@/components/dashboard/NavSection.vue'
import StatsSection from '@/components/dashboard/StatsSection.vue'
@ -32,6 +40,37 @@ import QualitySection from '@/components/dashboard/QualitySection.vue'
const { t } = useI18n()
const pageTitle = computed(() => t('nav.home'))
const userStore = useUserStore()
const { menus } = storeToRefs(userStore)
const homeMenu = computed(() => {
return (menus.value || []).find((m) => m.name === '首页')
})
const hasProduction = computed(() => {
const children = homeMenu.value?.children || []
return children.some((c) => c.name === '生产')
})
const hasQuality = computed(() => {
const children = homeMenu.value?.children || []
return children.some((c) => c.name === '质量')
})
const showStats = computed(() => {
if (!hasProduction.value) return false
if (!hasQuality.value) return true
return currentMode.value === 'production'
})
const showQuality = computed(() => {
if (!hasQuality.value) return false
if (!hasProduction.value) return true
return currentMode.value === 'quality'
})
const showEmptyState = computed(() => !hasProduction.value && !hasQuality.value)
const scrollTop = ref(0)
const currentMode = ref('production')

Loading…
Cancel
Save