Files
yunzerwebsiteallinone/uniapp/pages/nav/nav.vue
T

335 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<!-- 顶部信息:融入页面背景,不再单独做彩色抬头 -->
<view class="head" :style="headPad">
<view class="head-row">
<text class="head-title">选择子系统</text>
<text class="head-count">{{ enabledCount }}/{{ cards.length }} 已开通</text>
</view>
<text class="head-sub">{{ user.tenant || '云泽平台' }}<text v-if="user.account"> · {{ user.account }}</text></text>
</view>
<view class="body">
<!-- 加载失败 -->
<view v-if="loadError" class="err-box" @tap="load(true)">
<FaIcon name="triangle-exclamation" color="#f04438" :size="18" />
<view class="err-body">
<text class="err-title">子系统加载失败</text>
<text class="err-msg">{{ loadError }},点击重试</text>
</view>
<FaIcon name="rotate" color="#f04438" :size="16" />
</view>
<!-- 子系统分组卡:组内一行 4 个 -->
<view v-else-if="!loading && cards.length === 0" class="empty-box">
<FaIcon name="cube" color="#c0c4cc" :size="32" />
<text class="empty-text">暂无子系统,请联系管理员开通</text>
</view>
<view v-else class="grid">
<view
v-for="card in cards"
:key="card.code || card.id"
class="cell"
@tap="enter(card)"
>
<view class="portal-card" :class="{ locked: card.locked }">
<view class="tint-icon portal-icon" :class="card.tint">
<FaIcon :name="card.icon" color="#ffffff" :size="26" />
</view>
<text class="portal-name">{{ card.name }}</text>
</view>
</view>
</view>
<view class="bottom-space" />
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { mpHeadPad } from '@/utils/mpHead.js'
const headPad = mpHeadPad()
import { onShow } from '@dcloudio/uni-app'
import { isLoggedIn } from '@/utils/auth.js'
import { fetchHome } from '@/api/workspace.js'
import { openMenu } from '@/utils/nav.js'
import { getSubsystemByModule, goSubsystem } from '@/config/subsystems.js'
/** 模块 code → 子系统 slug → 进入其概览页;未注册返回 false */
function goSubsystemByModule(code) {
const sub = getSubsystemByModule(code)
return sub ? goSubsystem(sub.slug) : false
}
/** 模块 code → 端上图标(后端 icon 存的是 Element Plus 图标名,端上用不了) */
const MODULE_ICONS = {
crm: 'handshake',
erp: 'boxes-stacked',
oa: 'clipboard-check',
cms: 'globe',
ai: 'robot',
system: 'gear',
tool: 'wrench'
}
const loading = ref(true)
const loadError = ref('')
const user = ref({})
const modules = ref([])
const menus = ref([])
/** 叶子菜单(可直接跳转的功能页),已锁定的过滤掉 */
const leafMenus = computed(() => menus.value.filter((m) => m.leaf !== false && !m.locked))
/** 子系统卡片:每个业务模块一张卡 */
const cards = computed(() => {
const byCode = {}
leafMenus.value.forEach((m) => {
if (!m.module) return
if (!byCode[m.module]) byCode[m.module] = []
byCode[m.module].push(m)
})
return modules.value
.filter((m) => m.type !== 2) // 2=系统配置,不作为子系统入口
.map((mod, index) => {
const code = String(mod.code || '').toLowerCase()
const items = byCode[mod.code] || []
const locked = mod.enabled === false
return {
id: mod.id,
code: mod.code,
path: mod.path || '',
name: mod.name || '未命名系统',
desc: locked
? '未开通该模块'
: mod.description || (items.length ? items.length + ' 个功能' : '暂无功能'),
icon: MODULE_ICONS[code] || 'cube',
tint: locked ? 'tint-off' : 'tint-' + (index % 8),
locked,
items,
total: items.length
}
})
})
const enabledCount = computed(() => cards.value.filter((c) => !c.locked).length)
/* ==================== 数据加载 ==================== */
async function load(force = false) {
loading.value = true
try {
const data = await fetchHome(force)
user.value = data.user || {}
modules.value = Array.isArray(data.modules) ? data.modules : []
menus.value = Array.isArray(data.menus) ? data.menus : []
loadError.value = ''
} catch (e) {
console.error('[nav] load failed:', e)
loadError.value = (e && e.message) || '接口请求失败'
} finally {
loading.value = false
}
}
/* ==================== 交互 ==================== */
/**
* 点击子系统卡片 → 进入该子系统独立页面组(带自己的 tabbar)。
* 未在 config/subsystems.js 注册的模块,降级为直接打开 Web 端该模块首页。
*/
function enter(card) {
if (card.locked) {
openMenu({ locked: true, title: card.name })
return
}
if (goSubsystemByModule(card.code)) return
if (card.items.length) {
openMenu(card.items[0], card.name)
return
}
if (card.path) {
openMenu({ title: card.name, path: card.path, leaf: true }, card.name)
return
}
uni.showToast({ title: card.name + '暂未开放', icon: 'none' })
}
/* ==================== 生命周期 ==================== */
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
return
}
load()
})
onShow(() => {
if (isLoggedIn() && !loading.value) load()
})
</script>
<style lang="scss" scoped>
@import '@/src/styles/page-common.scss';
.page {
@include page-shell;
}
/* ============ 顶部信息(融入页面背景) ============ */
.head {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 32rpx) $page-padding-x 12rpx;
/* #ifdef MP-WEIXIN */
/* 小程序端 --status-bar-height 固定 25px,需真实状态栏(env)+胶囊避让 */
padding-top: calc(env(safe-area-inset-top, 25px) + 96rpx);
/* #endif */
background: $color-bg-page;
}
.head-row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16rpx;
}
.head-title {
font-size: 42rpx;
font-weight: 600;
color: $color-text;
letter-spacing: 1rpx;
}
.head-count {
font-size: 23rpx;
color: $color-text-muted;
flex-shrink: 0;
}
.head-sub {
display: block;
margin-top: 8rpx;
font-size: 23rpx;
color: $color-text-muted;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ============ 内容区 ============ */
.body {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 8rpx $page-padding-x 0;
}
/* ============ 失败提示 ============ */
.err-box {
display: flex;
align-items: center;
gap: 16rpx;
padding: 24rpx 22rpx;
background: #fef3f2;
border: 1rpx solid #fdd8d4;
border-radius: $radius-md;
}
.err-body {
flex: 1;
min-width: 0;
}
.err-title {
display: block;
font-size: 27rpx;
font-weight: 500;
color: $color-danger;
}
.err-msg {
display: block;
margin-top: 6rpx;
font-size: 22rpx;
color: #b42318;
word-break: break-all;
}
.empty-box {
@include card;
margin-top: 24rpx;
padding: 80rpx 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 18rpx;
}
.empty-text {
font-size: 26rpx;
color: $color-text-muted;
}
/* ============ 子系统门户宫格:一行 2 个,仅图标 + 名称 ============ */
.grid {
display: flex;
flex-wrap: wrap;
margin-right: -20rpx;
padding-top: 16rpx;
}
.cell {
width: 50%;
padding-right: 20rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
}
.portal-card {
@include card;
padding: 40rpx 16rpx 34rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
transition: transform 0.15s ease;
&:active {
transform: scale(0.97);
}
}
.portal-icon {
@include tint-icon(108rpx, 28rpx);
}
.portal-name {
max-width: 100%;
font-size: 28rpx;
font-weight: 600;
letter-spacing: 0.5rpx;
color: $color-text;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 未开通:灰底图标 + 灰字,不做角标,保持门户的干净 */
.portal-card.locked .portal-name {
color: $color-text-muted;
}
.bottom-space {
height: 48rpx;
}
</style>