|
@@ -1,69 +1,91 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="layout">
|
|
<div class="layout">
|
|
|
- <!-- 左侧菜单 -->
|
|
|
|
|
|
|
+ <!-- 左侧菜单:对齐流程协同页完整分组;未落地模块 path 为空 -->
|
|
|
<aside class="sidebar">
|
|
<aside class="sidebar">
|
|
|
<div class="brand">
|
|
<div class="brand">
|
|
|
- <!-- <div class="brand-icon">申</div> -->
|
|
|
|
|
- <div class="brand-icon"><img :src="logo" alt="logo" style="width: 40px; height: 40px;" /></div>
|
|
|
|
|
|
|
+ <div class="brand-icon"><img :src="logo" alt="logo" style="width: 40px; height: 40px;" /></div>
|
|
|
<div>
|
|
<div>
|
|
|
<strong>申勤物业</strong>
|
|
<strong>申勤物业</strong>
|
|
|
<span>数字化平台</span>
|
|
<span>数字化平台</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<nav class="nav">
|
|
<nav class="nav">
|
|
|
- <div v-for="item in navItems" :key="item.path" @click="handleClick(item.path)">
|
|
|
|
|
- <router-link
|
|
|
|
|
- v-if="item.path !== 'nav-label'"
|
|
|
|
|
- :class="{ active: $route.path === item.path }"
|
|
|
|
|
- :to="item.path"
|
|
|
|
|
- >
|
|
|
|
|
- {{ item.name }}
|
|
|
|
|
- </router-link>
|
|
|
|
|
- <div v-else class="nav-label">
|
|
|
|
|
- {{ item.name }}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <template v-for="item in navTree" :key="item.id">
|
|
|
|
|
+ <!-- 无子目录:直接入口(如首页) -->
|
|
|
|
|
+ <router-link
|
|
|
|
|
+ v-if="item.type === 'link'"
|
|
|
|
|
+ :class="{ active: isActive(item.path) }"
|
|
|
|
|
+ :to="item.path"
|
|
|
|
|
+ @click="handleNavClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ item.name }}
|
|
|
|
|
+ </router-link>
|
|
|
|
|
+ <!-- 有子目录的分组 -->
|
|
|
|
|
+ <div v-else class="nav-group">
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="nav-group-btn"
|
|
|
|
|
+ :class="{ active: isGroupActive(item) }"
|
|
|
|
|
+ @click="toggleGroup(item.id)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span>{{ item.name }}</span>
|
|
|
|
|
+ <span class="nav-chevron">{{ expanded[item.id] ? '▾' : '▸' }}</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <div v-show="expanded[item.id]" class="nav-children">
|
|
|
|
|
+ <template v-for="child in item.children" :key="child.name">
|
|
|
|
|
+ <router-link
|
|
|
|
|
+ v-if="child.path"
|
|
|
|
|
+ :class="{ active: isActive(child.path) }"
|
|
|
|
|
+ :to="child.path"
|
|
|
|
|
+ @click="handleNavClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ child.name }}
|
|
|
|
|
+ </router-link>
|
|
|
|
|
+ <span
|
|
|
|
|
+ v-else
|
|
|
|
|
+ class="nav-placeholder"
|
|
|
|
|
+ title="模块待建设"
|
|
|
|
|
+ @click="onPlaceholder(child.name)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ child.name }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
</nav>
|
|
</nav>
|
|
|
-
|
|
|
|
|
</aside>
|
|
</aside>
|
|
|
|
|
|
|
|
- <!-- 右侧内容区 -->
|
|
|
|
|
<div class="main">
|
|
<div class="main">
|
|
|
- <!-- 顶部导航 -->
|
|
|
|
|
<header class="topbar">
|
|
<header class="topbar">
|
|
|
<div>
|
|
<div>
|
|
|
<h1>{{ currentTitle }}</h1>
|
|
<h1>{{ currentTitle }}</h1>
|
|
|
<p>{{ currentSubtitle }}</p>
|
|
<p>{{ currentSubtitle }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
- <!-- <el-input
|
|
|
|
|
- v-model="searchText"
|
|
|
|
|
- placeholder="搜索项目、人员…"
|
|
|
|
|
- class="search-input"
|
|
|
|
|
- /> -->
|
|
|
|
|
- <div class="sidebar-footer">
|
|
|
|
|
- <el-dropdown @command="handleLogout">
|
|
|
|
|
- <span class="el-dropdown-link">
|
|
|
|
|
- <el-icon :size="20" color="var(--text2)">
|
|
|
|
|
- <UserFilled />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- <span style="padding: 0 6px">
|
|
|
|
|
- {{userName}}
|
|
|
|
|
|
|
+ <div class="topbar-right">
|
|
|
|
|
+ <!-- 子页可通过 Teleport to="#topbar-extra" 注入右上角操作按钮 -->
|
|
|
|
|
+ <div id="topbar-extra" class="topbar-extra"></div>
|
|
|
|
|
+ <div class="sidebar-footer">
|
|
|
|
|
+ <el-dropdown @command="handleLogout">
|
|
|
|
|
+ <span class="el-dropdown-link">
|
|
|
|
|
+ <el-icon :size="20" color="var(--text2)">
|
|
|
|
|
+ <UserFilled />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <span style="padding: 0 6px">
|
|
|
|
|
+ {{ userName }}
|
|
|
|
|
+ </span>
|
|
|
</span>
|
|
</span>
|
|
|
- </span>
|
|
|
|
|
- <template #dropdown>
|
|
|
|
|
- <el-dropdown-menu>
|
|
|
|
|
- <!-- <el-dropdown-item command="login">账号登录</el-dropdown-item> -->
|
|
|
|
|
- <el-dropdown-item command="logout">退出登录</el-dropdown-item
|
|
|
|
|
- >
|
|
|
|
|
- </el-dropdown-menu>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dropdown>
|
|
|
|
|
-
|
|
|
|
|
|
|
+ <template #dropdown>
|
|
|
|
|
+ <el-dropdown-menu>
|
|
|
|
|
+ <el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
|
|
|
|
+ </el-dropdown-menu>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dropdown>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</header>
|
|
</header>
|
|
|
|
|
|
|
|
- <!-- 路由内容 -->
|
|
|
|
|
- <div :class="isGis == '/onemap' ? 'content-gis' : 'content'">
|
|
|
|
|
|
|
+ <div :class="isGis ? 'content-gis' : 'content'">
|
|
|
<router-view />
|
|
<router-view />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -71,7 +93,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { computed, ref } from 'vue'
|
|
|
|
|
|
|
+import { computed, reactive, watch } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
import { useRouter } from 'vue-router'
|
|
|
import { ElMessage } from 'element-plus'
|
|
import { ElMessage } from 'element-plus'
|
|
|
import { useUserStore } from '@/stores/user'
|
|
import { useUserStore } from '@/stores/user'
|
|
@@ -83,55 +105,203 @@ const logo = new URL('/static/image/logo.png', import.meta.url).href
|
|
|
const userStore = useUserStore()
|
|
const userStore = useUserStore()
|
|
|
const router = useRouter()
|
|
const router = useRouter()
|
|
|
const userName = getDisplayUserName()
|
|
const userName = getDisplayUserName()
|
|
|
-const navItems = [
|
|
|
|
|
- { path: '/dashboard', name: '工作台' },
|
|
|
|
|
- { path: '/onemap', name: '空间一张图' },
|
|
|
|
|
- { path: '/projects', name: '项目台账' },
|
|
|
|
|
- { path: 'nav-label', name: '市场拓展' },
|
|
|
|
|
- { path: '/market', name: '市场拓展中心' },
|
|
|
|
|
- { path: '/renewal', name: '标书编制' },
|
|
|
|
|
- { path: '/tender-pool', name: '招标监测' },
|
|
|
|
|
- { path: '/bid-result', name: '开标与中标' }
|
|
|
|
|
|
|
+
|
|
|
|
|
+/** path 为空表示暂未落地,仅展示菜单名 */
|
|
|
|
|
+type NavChild = { name: string; path?: string }
|
|
|
|
|
+type NavNode =
|
|
|
|
|
+ | { id: string; type: 'link'; name: string; path: string }
|
|
|
|
|
+ | { id: string; type: 'group'; name: string; children: NavChild[] }
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 侧栏对齐 word/2026-09-11-申勤数字化平台-流程协同.html 的 menus
|
|
|
|
|
+ * 已有业务挂现有路由;其余子项 path 留空,后续按部门落地再改
|
|
|
|
|
+ */
|
|
|
|
|
+const navTree: NavNode[] = [
|
|
|
|
|
+ { id: 'home', type: 'link', name: '首页', path: '/dashboard' },
|
|
|
|
|
+ // {
|
|
|
|
|
+ // id: 'home',
|
|
|
|
|
+ // name: '首页',
|
|
|
|
|
+ // children: [
|
|
|
|
|
+ // { name: '经营总览', path: '/dashboard' },
|
|
|
|
|
+ // // { name: '我的待办' },
|
|
|
|
|
+ // ],
|
|
|
|
|
+ // },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'projects',
|
|
|
|
|
+ type: 'group',
|
|
|
|
|
+ name: '项目中心',
|
|
|
|
|
+ children: [
|
|
|
|
|
+ { name: '项目库', path: '/projects' },
|
|
|
|
|
+ // { name: '流程总览' },
|
|
|
|
|
+ { name: '项目分布', path: '/onemap' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'market',
|
|
|
|
|
+ type: 'group',
|
|
|
|
|
+ name: '市场部',
|
|
|
|
|
+ children: [
|
|
|
|
|
+ // { name: '部门工作台' },
|
|
|
|
|
+ { name: '招标监测', path: '/tender-pool' },
|
|
|
|
|
+ { name: '标书编制', path: '/renewal' },
|
|
|
|
|
+ { name: '开标与中标', path: '/bid-result' },
|
|
|
|
|
+ // { name: '投标 / 续标跟进' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'ops',
|
|
|
|
|
+ type: 'group',
|
|
|
|
|
+ name: '运营部',
|
|
|
|
|
+ children: [
|
|
|
|
|
+ { name: '部门工作台' },
|
|
|
|
|
+ { name: '交接与定编' },
|
|
|
|
|
+ { name: '履约与项目报告' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'hr',
|
|
|
|
|
+ type: 'group',
|
|
|
|
|
+ name: '人事部',
|
|
|
|
|
+ children: [
|
|
|
|
|
+ { name: '部门工作台' },
|
|
|
|
|
+ { name: '人员清单' },
|
|
|
|
|
+ { name: '证书管理' },
|
|
|
|
|
+ { name: '差额调配' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'purchase',
|
|
|
|
|
+ type: 'group',
|
|
|
|
|
+ name: '采购部',
|
|
|
|
|
+ children: [{ name: '采购与物资' }],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'finance',
|
|
|
|
|
+ type: 'group',
|
|
|
|
|
+ name: '财务部',
|
|
|
|
|
+ children: [
|
|
|
|
|
+ { name: '财务工作台' },
|
|
|
|
|
+ { name: '合同与回款' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // ,
|
|
|
|
|
+ // {
|
|
|
|
|
+ // id: 'field',
|
|
|
|
|
+ // type: 'group',
|
|
|
|
|
+ // name: '现场条线',
|
|
|
|
|
+ // children: [{ name: '现场工作台' }],
|
|
|
|
|
+ // },
|
|
|
|
|
+ // {
|
|
|
|
|
+ // id: 'data',
|
|
|
|
|
+ // type: 'group',
|
|
|
|
|
+ // name: '资料中心',
|
|
|
|
|
+ // children: [
|
|
|
|
|
+ // { name: '项目资料' },
|
|
|
|
|
+ // { name: '流程规则' },
|
|
|
|
|
+ // { name: '数据回填记录' },
|
|
|
|
|
+ // ],
|
|
|
|
|
+ // },
|
|
|
|
|
+ // {
|
|
|
|
|
+ // id: 'reports',
|
|
|
|
|
+ // type: 'group',
|
|
|
|
|
+ // name: '报表中心',
|
|
|
|
|
+ // children: [{ name: '项目协同报表' }],
|
|
|
|
|
+ // },
|
|
|
]
|
|
]
|
|
|
-const isGis = ref('')
|
|
|
|
|
|
|
+
|
|
|
|
|
+const flatLinks = computed(() => {
|
|
|
|
|
+ const list: Array<{ name: string; path: string }> = []
|
|
|
|
|
+ for (const n of navTree) {
|
|
|
|
|
+ if (n.type === 'link') list.push({ name: n.name, path: n.path })
|
|
|
|
|
+ else list.push(...n.children.filter((c): c is NavChild & { path: string } => !!c.path))
|
|
|
|
|
+ }
|
|
|
|
|
+ return list
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const expanded = reactive<Record<string, boolean>>(
|
|
|
|
|
+ Object.fromEntries(
|
|
|
|
|
+ navTree
|
|
|
|
|
+ .filter((n): n is Extract<NavNode, { type: 'group' }> => n.type === 'group')
|
|
|
|
|
+ .map((g) => [g.id, g.id === 'projects' || g.id === 'market'])
|
|
|
|
|
+ )
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const routePath = computed(() => router.currentRoute.value.path)
|
|
|
|
|
+
|
|
|
|
|
+const isGis = computed(() => routePath.value === '/onemap' || routePath.value.startsWith('/onemap'))
|
|
|
|
|
+
|
|
|
|
|
+const isActive = (path: string) => {
|
|
|
|
|
+ const p = routePath.value
|
|
|
|
|
+ if (path === '/dashboard') return p === '/dashboard' || p === '/'
|
|
|
|
|
+ return p === path || p.startsWith(path + '/')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const isGroupActive = (item: NavNode) => {
|
|
|
|
|
+ if (item.type !== 'group') return false
|
|
|
|
|
+ return item.children.some((c) => c.path && isActive(c.path))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const toggleGroup = (id: string) => {
|
|
|
|
|
+ expanded[id] = !expanded[id]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const findGroupIdByPath = (p: string): string | undefined => {
|
|
|
|
|
+ for (const n of navTree) {
|
|
|
|
|
+ if (n.type !== 'group') continue
|
|
|
|
|
+ if (n.children.some((c) => c.path && (p === c.path || p.startsWith(c.path + '/')))) return n.id
|
|
|
|
|
+ }
|
|
|
|
|
+ if (p.includes('project-detail')) return 'projects'
|
|
|
|
|
+ if (p.includes('renewal')) return 'market'
|
|
|
|
|
+ return undefined
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 进入子路由时自动展开所属分组 */
|
|
|
|
|
+watch(
|
|
|
|
|
+ routePath,
|
|
|
|
|
+ (p) => {
|
|
|
|
|
+ const gid = findGroupIdByPath(p)
|
|
|
|
|
+ if (gid) expanded[gid] = true
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
const currentTitle = computed(() => {
|
|
const currentTitle = computed(() => {
|
|
|
- // sessionStorage.setItem('activeTab','ongoing')
|
|
|
|
|
- const item = navItems.find(n => n.path === router.currentRoute.value.path)
|
|
|
|
|
- isGis.value = item?.path ?? ''
|
|
|
|
|
- if(!item){
|
|
|
|
|
- return sessionStorage.getItem('name')
|
|
|
|
|
- }else{
|
|
|
|
|
- return item ? item.name : '工作台'
|
|
|
|
|
|
|
+ const p = routePath.value
|
|
|
|
|
+ const hit = flatLinks.value.find((n) => p === n.path)
|
|
|
|
|
+ if (hit) return hit.name
|
|
|
|
|
+ if (p.includes('project-detail')) return sessionStorage.getItem('name') || '项目详情'
|
|
|
|
|
+ if (p.includes('renewal-expire-detail') || p.includes('renewal-xq-detail') || p.includes('renewal-detail')) {
|
|
|
|
|
+ return sessionStorage.getItem('name') || '续标详情'
|
|
|
}
|
|
}
|
|
|
|
|
+ return sessionStorage.getItem('name') || '首页'
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const handleClick = (_path: string) => {
|
|
|
|
|
- sessionStorage.setItem('activeTab','ongoing')
|
|
|
|
|
|
|
+const handleNavClick = () => {
|
|
|
|
|
+ sessionStorage.setItem('activeTab', 'ongoing')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onPlaceholder = (name: string) => {
|
|
|
|
|
+ ElMessage.info(`「${name}」模块待建设`)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const currentSubtitle = computed(() => {
|
|
const currentSubtitle = computed(() => {
|
|
|
- if (router.currentRoute.value.path === '/dashboard') {
|
|
|
|
|
- return '今日待办与关键指标 · 续标预警优先展示'
|
|
|
|
|
- }else if (router.currentRoute.value.path === '/onemap') {
|
|
|
|
|
- return '数据可视化平台 · 空间一张图'
|
|
|
|
|
- }else if (router.currentRoute.value.path === '/projects') {
|
|
|
|
|
- return '在管项目一览'
|
|
|
|
|
- }else if (router.currentRoute.value.path === '/market') {
|
|
|
|
|
- return '招标监测 · 标书编制 · 投标履约 · 项目续标'
|
|
|
|
|
- }else if (router.currentRoute.value.path === '/renewal') {
|
|
|
|
|
- return '合同到期预警 · 续标全流程跟进'
|
|
|
|
|
- }else if (router.currentRoute.value.path === '/tender-pool') {
|
|
|
|
|
- return '政府采购网抓取 · 续标监控 · 智能匹配'
|
|
|
|
|
- }else if (router.currentRoute.value.path === '/bid-workbench') {
|
|
|
|
|
- return '需求拆解 · 数据映射 · 分步确认 · 生成导出'
|
|
|
|
|
- }else if (router.currentRoute.value.path === '/bid-result') {
|
|
|
|
|
- return '投标登记 · 开标监控 · 结果抓取 · 中标推送'
|
|
|
|
|
- }else if (router.currentRoute.value.path.includes('project-detail/') || router.currentRoute.value.path.includes('renewal-detail/') || router.currentRoute.value.path.includes('renewal-expire-detail/')) {
|
|
|
|
|
- const addr = (sessionStorage.getItem('projectName'))
|
|
|
|
|
- return addr
|
|
|
|
|
- }else {
|
|
|
|
|
- return '欢迎使用申勤物业数字化平台'
|
|
|
|
|
|
|
+ const p = routePath.value
|
|
|
|
|
+ if (p === '/dashboard') return '首页 · 今日待办与关键指标'
|
|
|
|
|
+ if (p === '/onemap') return '项目中心 · 项目分布'
|
|
|
|
|
+ if (p === '/projects') return '项目中心 · 项目库'
|
|
|
|
|
+ if (p === '/market') return '市场部 · 招标监测 · 标书编制 · 开标中标'
|
|
|
|
|
+ if (p === '/renewal') return '市场部 · 标书编制 · 续标全流程跟进'
|
|
|
|
|
+ if (p === '/tender-pool') return '市场部 · 政府采购网抓取 · 招标监控'
|
|
|
|
|
+ if (p === '/bid-result') return '市场部 · 开标监控 · 中标推送'
|
|
|
|
|
+ if (
|
|
|
|
|
+ p.includes('project-detail/') ||
|
|
|
|
|
+ p.includes('renewal-detail/') ||
|
|
|
|
|
+ p.includes('renewal-expire-detail/') ||
|
|
|
|
|
+ p.includes('renewal-xq-detail/')
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return sessionStorage.getItem('projectName') || ''
|
|
|
}
|
|
}
|
|
|
|
|
+ return '欢迎使用申勤物业数字化平台'
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const handleLogout = () => {
|
|
const handleLogout = () => {
|
|
@@ -144,17 +314,21 @@ const handleLogout = () => {
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
.layout {
|
|
.layout {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- min-height: 100vh;
|
|
|
|
|
|
|
+ height: 100vh;
|
|
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.sidebar {
|
|
.sidebar {
|
|
|
width: 220px;
|
|
width: 220px;
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ min-height: 0;
|
|
|
background: var(--surface);
|
|
background: var(--surface);
|
|
|
border-right: 1px solid var(--border);
|
|
border-right: 1px solid var(--border);
|
|
|
padding: 16px 12px;
|
|
padding: 16px 12px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.brand {
|
|
.brand {
|
|
@@ -162,12 +336,12 @@ const handleLogout = () => {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
gap: 10px;
|
|
|
padding: 8px 10px 20px;
|
|
padding: 8px 10px 20px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.brand-icon {
|
|
.brand-icon {
|
|
|
width: 36px;
|
|
width: 36px;
|
|
|
height: 36px;
|
|
height: 36px;
|
|
|
- /* background: var(--primary); */
|
|
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
display: flex;
|
|
display: flex;
|
|
@@ -189,14 +363,40 @@ const handleLogout = () => {
|
|
|
|
|
|
|
|
.nav {
|
|
.nav {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ overflow-x: hidden;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ font-family: inherit;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ /* 菜单区滚动条常显,避免整页滚动 */
|
|
|
|
|
+ scrollbar-width: thin;
|
|
|
|
|
+ scrollbar-color: rgba(163, 166, 173, 0.7) transparent;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav::-webkit-scrollbar {
|
|
|
|
|
+ width: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav::-webkit-scrollbar-thumb {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ background-color: rgba(163, 166, 173, 0.55);
|
|
|
|
|
+ border-radius: 6px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.nav a {
|
|
|
|
|
|
|
+.nav::-webkit-scrollbar-track {
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav a,
|
|
|
|
|
+.nav-placeholder {
|
|
|
display: block;
|
|
display: block;
|
|
|
padding: 8px 12px;
|
|
padding: 8px 12px;
|
|
|
border-radius: 6px;
|
|
border-radius: 6px;
|
|
|
color: var(--text2);
|
|
color: var(--text2);
|
|
|
|
|
+ font-family: inherit;
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
margin-bottom: 2px;
|
|
margin-bottom: 2px;
|
|
|
text-decoration: none;
|
|
text-decoration: none;
|
|
|
}
|
|
}
|
|
@@ -211,12 +411,74 @@ const handleLogout = () => {
|
|
|
color: var(--primary);
|
|
color: var(--primary);
|
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
|
}
|
|
}
|
|
|
-.nav-label {
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
- color: var(--text3);
|
|
|
|
|
- padding: 12px 12px 6px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
|
|
+
|
|
|
|
|
+.nav-placeholder {
|
|
|
|
|
+ color: var(--text3);
|
|
|
|
|
+ cursor: default;
|
|
|
|
|
+ opacity: 0.75;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav-placeholder:hover {
|
|
|
|
|
+ background: #f8fafb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav-group {
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.nav-group-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ color: var(--text2);
|
|
|
|
|
+ font-family: inherit;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav-group-btn:hover,
|
|
|
|
|
+.nav-group-btn.active {
|
|
|
|
|
+ color: var(--text);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav-chevron {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ opacity: 0.7;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav-children {
|
|
|
|
|
+ padding-left: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav-children a,
|
|
|
|
|
+.nav-children .nav-placeholder {
|
|
|
|
|
+ padding-left: 16px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.topbar-right {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.topbar-extra {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.sidebar-footer {
|
|
.sidebar-footer {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
@@ -239,6 +501,8 @@ const handleLogout = () => {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ height: 100%;
|
|
|
background: var(--bg);
|
|
background: var(--bg);
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
}
|
|
}
|
|
@@ -265,20 +529,13 @@ const handleLogout = () => {
|
|
|
color: var(--text3);
|
|
color: var(--text3);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.search-input {
|
|
|
|
|
- width: 240px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
.content {
|
|
.content {
|
|
|
padding: 20px 24px 32px;
|
|
padding: 20px 24px 32px;
|
|
|
- /* flex: 1; */
|
|
|
|
|
height: calc(100vh - 70px);
|
|
height: calc(100vh - 70px);
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
|
}
|
|
}
|
|
|
.content-gis {
|
|
.content-gis {
|
|
|
padding: 0 0;
|
|
padding: 0 0;
|
|
|
- /* flex: 1; */
|
|
|
|
|
- /* height: calc(70px); */
|
|
|
|
|
height: 100%;
|
|
height: 100%;
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
|
}
|
|
}
|
|
@@ -292,14 +549,19 @@ const handleLogout = () => {
|
|
|
}
|
|
}
|
|
|
.el-dropdown,
|
|
.el-dropdown,
|
|
|
.el-dropdown * {
|
|
.el-dropdown * {
|
|
|
- outline: none !important; /* 确保下拉触发器没有轮廓 */
|
|
|
|
|
|
|
+ outline: none !important;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
@media (max-width: 768px) {
|
|
|
.sidebar {
|
|
.sidebar {
|
|
|
width: 60px;
|
|
width: 60px;
|
|
|
}
|
|
}
|
|
|
- .brand span, .brand strong, .nav a, .sidebar-footer span {
|
|
|
|
|
|
|
+ .brand span,
|
|
|
|
|
+ .brand strong,
|
|
|
|
|
+ .nav a,
|
|
|
|
|
+ .nav-placeholder,
|
|
|
|
|
+ .nav-group-btn,
|
|
|
|
|
+ .sidebar-footer span {
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|