You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_app/src/components/dashboard/NavSection.vue

265 lines
6.8 KiB
Vue

<template>
<view class="nav-section">
<view class="section-header">
<text class="section-title">{{ t('dashboard.functionNav') }}</text>
<view class="more-btn" @click="handleMoreClick">
<text class="more-text">{{ t('common.more') }}</text>
</view>
</view>
<view class="nav-grid">
<view v-for="(item, index) in displayNavList" :key="item.id || index" class="nav-item" @click="handleNavClick(item)">
<view class="nav-icon" :style="{ background: '#e1e6eb' }">
<uni-icons v-if="isUniIcon(item.icon)" :type="getUniIconName(item.icon)" size="28" :color="item.accentColor" />
<u-icon v-else-if="isUviewIcon(item.icon)" :name="getUviewIconName(item.icon)" size="28" :color="item.accentColor"></u-icon>
<text v-else class="nav-icon-text">{{ item.symbol }}</text>
</view>
<text class="nav-text">{{ item.displayName }}</text>
</view>
</view>
<NavMenuMore
:visible="showMoreModal"
:menu-list="moreNavList"
@close="showMoreModal = false"
@edit="handleEdit"
/>
<NavMenuEditor
:visible="showEditModal"
@close="showEditModal = false"
@update="handleUpdate"
/>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { getUserNavMenuList } from '@/api/system/userNavMenu'
import { getNavPermissionInfo } from '@/api/login'
import { buildNavMenuViewModels } from '@/utils/permissionMenu'
import NavMenuMore from './NavMenuMore.vue'
import NavMenuEditor from './NavMenuEditor.vue'
const { t } = useI18n()
const TABBAR_VISIBILITY_EVENT = 'tabbar-visibility-change'
function hexToRgba(hex, alpha) {
const value = String(hex || '').replace('#', '')
if (value.length !== 6) {
return `rgba(45, 90, 135, ${alpha})`
}
const red = parseInt(value.slice(0, 2), 16)
const green = parseInt(value.slice(2, 4), 16)
const blue = parseInt(value.slice(4, 6), 16)
return `rgba(${red}, ${green}, ${blue}, ${alpha})`
}
function isUniIcon(icon) {
return String(icon || '').startsWith('uni-icons:')
}
function isUviewIcon(icon) {
return String(icon || '').startsWith('uview-plus:')
}
function getUniIconName(icon) {
return String(icon || '').replace(/^uni-icons:/, '').trim()
}
function getUviewIconName(icon) {
return String(icon || '').replace(/^uview-plus:/, '').trim()
}
const showMoreModal = ref(false)
const showEditModal = ref(false)
const configuredMenuList = ref([])
const allMenuList = ref([])
const loaded = ref(false)
const defaultNavList = [
{ id: 'default-mold', displayName: '模具', symbol: '🔧', accentColor: '#1a3a5c', route: '/pages_function/pages/mold/index' },
{ id: 'default-equipment', displayName: '设备', symbol: '⚙️', accentColor: '#2d5a87', route: '/pages_function/pages/equipment/index' },
{ id: 'default-keypart', displayName: '配件', symbol: '🔩', accentColor: '#3d7ab5', route: '/pages_function/pages/keypart/index' },
{ id: 'default-spare', displayName: '备件', symbol: '📦', accentColor: '#4a90c2', route: '/pages_function/pages/spare/index' },
{ id: 'default-product', displayName: '产品', symbol: '🧾', accentColor: '#5aa0d2', route: '/pages_function/pages/product/index' }
]
const displayNavList = computed(() => {
if (!loaded.value) {
return []
}
if (configuredMenuList.value.length === 0) {
return defaultNavList
}
return configuredMenuList.value.slice(0, 5)
})
const moreNavList = computed(() => {
if (!loaded.value) {
return []
}
if (configuredMenuList.value.length === 0) {
return defaultNavList
}
return configuredMenuList.value
})
const modalVisible = computed(() => showMoreModal.value || showEditModal.value)
let tabbarRestoreTimer = null
async function loadConfiguredMenus() {
try {
const menuRes = await getNavPermissionInfo()
allMenuList.value = buildNavMenuViewModels(menuRes?.data?.menus)
const navRes = await getUserNavMenuList()
const configuredRecords = Array.isArray(navRes?.data) ? [...navRes.data] : []
configuredRecords.sort((left, right) => Number(left?.sort || 0) - Number(right?.sort || 0))
const configuredIds = configuredRecords.map(item => item.menuId)
if (configuredIds.length === 0) {
configuredMenuList.value = []
loaded.value = true
return
}
const configuredMenus = allMenuList.value
.filter(item => configuredIds.includes(item.id))
.sort((a, b) => {
const indexA = configuredIds.indexOf(a.id)
const indexB = configuredIds.indexOf(b.id)
return indexA - indexB
})
configuredMenuList.value = configuredMenus
loaded.value = true
} catch (error) {
console.error('加载配置菜单失败:', error)
configuredMenuList.value = []
loaded.value = true
}
}
function handleNavClick(item) {
if (item.route) {
uni.navigateTo({ url: item.route })
} else {
uni.showToast({ title: t('common.moduleBuilding'), icon: 'none' })
}
}
function handleMoreClick() {
showMoreModal.value = true
}
function handleEdit() {
showMoreModal.value = false
showEditModal.value = true
}
function handleUpdate() {
loadConfiguredMenus()
}
onMounted(() => {
loadConfiguredMenus()
})
onUnmounted(() => {
if (tabbarRestoreTimer) {
clearTimeout(tabbarRestoreTimer)
tabbarRestoreTimer = null
}
uni.$emit(TABBAR_VISIBILITY_EVENT, true)
})
watch(modalVisible, (visible) => {
if (tabbarRestoreTimer) {
clearTimeout(tabbarRestoreTimer)
tabbarRestoreTimer = null
}
if (visible) {
uni.$emit(TABBAR_VISIBILITY_EVENT, false)
return
}
tabbarRestoreTimer = setTimeout(() => {
uni.$emit(TABBAR_VISIBILITY_EVENT, true)
tabbarRestoreTimer = null
}, 300)
}, { immediate: true })
</script>
<style lang="scss" scoped>
.nav-section {
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
}
.more-btn {
display: flex;
align-items: center;
gap: 6rpx;
padding: 8rpx 16rpx;
border-radius: 16rpx;
background: rgba(34, 72, 110, 0.08);
}
.more-text {
font-size: 26rpx;
color: #999;
}
.nav-grid {
display: flex;
justify-content: space-around;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
&:active {
opacity: 0.7;
}
}
.nav-icon {
width: 96rpx;
height: 96rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
.nav-icon-text {
font-size: 32rpx;
color: #1a3a5c;
font-weight: 700;
}
}
.nav-text {
font-size: 26rpx;
color: #333333;
}
</style>