Compare commits

...

3 Commits

@ -1 +1 @@
VITE_APP_BASE_URL=https://besure.ngsk.tech:7001
VITE_APP_BASE_URL=

@ -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>

@ -24,11 +24,13 @@
</view>
<scroll-view scroll-y class="content-scroll" :scroll-top="scrollTop" scroll-with-animation @scroll="onScroll">
<view class="content-inner">
<view v-if="filteredModules.length === 0" class="empty-state">
<text class="empty-title">暂无可用菜单</text>
<text class="empty-desc">当前账号未返回 {{ title }} 相关权限菜单</text>
</view>
<view class="content-inner" :class="{ 'safe-bottom': safeBottom }">
<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'
@ -108,6 +111,10 @@ const props = defineProps({
showGoTop: {
type: Boolean,
default: false
},
safeBottom: {
type: Boolean,
default: false
}
})
@ -144,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 = ''
}
@ -200,13 +227,13 @@ function handleClick(menu) {
icon: 'none'
})
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
display: flex;
flex-direction: column;
height: 100%;
background-color: #f0f2f5;
}
@ -277,12 +304,15 @@ function handleClick(menu) {
}
.content-scroll {
height: calc(100vh - 240rpx);
flex: 1;
}
.content-inner {
padding: 24rpx;
padding-bottom: 160rpx;
&.safe-bottom {
padding-bottom: calc(24rpx + 120rpx + env(safe-area-inset-bottom));
}
}
.module-section {
@ -393,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;
@ -427,7 +435,7 @@ function handleClick(menu) {
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 10;
z-index: 50;
}
.go-top-icon {
@ -435,5 +443,4 @@ function handleClick(menu) {
color: #ffffff;
font-weight: bold;
}
</style>
</style>

@ -1,21 +1,7 @@
<template>
<up-tabbar
:value="activeIndex"
:activeColor="activeColor"
:inactiveColor="inactiveColor"
:safeAreaInsetBottom="true"
:fixed="true"
:placeholder="true"
:border="false"
@change="handleChange"
zIndex="100"
>
<up-tabbar-item
v-for="(item, index) in tabList"
:key="index"
:text="item.text"
:name="index"
>
<up-tabbar :value="activeIndex" :activeColor="activeColor" :inactiveColor="inactiveColor" :safeAreaInsetBottom="true"
:fixed="true" :placeholder="true" :border="false" @change="handleChange" zIndex="100">
<up-tabbar-item v-for="(item, index) in tabList" :key="index" :text="item.text" :name="index">
<template #active-icon>
<image :src="item.selectedIcon" class="tabbar-icon" mode="widthFix" />
</template>
@ -124,3 +110,9 @@ watch(() => useUserStore().menus, () => {
height: 48rpx;
}
</style>
<style>
.u-tabbar {
flex: none !important;
}
</style>

@ -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')
@ -59,9 +98,8 @@ function goTop() {
.page-container {
display: flex;
flex-direction: column;
height: 100vh;
min-height: 100vh;
background-color: #f0f2f5;
padding-bottom: 100rpx;
}
.main-scroll {

@ -1,5 +1,5 @@
<template>
<view class="mine-container" :style="{ height: `${windowHeight}px` }">
<view class="mine-container">
<NavBar :title="pageTitle" />
<view class="header-section">
<view class="flex padding justify-between">
@ -98,7 +98,6 @@ const { t } = useI18n()
const pageTitle = computed(() => t('nav.mine'))
const avatar = ref(userStore.avatar);
const windowHeight = ref(uni.getSystemInfoSync().windowHeight - 50);
const popup = ref(null);
uni.$on('refresh', () => {
@ -175,8 +174,7 @@ page {
.mine-container {
width: 100%;
height: 100%;
min-height: 100vh;
.header-section {
padding: 15px 15px 45px 15px;
@ -239,8 +237,7 @@ page {
.mine-container {
width: 100%;
height: 100%;
min-height: 100vh;
.header-section {
padding: 15px 15px 45px 15px;

@ -2,9 +2,11 @@
<view class="page-container">
<NavBar :title="pageTitle" />
<PermissionMenuPage
class="flex-fill"
page-path="pages/report"
title="报表中心"
subtitle="数据驱动决策 · 智能分析"
:safe-bottom="true"
/>
<TabBar />
</view>
@ -27,6 +29,10 @@ const pageTitle = computed(() => t('tab.report'))
flex-direction: column;
height: 100vh;
background-color: #f5f6f7;
padding-bottom: 100rpx;
}
</style>
.flex-fill {
flex: 1;
overflow: visible;
}
</style>

@ -1,13 +1,8 @@
<template>
<view class="page-container">
<NavBar :title="pageTitle" />
<PermissionMenuPage
page-path="pages/work"
title="管理中心"
subtitle="系统配置与管理"
:searchable="true"
:show-go-top="true"
/>
<PermissionMenuPage class="flex-fill" page-path="pages/work" title="管理中心" subtitle="系统配置与管理" :searchable="true"
:show-go-top="true" :safe-bottom="true" />
<TabBar />
</view>
</template>
@ -29,6 +24,10 @@ const pageTitle = computed(() => t('tab.work'))
flex-direction: column;
height: 100vh;
background-color: #f5f6f7;
padding-bottom: 100rpx;
}
</style>
.flex-fill {
flex: 1;
overflow: visible;
}
</style>

Loading…
Cancel
Save