feat:报表/管理菜单动态渲染

master
黄伟杰 4 days ago
parent d16f397336
commit a97d9223b6

@ -1,7 +1,8 @@
<script>
import { initAllDict } from '@/utils/dict'
import { getToken } from '@/utils/auth'
import { initializeLocale, translateLiteral } from '@/locales'
import useUserStore from '@/store/modules/user'
import { applyNavigationBarTheme } from '@/utils/navigationBar'
let wrapped = false
@ -47,7 +48,9 @@ function wrapUniTextApi() {
if (typeof next.title === 'string') {
next.title = translateLiteral(next.title)
}
return rawSetNavigationBarTitle(next)
const result = rawSetNavigationBarTitle(next)
applyNavigationBarTheme()
return result
}
}
@ -55,13 +58,15 @@ export default {
onLaunch: function () {
initializeLocale()
wrapUniTextApi()
applyNavigationBarTheme()
if (getToken()) {
initAllDict().catch(() => {})
useUserStore().getInfo().catch(() => {})
}
},
onShow: function () {
initializeLocale()
wrapUniTextApi()
applyNavigationBarTheme()
},
onHide: function () {
}

@ -37,7 +37,10 @@ export function register(data) {
export function getInfo() {
return request({
url: '/admin-api/system/auth/get-permission-info',
method: 'get'
method: 'get',
params: {
clientType: 2
}
})
}

@ -0,0 +1,443 @@
<template>
<view class="page-container">
<view class="header-section">
<text class="header-title">{{ title }}</text>
<text class="header-subtitle">{{ subtitle }}</text>
</view>
<view v-if="searchable" class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="menuSearchKeyword"
class="search-input"
type="text"
placeholder="搜索菜单"
placeholder-class="input-placeholder"
/>
<view v-if="menuSearchKeyword" class="clear-btn" @click="clearMenuSearch">
<text class="clear-icon">×</text>
</view>
</view>
</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 v-for="(module, moduleIndex) in filteredModules" :key="module.id || `${pagePath}-${moduleIndex}`" class="module-section">
<view class="module-header">
<view class="module-icon" :style="{ background: getModuleColor(moduleIndex) }">
<text class="icon-text">{{ getMenuSymbol(module.name, moduleIndex) }}</text>
</view>
<text class="module-title">{{ module.name }}</text>
</view>
<view class="submodule-list">
<view v-for="(group, groupIndex) in module.groups" :key="group.id || `${moduleIndex}-${groupIndex}`" class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">{{ group.name }}</text>
</view>
<view class="function-grid">
<view
v-for="(entry, entryIndex) in group.children"
:key="entry.id || `${moduleIndex}-${groupIndex}-${entryIndex}`"
class="function-item"
@click="handleClick(entry)"
>
<view class="function-icon" :style="{ background: `${hexToRgba(getModuleColor(moduleIndex), 0.1)}` }">
<uni-icons
v-if="isUniIcon(entry.icon)"
:type="getUniIconName(entry.icon)"
size="24"
:color="getModuleColor(moduleIndex)"
/>
<u-icon
v-else-if="isUviewIcon(entry.icon)"
:name="getUviewIconName(entry.icon)"
size="24"
:color="getModuleColor(moduleIndex)"
></u-icon>
<text v-else class="icon-inner">{{ getMenuSymbol(entry.name, entryIndex) }}</text>
</view>
<text class="function-name">{{ entry.name }}</text>
</view>
</view>
<view v-if="groupIndex < module.groups.length - 1" class="divider"></view>
</view>
</view>
</view>
</view>
</scroll-view>
<view v-if="showGoTop && showGoTopButton" class="go-top-btn" @click="goTop">
<text class="go-top-icon"></text>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import useUserStore from '@/store/modules/user'
import { applyTabBarLanguage } from '@/locales'
import { buildPageModules, findTabMenuByPage, getMenuSymbol, getModuleColor, resolveMenuUrl } from '@/utils/permissionMenu'
const props = defineProps({
pagePath: {
type: String,
required: true
},
title: {
type: String,
required: true
},
subtitle: {
type: String,
default: ''
},
searchable: {
type: Boolean,
default: false
},
showGoTop: {
type: Boolean,
default: false
}
})
const userStore = useUserStore()
const menuSearchKeyword = ref('')
const scrollTop = ref(0)
const currentScrollTop = ref(0)
const showGoTopButton = ref(false)
const modules = computed(() => {
const tabMenu = findTabMenuByPage(userStore.menus, props.pagePath)
return buildPageModules(tabMenu)
})
const filteredModules = computed(() => {
const keyword = menuSearchKeyword.value.trim().toLowerCase()
if (!keyword) {
return modules.value
}
return modules.value
.map((module) => ({
...module,
groups: module.groups
.map((group) => ({
...group,
children: group.children.filter((entry) => {
const target = `${module.name}|${group.name}|${entry.name}`.toLowerCase()
return target.includes(keyword)
})
}))
.filter((group) => group.children.length > 0 || `${module.name}|${group.name}`.toLowerCase().includes(keyword))
}))
.filter((module) => module.groups.length > 0 || String(module.name || '').toLowerCase().includes(keyword))
})
function clearMenuSearch() {
menuSearchKeyword.value = ''
}
function onScroll(event) {
const top = Number(event?.detail?.scrollTop || 0)
currentScrollTop.value = top
showGoTopButton.value = top > 600
}
function goTop() {
scrollTop.value = currentScrollTop.value + 1
setTimeout(() => {
scrollTop.value = 0
}, 0)
}
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()
}
function handleClick(menu) {
const url = resolveMenuUrl(menu)
if (url) {
uni.navigateTo({ url })
return
}
uni.showToast({
title: `暂未配置${menu?.name || '该菜单'}页面`,
icon: 'none'
})
}
onShow(() => {
applyTabBarLanguage()
})
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx;
padding-top: 80rpx;
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 12rpx;
}
.header-subtitle {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
}
.search-section {
padding: 24rpx;
background-color: #ffffff;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f3f5f8;
border-radius: 999rpx;
padding: 0 28rpx;
height: 88rpx;
}
.search-icon {
color: #a0a7b4;
font-size: 30rpx;
margin-right: 16rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #1f2d3d;
}
.input-placeholder {
color: #a0a7b4;
}
.clear-btn {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #d9dee8;
display: flex;
align-items: center;
justify-content: center;
}
.clear-icon {
color: #ffffff;
font-size: 28rpx;
line-height: 1;
}
.content-scroll {
height: calc(100vh - 240rpx);
}
.content-inner {
padding: 24rpx;
padding-bottom: 160rpx;
}
.module-section {
background: #ffffff;
border-radius: 28rpx;
margin-bottom: 24rpx;
overflow: hidden;
box-shadow: 0 10rpx 30rpx rgba(26, 58, 92, 0.06);
}
.module-header {
display: flex;
align-items: center;
padding: 32rpx;
border-bottom: 1rpx solid #eef1f5;
}
.module-icon {
width: 68rpx;
height: 68rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 18rpx;
}
.icon-text {
color: #ffffff;
font-size: 30rpx;
font-weight: 600;
}
.module-title {
font-size: 36rpx;
font-weight: 700;
color: #1a3a5c;
}
.submodule-list {
padding: 0 24rpx 24rpx;
}
.submodule-group {
padding-top: 24rpx;
}
.submodule-header {
padding: 0 8rpx 18rpx;
}
.submodule-name {
display: inline-block;
padding-left: 16rpx;
border-left: 6rpx solid #1a3a5c;
font-size: 30rpx;
font-weight: 600;
color: #435466;
}
.function-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 14rpx;
}
.function-item {
background: #f7f9fc;
border-radius: 18rpx;
padding: 22rpx 10rpx 18rpx;
display: flex;
flex-direction: column;
align-items: center;
min-height: 148rpx;
&:active {
transform: scale(0.98);
}
}
.function-icon {
width: 72rpx;
height: 72rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 14rpx;
}
.icon-inner {
font-size: 28rpx;
color: #1a3a5c;
font-weight: 700;
}
.function-name {
font-size: 24rpx;
color: #233242;
text-align: center;
line-height: 1.35;
word-break: break-all;
}
.divider {
height: 1rpx;
background: #eef1f5;
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;
right: 30rpx;
width: 88rpx;
height: 88rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 10;
}
.go-top-icon {
font-size: 44rpx;
color: #ffffff;
font-weight: bold;
}
</style>

@ -1,6 +1,8 @@
import { createI18n } from 'vue-i18n'
import zhCN from './zh-CN'
import enUS from './en-US'
import useUserStore from '@/store/modules/user'
import { syncTabBarMenus } from '@/utils/permissionMenu'
const LOCALE_STORAGE_KEY = 'app_locale'
const LOCALE_CHANGE_EVENT = 'app-locale-changed'
@ -83,7 +85,7 @@ const literalMap = {
'点检记录详情': 'moldWorkOrder.detailTitle'
}
function applyTabBarLanguage() {
export function applyTabBarLanguage() {
try {
const pages = getCurrentPages()
if (!pages || pages.length === 0) return
@ -93,17 +95,11 @@ function applyTabBarLanguage() {
const tabBarPages = ['pages/index', 'pages/report', 'pages/work', 'pages/mine']
if (!tabBarPages.includes(route)) return
const labels = [
i18n.global.t('tab.home'),
i18n.global.t('tab.report'),
i18n.global.t('tab.work'),
i18n.global.t('tab.mine')
]
labels.forEach((text, index) => {
uni.setTabBarItem({
index,
text
})
syncTabBarMenus(useUserStore().menus, {
homeText: i18n.global.t('tab.home'),
reportFallback: i18n.global.t('tab.report'),
workFallback: i18n.global.t('tab.work'),
mineText: i18n.global.t('tab.mine')
})
} catch (e) {
}

@ -11,6 +11,7 @@ import { translateLiteral } from '@/locales'
import { useDict } from '@/utils/dict'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
import { applyNavigationBarTheme } from '@/utils/navigationBar'
@ -32,6 +33,12 @@ export function createApp() {
app.config.globalProperties.selectDictLabels = selectDictLabels
app.config.globalProperties.$tl = translateLiteral
app.mixin({
onShow() {
applyNavigationBarTheme()
}
})
return {
app
}

@ -16,46 +16,60 @@
"path": "pages/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": false
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#22486e",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/login",
"style": {
"navigationBarTitleText": "登录"
"navigationBarTitleText": "登录",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/work",
"style": {
"navigationBarTitleText": "管理",
"enablePullDownRefresh": false
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#22486e",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/mine",
"style": {
"navigationBarTitleText": "个人中心"
"navigationBarTitleText": "个人中心",
"navigationBarBackgroundColor": "#22486e",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/webview/index",
"style": {
"navigationBarTitleText": "浏览网页"
"navigationBarTitleText": "浏览网页",
"navigationBarBackgroundColor": "#22486e",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/common/textview/index",
"style": {
"navigationBarTitleText": "浏览文本"
"navigationBarTitleText": "浏览文本",
"navigationBarBackgroundColor": "#22486e",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/report",
"style": {
"navigationBarTitleText": "报表",
"enablePullDownRefresh": false
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#22486e",
"navigationBarTextStyle": "white"
}
}
],
@ -639,6 +653,6 @@
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "BESURE",
"navigationBarBackgroundColor": "#1a3a5c"
"navigationBarBackgroundColor": "#22486e"
}
}

@ -1,357 +1,12 @@
<template>
<view class="page-container">
<view class="header-section">
<text class="header-title">报表中心</text>
<text class="header-subtitle">数据驱动决策 · 智能分析</text>
</view>
<scroll-view scroll-y class="content-scroll">
<!-- 生产报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #1a3a5c;">
<text class="icon-text">🏭</text>
</view>
<text class="module-title">生产报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('生产日报')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">生产日报</text>
</view>
<view class="function-item" @click="handleClick('生产周报')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">生产周报</text>
</view>
<view class="function-item" @click="handleClick('生产月报')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📅</text>
</view>
<text class="function-name">生产月报</text>
</view>
<view class="function-item" @click="handleClick('产能分析')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">产能分析</text>
</view>
</view>
</view>
<!-- 仓储报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #2d5a87;">
<text class="icon-text">📦</text>
</view>
<text class="module-title">仓储报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('库存盘点')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">库存盘点</text>
</view>
<view class="function-item" @click="handleClick('出入库统计')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🚚</text>
</view>
<text class="function-name">出入库统计</text>
</view>
<view class="function-item" @click="handleClick('库存周转')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🔄</text>
</view>
<text class="function-name">库存周转</text>
</view>
</view>
</view>
<!-- 质量报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #3d7ab5;">
<text class="icon-text"></text>
</view>
<text class="module-title">质量报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('合格率统计')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">合格率统计</text>
</view>
<view class="function-item" @click="handleClick('不良品分析')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">不良品分析</text>
</view>
<view class="function-item" @click="handleClick('质量趋势')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">质量趋势</text>
</view>
<view class="function-item" @click="handleClick('质检报告')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">质检报告</text>
</view>
</view>
</view>
<!-- 数据采集 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #4a90c2;">
<text class="icon-text">📡</text>
</view>
<text class="module-title">数据采集</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('实时数据')">
<view class="function-icon" style="background: rgba(74, 144, 194, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">实时数据</text>
</view>
<view class="function-item" @click="handleClick('历史数据')">
<view class="function-icon" style="background: rgba(74, 144, 194, 0.1);">
<text class="icon-inner">📚</text>
</view>
<text class="function-name">历史数据</text>
</view>
<view class="function-item" @click="handleClick('数据导出')">
<view class="function-icon" style="background: rgba(74, 144, 194, 0.1);">
<text class="icon-inner">📤</text>
</view>
<text class="function-name">数据导出</text>
</view>
</view>
</view>
<!-- 能耗报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #ff8c00;">
<text class="icon-text"></text>
</view>
<text class="module-title">能耗报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('用电统计')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">🔌</text>
</view>
<text class="function-name">用电统计</text>
</view>
<view class="function-item" @click="handleClick('能耗分析')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">能耗分析</text>
</view>
<view class="function-item" @click="handleClick('节能报告')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">🌱</text>
</view>
<text class="function-name">节能报告</text>
</view>
</view>
</view>
<!-- 设备报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #18bc37;">
<text class="icon-text">🔧</text>
</view>
<text class="module-title">设备报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('设备效率')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">设备效率</text>
</view>
<view class="function-item" @click="handleClick('故障统计')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">🔩</text>
</view>
<text class="function-name">故障统计</text>
</view>
<view class="function-item" @click="handleClick('维护记录')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">🔨</text>
</view>
<text class="function-name">维护记录</text>
</view>
<view class="function-item" @click="handleClick('开机率')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">开机率</text>
</view>
</view>
</view>
<!-- 模具报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #5aa0d2;">
<text class="icon-text">🔩</text>
</view>
<text class="module-title">模具报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('模具寿命')">
<view class="function-icon" style="background: rgba(90, 160, 210, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">模具寿命</text>
</view>
<view class="function-item" @click="handleClick('使用统计')">
<view class="function-icon" style="background: rgba(90, 160, 210, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">使用统计</text>
</view>
<view class="function-item" @click="handleClick('保养记录')">
<view class="function-icon" style="background: rgba(90, 160, 210, 0.1);">
<text class="icon-inner">🔧</text>
</view>
<text class="function-name">保养记录</text>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 原固定报表菜单已移除改为根据权限接口返回的 menus 动态渲染 -->
<PermissionMenuPage
page-path="pages/report"
title="报表中心"
subtitle="数据驱动决策 · 智能分析"
/>
</template>
<script setup>
function handleClick(name) {
uni.showToast({
title: `查看${name}`,
icon: 'none'
});
}
import PermissionMenuPage from '@/components/common/PermissionMenuPage.vue'
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx;
padding-top: 80rpx;
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 12rpx;
}
.header-subtitle {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.content-scroll {
padding: 24rpx;
}
.module-section {
background: #ffffff;
border-radius: 20rpx;
margin-bottom: 24rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.module-header {
display: flex;
align-items: center;
padding: 28rpx 24rpx;
background: #fafbfc;
border-bottom: 1rpx solid #e8eaed;
.module-icon {
width: 72rpx;
height: 72rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
.icon-text {
font-size: 36rpx;
}
}
.module-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
}
}
.function-grid {
display: flex;
flex-wrap: wrap;
padding: 24rpx;
gap: 20rpx;
}
.function-item {
width: calc(25% - 15rpx);
background: #f8fafc;
border-radius: 16rpx;
padding: 28rpx 12rpx;
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.3s ease;
&:active {
background: #e8f4ff;
transform: scale(0.98);
}
.function-icon {
width: 80rpx;
height: 80rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
.icon-inner {
font-size: 40rpx;
}
}
.function-name {
font-size: 24rpx;
color: #333333;
text-align: center;
}
}
</style>

@ -1,619 +1,14 @@
<template>
<view class="page-container">
<view class="header-section">
<text class="header-title">管理中心</text>
<text class="header-subtitle">系统配置与管理</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input v-model="menuSearchKeyword" class="search-input" type="text" placeholder="搜索菜单"
placeholder-class="input-placeholder" @input="handleMenuSearch" @confirm="handleMenuSearch" />
<view v-if="menuSearchKeyword" class="clear-btn" @click="clearMenuSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll" :scroll-top="scrollTop" scrollIntoView="top">
<view id="top" class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #1a3a5c;">
<text class="icon-text"></text>
</view>
<text class="module-title">生产管控</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">生产计划</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('生产任务')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">生产任务</text>
</view>
<view class="function-item" @click="handleClick('生产计划')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">生产计划</text>
</view>
<view class="function-item" @click="handleClick('生产投料')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🏭</text>
</view>
<text class="function-name">生产投料</text>
</view>
</view>
</view>
<view class="divider"></view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">仓储管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('仓库信息')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🏢</text>
</view>
<text class="function-name">仓库信息</text>
</view>
<view class="function-item" @click="handleClick('出入库')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🚚</text>
</view>
<text class="function-name">出入库</text>
</view>
<view class="function-item" @click="handleClick('库存报表')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">库存报表</text>
</view>
</view>
</view>
<view class="divider"></view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">质量管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('检验类型')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">检验类型</text>
</view>
<view class="function-item" @click="handleClick('检验项库')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📚</text>
</view>
<text class="function-name">检验项库</text>
</view>
<view class="function-item" @click="handleClick('检验模板')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📄</text>
</view>
<text class="function-name">检验模板</text>
</view>
<view class="function-item" @click="handleClick('检验记录')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">检验记录</text>
</view>
</view>
</view>
</view>
</view>
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #ff8c00;">
<text class="icon-text">🔧</text>
</view>
<text class="module-title">设备运维</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">设备管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('设备分类')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">设备分类</text>
</view>
<view class="function-item" @click="handleClick('设备台账')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">设备台账</text>
</view>
<view class="function-item" @click="handleClick('设备关键件')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🔩</text>
</view>
<text class="function-name">设备关键件</text>
</view>
</view>
</view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">点检保养</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('点检项库')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">点检项库</text>
</view>
<view class="function-item" @click="handleClick('点检模板')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📄</text>
</view>
<text class="function-name">点检模板</text>
</view>
<view class="function-item" @click="handleClick('点检任务')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">点检任务</text>
</view>
<view class="function-item" @click="handleClick('点检记录')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📜</text>
</view>
<text class="function-name">点检记录</text>
</view>
</view>
</view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">维修管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('维修项目')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🔧</text>
</view>
<text class="function-name">维修项目</text>
</view>
<view class="function-item" @click="handleClick('维修单')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">维修单</text>
</view>
</view>
</view>
</view>
</view>
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #9c27b0;">
<text class="icon-text">🧱</text>
</view>
<text class="module-title">模具管理</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">模具管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('模具类型')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="list" size="22" color="#9c27b0"></uni-icons>
</view>
<text class="function-name">模具类型</text>
</view>
<view class="function-item" @click="handleClick('模具台账')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="bars" size="22" color="#9c27b0"></uni-icons>
</view>
<text class="function-name">模具台账</text>
</view>
<view class="function-item" @click="handleClick('moldGet')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="redo" size="22" color="#9c27b0"></uni-icons>
</view>
<text class="function-name">{{ t('moldGet.moduleName') }}</text>
</view>
<view class="function-item" @click="handleClick('moldReturn')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="undo" size="22" color="#9c27b0"></uni-icons>
</view>
<text class="function-name">{{ t('moldReturn.moduleName') }}</text>
</view>
<view class="function-item" @click="handleClick('moldOperate')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="loop" size="22" color="#9c27b0"></uni-icons>
</view>
<text class="function-name">{{ t('moldOperate.moduleName') }}</text>
</view>
</view>
</view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">点检保养</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('moldInspectionItems')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">{{ t('moldInspectionItems.moduleName') }}</text>
</view>
<view class="function-item" @click="handleClick('点检模板')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="paperclip" size="22" color="#00bcd4"></uni-icons>
</view>
<text class="function-name">{{ t('moldInspectionPlan.moduleName') }}</text>
</view>
<view class="function-item" @click="handleClick('点检任务')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="checkbox" size="22" color="#00bcd4"></uni-icons>
</view>
<text class="function-name">{{ t('moldTaskConfig.moduleName') }}</text>
</view>
<view class="function-item" @click="handleClick('点检记录')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<uni-icons type="list" size="22" color="#00bcd4"></uni-icons>
</view>
<text class="function-name">{{ t('moldWorkOrder.moduleName') }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #18bc37;">
<text class="icon-text">📦</text>
</view>
<text class="module-title">基础数据</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">产品管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('产品物料分类')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🏷</text>
</view>
<text class="function-name">产品物料分类</text>
</view>
<view class="function-item" @click="handleClick('产品物料信息')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">产品物料信息</text>
</view>
<view class="function-item" @click="handleClick('产品BOM')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🌳</text>
</view>
<text class="function-name">产品BOM</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="go-top-btn" @click="goTop">
<text class="go-top-icon"></text>
</view>
</view>
<!-- 原固定管理菜单已移除改为根据权限接口返回的 menus 动态渲染 -->
<PermissionMenuPage
page-path="pages/work"
title="管理中心"
subtitle="系统配置与管理"
:searchable="true"
:show-go-top="true"
/>
</template>
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const scrollTop = ref(0);
const menuSearchKeyword = ref('');
function goTop() {
uni.pageScrollTo({
scrollTop: 0,
duration: 300
})
}
function handleMenuSearch() {
if (menuSearchKeyword.value.trim() === '') {
return;
}
uni.showToast({
title: `搜索: ${menuSearchKeyword.value}`,
icon: 'none'
});
}
function clearMenuSearch() {
menuSearchKeyword.value = '';
}
function handleClick(name) {
const routeMap = {
'仓库信息': '/pages_function/pages/warehouse/index',
'检验类型': '/pages_function/pages/inspection/index',
'检验项库': '/pages_function/pages/inspectionItem/index',
'检验模板': '/pages_function/pages/inspectionTemplate/index',
'产品物料分类': '/pages_function/pages/materialCategory/index',
'产品物料信息': '/pages_function/pages/materialInfo/index',
'产品BOM': '/pages_function/pages/productBom/index',
'设备分类': '/pages_function/pages/equipmentCategory/index',
'设备台账': '/pages_function/pages/equipmentLedger/index',
'设备关键件': '/pages_function/pages/criticalComponent/index',
'模具类型': '/pages_function/pages/moldType/index',
'模具台账': '/pages_function/pages/moldLedger/index',
moldGet: '/pages_function/pages/moldget/index',
moldReturn: '/pages_function/pages/moldreturn/index',
moldOperate: '/pages_function/pages/moldoperate/index',
moldInspectionItems: '/pages_function/pages/moldInspectionItems/index',
'点检项库': '',
'点检模板': '/pages_function/pages/moldInspectionPlan/index',
'点检任务': '/pages_function/pages/moldTaskConfiguration/index',
'点检记录': '/pages_function/pages/moldWorkOrderInquiry/index',
'维修项目': '',
'维修单': ''
};
const url = routeMap[name];
if (url) {
uni.navigateTo({ url });
} else {
uni.showToast({
title: `进入${name}`,
icon: 'none'
});
}
}
import PermissionMenuPage from '@/components/common/PermissionMenuPage.vue'
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx;
padding-top: 80rpx;
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 12rpx;
}
.header-subtitle {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.search-section {
background: #ffffff;
padding: 20rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
padding: 24rpx;
}
.module-section {
background: #ffffff;
border-radius: 20rpx;
margin-bottom: 24rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.module-header {
display: flex;
align-items: center;
padding: 28rpx 24rpx;
background: #fafbfc;
border-bottom: 1rpx solid #e8eaed;
.module-icon {
width: 72rpx;
height: 72rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
.icon-text {
font-size: 36rpx;
}
}
.module-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
}
}
.submodule-list {
padding: 20rpx;
}
.submodule-group {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
.submodule-header {
margin-bottom: 16rpx;
.submodule-name {
font-size: 28rpx;
font-weight: 500;
color: #666666;
padding-left: 16rpx;
border-left: 6rpx solid #1a3a5c;
}
}
.divider {
height: 2rpx;
background: #e8eaed;
margin: 24rpx 0;
}
.function-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.function-item {
width: calc(25% - 15rpx);
background: #f8fafc;
border-radius: 16rpx;
padding: 28rpx 12rpx;
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.3s ease;
&:active {
background: #e8f4ff;
transform: scale(0.98);
}
.function-icon {
width: 80rpx;
height: 80rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
.icon-inner {
font-size: 40rpx;
}
}
.function-name {
font-size: 24rpx;
color: #333333;
text-align: center;
}
}
.go-top-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 88rpx;
height: 88rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.go-top-icon {
font-size: 44rpx;
color: #ffffff;
font-weight: bold;
}
}
/* 自定义 TabBar */
</style>

@ -3,6 +3,7 @@ import { getToken, setToken, removeToken } from "@/utils/auth";
import defAva from "@/static/images/profile.jpg";
import { defineStore } from "pinia";
import { initAllDict } from "@/utils/dict";
import { syncTabBarMenus } from "@/utils/permissionMenu";
export interface LoginForm {
username: string;
@ -56,13 +57,15 @@ const useUserStore = defineStore("user", {
// const avatar =
// user.avatar == "" || user.avatar == null ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
const avatar = user.avatar == "" || user.avatar == null ? defAva : user.avatar;
if (res.roles && res.roles.length > 0) {
if (res.data.roles && res.data.roles.length > 0) {
// 验证返回的roles是否是一个非空数组
this.roles = res.data.roles;
} else {
this.roles = ["ROLE_DEFAULT"];
}
this.permissions = res.data.permissions;
this.menus = Array.isArray(res.data.menus) ? res.data.menus : [];
syncTabBarMenus(this.menus);
this.name = user.nickname;
this.userId = user.id;
this.avatar = avatar;
@ -82,6 +85,7 @@ const useUserStore = defineStore("user", {
this.token = "";
this.roles = [];
this.permissions = [];
this.menus = [];
this.name = "";
this.avatar = "";
removeToken();

@ -0,0 +1,48 @@
const DEFAULT_NAV_BACKGROUND = '#22486e'
const DEFAULT_NAV_FRONT = '#ffffff'
const LOGIN_NAV_BACKGROUND = '#ffffff'
const LOGIN_NAV_FRONT = '#000000'
function getCurrentRoute() {
try {
const pages = getCurrentPages()
if (!pages || pages.length === 0) return ''
return pages[pages.length - 1]?.route || ''
} catch (error) {
return ''
}
}
function getNavigationTheme(route = '') {
const currentRoute = route || getCurrentRoute()
if (currentRoute === 'pages/login') {
return {
frontColor: LOGIN_NAV_FRONT,
backgroundColor: LOGIN_NAV_BACKGROUND
}
}
return {
frontColor: DEFAULT_NAV_FRONT,
backgroundColor: DEFAULT_NAV_BACKGROUND
}
}
export function applyNavigationBarTheme(route = '') {
const theme = getNavigationTheme(route)
try {
uni.setNavigationBarColor({
frontColor: theme.frontColor,
backgroundColor: theme.backgroundColor,
animation: {
duration: 0,
timingFunc: 'linear'
}
})
} catch (error) {
}
}
export function getDefaultNavigationBackground() {
return DEFAULT_NAV_BACKGROUND
}

@ -0,0 +1,183 @@
const DIRECT_ROUTE_PREFIXES = ['pages/', 'page_', 'pages_']
const MENU_ROUTE_MAP = {
'生产任务': '/pages_function/pages/taskList/index',
tasklist: '/pages_function/pages/taskList/index',
'生产计划': '/pages_function/pages/planList/index',
planlist: '/pages_function/pages/planList/index',
'生产投料': '/page_record/feedingRecordForm',
feedingrecordform: '/page_record/feedingRecordForm',
'生产报工': '/page_report/reportForm',
reportform: '/page_report/reportForm',
'代报工': '/page_report/replaceForm',
replaceform: '/page_report/replaceForm',
'计划进度': '/page_report/planProgress',
planprogress: '/page_report/planProgress',
'仓库信息': '/pages_function/pages/warehouse/index',
warehouse: '/pages_function/pages/warehouse/index',
'检验类型': '/pages_function/pages/inspection/index',
inspection: '/pages_function/pages/inspection/index',
'检验项库': '/pages_function/pages/inspectionItem/index',
inspectionitem: '/pages_function/pages/inspectionItem/index',
moldinspectionitems: '/pages_function/pages/moldInspectionItems/index',
'检验模板': '/pages_function/pages/inspectionTemplate/index',
inspectiontemplate: '/pages_function/pages/inspectionTemplate/index',
moldinspectionplan: '/pages_function/pages/moldInspectionPlan/index',
'点检模板': '/pages_function/pages/moldInspectionPlan/index',
'点检任务': '/pages_function/pages/moldTaskConfiguration/index',
moldtaskconfiguration: '/pages_function/pages/moldTaskConfiguration/index',
moldtaskconfig: '/pages_function/pages/moldTaskConfiguration/index',
'点检记录': '/pages_function/pages/moldWorkOrderInquiry/index',
moldworkorderinquiry: '/pages_function/pages/moldWorkOrderInquiry/index',
'产品物料分类': '/pages_function/pages/materialCategory/index',
materialcategory: '/pages_function/pages/materialCategory/index',
'产品物料信息': '/pages_function/pages/materialInfo/index',
materialinfo: '/pages_function/pages/materialInfo/index',
'产品BOM': '/pages_function/pages/productBom/index',
productbom: '/pages_function/pages/productBom/index',
'设备分类': '/pages_function/pages/equipmentCategory/index',
equipmentcategory: '/pages_function/pages/equipmentCategory/index',
'设备台账': '/pages_function/pages/equipmentLedger/index',
equipmentledger: '/pages_function/pages/equipmentLedger/index',
'设备关键件': '/pages_function/pages/criticalComponent/index',
criticalcomponent: '/pages_function/pages/criticalComponent/index',
equipmentkeypart: '/pages_function/pages/equipmentKeypart/index',
'模具类型': '/pages_function/pages/moldType/index',
moldtype: '/pages_function/pages/moldType/index',
'模具台账': '/pages_function/pages/moldLedger/index',
moldledger: '/pages_function/pages/moldLedger/index',
moldget: '/pages_function/pages/moldget/index',
moldreturn: '/pages_function/pages/moldreturn/index',
moldoperate: '/pages_function/pages/moldoperate/index',
mold: '/pages_function/pages/mold/index',
equipment: '/pages_function/pages/equipment/index',
spare: '/pages_function/pages/spare/index',
keypart: '/pages_function/pages/keypart/index',
product: '/pages_function/pages/product/index'
}
const HOME_KEYWORDS = ['首页', 'home', 'index', 'dashboard']
const MODULE_COLORS = ['#1a3a5c', '#2d5a87', '#3d7ab5', '#ff8c00', '#18bc37', '#5aa0d2']
const MENU_SYMBOLS = ['●', '■', '▲', '◆', '★', '▣']
function toArray(value) {
return Array.isArray(value) ? value : []
}
function normalizeMenuKey(value) {
return String(value || '')
.trim()
.toLowerCase()
.replace(/\/index$/i, '')
.replace(/[^a-z0-9\u4e00-\u9fa5]/g, '')
}
function getDirectRoute(value) {
const route = String(value || '').trim().replace(/^\/+/, '')
if (!route) return ''
return DIRECT_ROUTE_PREFIXES.some((prefix) => route.startsWith(prefix)) ? `/${route}` : ''
}
function looksLikeHomeMenu(menu) {
const fields = [menu.name, menu.enName, menu.path, menu.component]
.map((item) => String(item || '').toLowerCase())
.join('|')
return HOME_KEYWORDS.some((keyword) => fields.includes(keyword))
}
function wrapGroupEntries(group) {
const directChildren = toArray(group.children)
const hasNestedEntries = directChildren.some((child) => toArray(child.children).length > 0)
if (hasNestedEntries) {
return directChildren
.map((child) => ({
...child,
children: toArray(child.children)
}))
.filter((child) => child.children.length > 0)
}
if (directChildren.length === 0) {
return []
}
return [
{
id: `${group.id || group.name || 'group'}-entries`,
name: group.name,
children: directChildren
}
]
}
export function getTopLevelMenus(menus) {
return toArray(menus).filter((menu) => menu && typeof menu === 'object')
}
export function getDynamicTabMenus(menus) {
return getTopLevelMenus(menus).filter((menu) => !looksLikeHomeMenu(menu))
}
export function findTabMenuByPage(menus, pagePath) {
const dynamicMenus = getDynamicTabMenus(menus)
if (pagePath === 'pages/report') {
return dynamicMenus[0] || null
}
if (pagePath === 'pages/work') {
return dynamicMenus[1] || dynamicMenus[0] || null
}
return null
}
export function buildPageModules(tabMenu) {
return toArray(tabMenu?.children)
.map((module) => ({
...module,
groups: wrapGroupEntries(module)
}))
.filter((module) => module.groups.length > 0)
}
export function resolveMenuUrl(menu) {
const directRoute = getDirectRoute(menu?.path) || getDirectRoute(menu?.component)
if (directRoute) {
return directRoute
}
const keys = [menu?.component, menu?.path, menu?.enName, menu?.name]
for (const key of keys) {
const normalizedKey = normalizeMenuKey(key)
if (normalizedKey && MENU_ROUTE_MAP[normalizedKey]) {
return MENU_ROUTE_MAP[normalizedKey]
}
}
return ''
}
export function getModuleColor(index) {
return MODULE_COLORS[index % MODULE_COLORS.length]
}
export function getMenuSymbol(name, index) {
const trimmed = String(name || '').trim()
if (trimmed) {
return trimmed.slice(0, 1)
}
return MENU_SYMBOLS[index % MENU_SYMBOLS.length]
}
export function syncTabBarMenus(menus, options = {}) {
const dynamicMenus = getDynamicTabMenus(menus)
const labels = [
options.homeText || '首页',
dynamicMenus[0]?.name || options.reportFallback || '报表',
dynamicMenus[1]?.name || dynamicMenus[0]?.name || options.workFallback || '管理',
options.mineText || '我的'
]
labels.forEach((text, index) => {
uni.setTabBarItem({
index,
text
})
})
}
Loading…
Cancel
Save