From 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 27 四月 2026 10:13:53 +0800
Subject: [PATCH] 小程序对接finish
---
frontend-admin/src/layouts/MainLayout.vue | 139 +++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 130 insertions(+), 9 deletions(-)
diff --git a/frontend-admin/src/layouts/MainLayout.vue b/frontend-admin/src/layouts/MainLayout.vue
index 839f2a9..632a048 100644
--- a/frontend-admin/src/layouts/MainLayout.vue
+++ b/frontend-admin/src/layouts/MainLayout.vue
@@ -8,6 +8,7 @@
<el-menu
:default-active="active"
:default-openeds="defaultOpeneds"
+ unique-opened
router
class="side-menu"
background-color="#2f63d4"
@@ -76,12 +77,12 @@
>
<template #title>
<el-icon><Document /></el-icon>
- <span>记录与日志</span>
- </template>
- <el-menu-item v-if="auth.hasPermission('appointments:page')" index="/appointments">预约记录</el-menu-item>
- <el-menu-item v-if="auth.hasPermission('approvals:page')" index="/approvals">待办日志</el-menu-item>
- <el-menu-item v-if="auth.hasPermission('exports:page')" index="/exports">导出任务</el-menu-item>
- </el-sub-menu>
+ <span>记录与日志</span>
+ </template>
+ <el-menu-item v-if="auth.hasPermission('appointments:page')" index="/appointments">预约记录</el-menu-item>
+ <el-menu-item v-if="auth.hasPermission('approvals:page')" index="/approvals">运维日志</el-menu-item>
+ <el-menu-item v-if="auth.hasPermission('exports:page')" index="/exports">操作日志</el-menu-item>
+ </el-sub-menu>
<el-sub-menu
v-if="auth.hasAnyPermission(['sys:users:page', 'sys:roles:page'])"
@@ -125,6 +126,7 @@
class="route-tab"
:class="{ active: tab.path === active }"
@click="goTab(tab.path)"
+ @contextmenu.prevent="openTabContextMenu($event, tab.path)"
>
<el-icon class="tab-home" v-if="tab.path === '/dashboard'"><House /></el-icon>
<span>{{ tab.title }}</span>
@@ -137,6 +139,15 @@
x
</button>
</div>
+ <div
+ v-if="tabContextMenu.visible"
+ class="tab-context-menu"
+ :style="{ left: `${tabContextMenu.x}px`, top: `${tabContextMenu.y}px` }"
+ >
+ <button type="button" class="tab-context-item" @click="closeLeftTabs">关闭左边</button>
+ <button type="button" class="tab-context-item" @click="closeRightTabs">关闭右边</button>
+ <button type="button" class="tab-context-item" @click="closeOtherTabs">关闭其他</button>
+ </div>
</div>
</div>
@@ -148,7 +159,7 @@
</template>
<script setup lang="ts">
-import { computed, ref, watch } from "vue";
+import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Document, Grid, House, Operation, Setting, User, UserFilled, Wallet } from "@element-plus/icons-vue";
import { useAuthStore } from "../stores/auth";
@@ -164,12 +175,27 @@
const router = useRouter();
const auth = useAuthStore();
const active = computed(() => route.path);
-const defaultOpeneds: string[] = [];
+const defaultOpeneds = computed(() => {
+ const path = route.path;
+ if (path.startsWith("/system/")) return ["system"];
+ if (path.startsWith("/staff/")) return ["staff"];
+ if (path.startsWith("/region/")) return ["region"];
+ if (path === "/appointments" || path === "/approvals" || path === "/exports") return ["logs"];
+ if (path === "/users" || path.startsWith("/roles")) return ["account"];
+ return [];
+});
const tabs = ref<LayoutTab[]>([{ path: "/dashboard", title: "首页", closable: false }]);
+const tabContextMenu = reactive({
+ visible: false,
+ x: 0,
+ y: 0,
+ path: "",
+});
const routeTitleMap: Record<string, string> = {
"/dashboard": "首页",
"/appointments": "预约记录",
- "/approvals": "待办日志",
+ "/approvals": "运维日志",
+ "/exports": "操作日志",
"/system/approval-settings": "审核配置",
"/system/booking-settings": "预约配置",
"/system/visit-reasons": "来访事由配置",
@@ -183,6 +209,7 @@
"/region/pass-areas": "通行区域管理",
"/region/local-units": "属地单位管理",
};
+const hiddenTabRouteNames = new Set(["staff-group-detail", "role-detail"]);
const nameInitial = computed(() => {
const n = auth.displayName;
return n && n.length ? n.slice(0, 1) : "?";
@@ -199,6 +226,9 @@
}
function ensureTab() {
+ if (typeof route.name === "string" && hiddenTabRouteNames.has(route.name)) {
+ return;
+ }
const path = route.path;
const title = resolveRouteTitle();
const idx = tabs.value.findIndex((item) => item.path === path);
@@ -220,6 +250,7 @@
}
function closeTab(path: string) {
+ hideTabContextMenu();
const idx = tabs.value.findIndex((item) => item.path === path);
if (idx < 0) {
return;
@@ -233,6 +264,59 @@
router.push(fallback?.path || "/dashboard");
}
+function openTabContextMenu(event: MouseEvent, path: string) {
+ tabContextMenu.visible = true;
+ tabContextMenu.x = event.clientX;
+ tabContextMenu.y = event.clientY;
+ tabContextMenu.path = path;
+}
+
+function hideTabContextMenu() {
+ tabContextMenu.visible = false;
+ tabContextMenu.path = "";
+}
+
+function closeTabsByFilter(keep: (tab: LayoutTab, index: number) => boolean) {
+ const currentPath = route.path;
+ tabs.value = tabs.value.filter((tab, index) => !tab.closable || keep(tab, index));
+ if (!tabs.value.some((tab) => tab.path === currentPath)) {
+ const fallback = tabs.value[tabs.value.length - 1] || tabs.value[0];
+ router.push(fallback?.path || "/dashboard");
+ }
+ hideTabContextMenu();
+}
+
+function closeLeftTabs() {
+ const targetIndex = tabs.value.findIndex((tab) => tab.path === tabContextMenu.path);
+ if (targetIndex < 0) {
+ hideTabContextMenu();
+ return;
+ }
+ closeTabsByFilter((tab, index) => index >= targetIndex || !tab.closable);
+}
+
+function closeRightTabs() {
+ const targetIndex = tabs.value.findIndex((tab) => tab.path === tabContextMenu.path);
+ if (targetIndex < 0) {
+ hideTabContextMenu();
+ return;
+ }
+ closeTabsByFilter((tab, index) => index <= targetIndex || !tab.closable);
+}
+
+function closeOtherTabs() {
+ const targetPath = tabContextMenu.path;
+ if (!targetPath) {
+ hideTabContextMenu();
+ return;
+ }
+ closeTabsByFilter((tab) => tab.path === targetPath || !tab.closable);
+}
+
+function handleGlobalClick() {
+ hideTabContextMenu();
+}
+
watch(
() => route.path,
() => {
@@ -240,6 +324,14 @@
},
{ immediate: true }
);
+
+onMounted(() => {
+ window.addEventListener("click", handleGlobalClick);
+});
+
+onBeforeUnmount(() => {
+ window.removeEventListener("click", handleGlobalClick);
+});
async function logout() {
try {
@@ -405,6 +497,7 @@
}
.tabs-bar {
+ position: relative;
display: flex;
align-items: center;
gap: 6px;
@@ -452,6 +545,34 @@
cursor: pointer;
}
+.tab-context-menu {
+ position: fixed;
+ z-index: 1200;
+ min-width: 120px;
+ padding: 6px 0;
+ border: 1px solid #d8e3fb;
+ border-radius: 8px;
+ background: #ffffff;
+ box-shadow: 0 8px 24px rgba(52, 84, 148, 0.16);
+}
+
+.tab-context-item {
+ display: block;
+ width: 100%;
+ border: 0;
+ padding: 8px 14px;
+ background: transparent;
+ color: #4e5f82;
+ font-size: 13px;
+ text-align: left;
+ cursor: pointer;
+}
+
+.tab-context-item:hover {
+ background: #f3f7ff;
+ color: #4f74d8;
+}
+
.main-area {
background: linear-gradient(180deg, #f3f7ff 0%, #eef3fd 100%);
padding: 14px;
--
Gitblit v1.7.1