admin跳转
This commit is contained in:
@@ -1,130 +0,0 @@
|
|||||||
|
|
||||||
// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68
|
|
||||||
|
|
||||||
import {loadShare} from "@module-federation/runtime";
|
|
||||||
const importMap = {
|
|
||||||
|
|
||||||
"element-plus": async () => {
|
|
||||||
let pkg = await import("__mf__virtual/platform__prebuild__element_mf_2_plus__prebuild__.js");
|
|
||||||
return pkg;
|
|
||||||
}
|
|
||||||
,
|
|
||||||
"vue": async () => {
|
|
||||||
let pkg = await import("__mf__virtual/platform__prebuild__vue__prebuild__.js");
|
|
||||||
return pkg;
|
|
||||||
}
|
|
||||||
,
|
|
||||||
"vue-router": async () => {
|
|
||||||
let pkg = await import("__mf__virtual/platform__prebuild__vue_mf_2_router__prebuild__.js");
|
|
||||||
return pkg;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const usedShared = {
|
|
||||||
|
|
||||||
"element-plus": {
|
|
||||||
name: "element-plus",
|
|
||||||
version: "2.12.0",
|
|
||||||
scope: ["default"],
|
|
||||||
loaded: false,
|
|
||||||
from: "platform",
|
|
||||||
async get () {
|
|
||||||
if (false) {
|
|
||||||
throw new Error(`Shared module '${"element-plus"}' must be provided by host`);
|
|
||||||
}
|
|
||||||
usedShared["element-plus"].loaded = true
|
|
||||||
const {"element-plus": pkgDynamicImport} = importMap
|
|
||||||
const res = await pkgDynamicImport()
|
|
||||||
const exportModule = {...res}
|
|
||||||
// All npm packages pre-built by vite will be converted to esm
|
|
||||||
Object.defineProperty(exportModule, "__esModule", {
|
|
||||||
value: true,
|
|
||||||
enumerable: false
|
|
||||||
})
|
|
||||||
return function () {
|
|
||||||
return exportModule
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shareConfig: {
|
|
||||||
singleton: false,
|
|
||||||
requiredVersion: "^2.12.0",
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
,
|
|
||||||
"vue": {
|
|
||||||
name: "vue",
|
|
||||||
version: "3.5.25",
|
|
||||||
scope: ["default"],
|
|
||||||
loaded: false,
|
|
||||||
from: "platform",
|
|
||||||
async get () {
|
|
||||||
if (false) {
|
|
||||||
throw new Error(`Shared module '${"vue"}' must be provided by host`);
|
|
||||||
}
|
|
||||||
usedShared["vue"].loaded = true
|
|
||||||
const {"vue": pkgDynamicImport} = importMap
|
|
||||||
const res = await pkgDynamicImport()
|
|
||||||
const exportModule = {...res}
|
|
||||||
// All npm packages pre-built by vite will be converted to esm
|
|
||||||
Object.defineProperty(exportModule, "__esModule", {
|
|
||||||
value: true,
|
|
||||||
enumerable: false
|
|
||||||
})
|
|
||||||
return function () {
|
|
||||||
return exportModule
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shareConfig: {
|
|
||||||
singleton: false,
|
|
||||||
requiredVersion: "^3.5.25",
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
,
|
|
||||||
"vue-router": {
|
|
||||||
name: "vue-router",
|
|
||||||
version: "4.6.3",
|
|
||||||
scope: ["default"],
|
|
||||||
loaded: false,
|
|
||||||
from: "platform",
|
|
||||||
async get () {
|
|
||||||
if (false) {
|
|
||||||
throw new Error(`Shared module '${"vue-router"}' must be provided by host`);
|
|
||||||
}
|
|
||||||
usedShared["vue-router"].loaded = true
|
|
||||||
const {"vue-router": pkgDynamicImport} = importMap
|
|
||||||
const res = await pkgDynamicImport()
|
|
||||||
const exportModule = {...res}
|
|
||||||
// All npm packages pre-built by vite will be converted to esm
|
|
||||||
Object.defineProperty(exportModule, "__esModule", {
|
|
||||||
value: true,
|
|
||||||
enumerable: false
|
|
||||||
})
|
|
||||||
return function () {
|
|
||||||
return exportModule
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shareConfig: {
|
|
||||||
singleton: false,
|
|
||||||
requiredVersion: "^4.6.3",
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const usedRemotes = [
|
|
||||||
{
|
|
||||||
entryGlobalName: "shared",
|
|
||||||
name: "shared",
|
|
||||||
type: "module",
|
|
||||||
entry: "http://localhost:5000/remoteEntry.js",
|
|
||||||
shareScope: "default",
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
export {
|
|
||||||
usedShared,
|
|
||||||
usedRemotes
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
.sidebar-layout {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 侧边栏 ====================
|
||||||
|
.sidebar {
|
||||||
|
width: 220px;
|
||||||
|
height: 100%;
|
||||||
|
background: #F0EAF4;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: #333;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 64px;
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 16px 12px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-btn {
|
||||||
|
position: static;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-section {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 侧边栏头部
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #888;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(124, 58, 237, 0.1);
|
||||||
|
color: #7c3aed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.logo-img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #fff;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导航菜单
|
||||||
|
.nav-menu {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-section {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
color: #555;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(124, 58, 237, 0.1);
|
||||||
|
color: #7c3aed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: rgba(124, 58, 237, 0.15);
|
||||||
|
color: #7c3aed;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
.user-section {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(124, 58, 237, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 主内容区 ====================
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iframe 容器
|
||||||
|
.iframe-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-header {
|
||||||
|
height: 56px;
|
||||||
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
background: #fafafa;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-iframe {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: #7c3aed;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 响应式 ====================
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
width: 64px;
|
||||||
|
|
||||||
|
&:not(.collapsed) {
|
||||||
|
width: 220px;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-header {
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.iframe-title {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sidebar-layout">
|
||||||
|
<!-- 侧边栏 -->
|
||||||
|
<aside class="sidebar" :class="{ collapsed: collapsed }">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="/logo.jpg" alt="Logo" class="logo-img" />
|
||||||
|
<span v-if="!collapsed" class="logo-text">城市生命线</span>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-btn" @click="toggleSidebar">
|
||||||
|
<el-icon>
|
||||||
|
<DArrowLeft v-if="!collapsed" />
|
||||||
|
<DArrowRight v-else />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="nav-menu">
|
||||||
|
<div class="nav-section">
|
||||||
|
<div
|
||||||
|
v-for="item in menuItems"
|
||||||
|
:key="item.key"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ active: activeMenu === item.key }"
|
||||||
|
@click="handleMenuClick(item)"
|
||||||
|
>
|
||||||
|
<el-icon><component :is="item.icon" /></el-icon>
|
||||||
|
<span v-if="!collapsed">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- 用户信息和返回按钮 -->
|
||||||
|
<div class="user-section">
|
||||||
|
<div class="user-avatar">
|
||||||
|
<el-avatar :size="36" src="/avatar.svg" @error="handleAvatarError" />
|
||||||
|
</div>
|
||||||
|
<span v-if="!collapsed" class="user-name">{{ userName }}</span>
|
||||||
|
<el-tooltip content="返回主系统" placement="top">
|
||||||
|
<div class="back-icon" @click="backToMain">
|
||||||
|
<el-icon><Back /></el-icon>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- 主内容区 -->
|
||||||
|
<main class="main-content">
|
||||||
|
<!-- iframe 模式 -->
|
||||||
|
<div v-if="currentIframeUrl" class="iframe-container">
|
||||||
|
<div class="iframe-header">
|
||||||
|
<span class="iframe-title">{{ currentMenuItem?.label }}</span>
|
||||||
|
<el-button
|
||||||
|
text
|
||||||
|
@click="handleRefreshIframe"
|
||||||
|
:icon="Refresh"
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<iframe
|
||||||
|
ref="iframeRef"
|
||||||
|
:src="currentIframeUrl"
|
||||||
|
class="content-iframe"
|
||||||
|
frameborder="0"
|
||||||
|
@load="handleIframeLoad"
|
||||||
|
/>
|
||||||
|
<div v-if="iframeLoading" class="iframe-loading">
|
||||||
|
<el-icon class="is-loading"><Loading /></el-icon>
|
||||||
|
<span>加载中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 路由模式 -->
|
||||||
|
<router-view v-else />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import {
|
||||||
|
ChatDotRound,
|
||||||
|
Grid,
|
||||||
|
Connection,
|
||||||
|
Document,
|
||||||
|
Service,
|
||||||
|
DArrowLeft,
|
||||||
|
DArrowRight,
|
||||||
|
User,
|
||||||
|
Setting,
|
||||||
|
SwitchButton,
|
||||||
|
Refresh,
|
||||||
|
Loading,
|
||||||
|
Back
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
key: string
|
||||||
|
label: string
|
||||||
|
icon: string
|
||||||
|
url?: string
|
||||||
|
type: 'route' | 'iframe'
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const collapsed = ref(false)
|
||||||
|
const activeMenu = ref('home')
|
||||||
|
const iframeLoading = ref(false)
|
||||||
|
const iframeRef = ref<HTMLIFrameElement>()
|
||||||
|
|
||||||
|
// 从 LocalStorage 获取用户名
|
||||||
|
function getUserName(): string {
|
||||||
|
try {
|
||||||
|
const loginDomainStr = localStorage.getItem('loginDomain')
|
||||||
|
if (loginDomainStr) {
|
||||||
|
const loginDomain = JSON.parse(loginDomainStr)
|
||||||
|
return loginDomain.user?.username || loginDomain.userInfo?.username || '管理员'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
return '管理员'
|
||||||
|
}
|
||||||
|
|
||||||
|
const userName = ref(getUserName())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 LocalStorage 加载菜单
|
||||||
|
*/
|
||||||
|
function loadMenuFromStorage(): MenuItem[] {
|
||||||
|
try {
|
||||||
|
const loginDomainStr = localStorage.getItem('loginDomain')
|
||||||
|
if (!loginDomainStr) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginDomain = JSON.parse(loginDomainStr)
|
||||||
|
const userViews = loginDomain.userViews || []
|
||||||
|
|
||||||
|
|
||||||
|
// 过滤出 SidebarLayout 的顶级菜单(没有 parentId,且属于 platform 服务,且不是admin路由)
|
||||||
|
const sidebarViews = userViews.filter((view: any) =>
|
||||||
|
view.layout === 'SidebarLayout' &&
|
||||||
|
!view.parentId &&
|
||||||
|
view.type === 1 && // type 1 是侧边栏菜单
|
||||||
|
view.service === 'platform' && // 只显示 platform 服务的视图
|
||||||
|
view.url?.startsWith('/admin') // 只留admin 路由(由 AdminSidebar 管理)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 按 orderNum 排序
|
||||||
|
sidebarViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
|
||||||
|
|
||||||
|
// 转换为 MenuItem 格式
|
||||||
|
const menuItems: MenuItem[] = sidebarViews.map((view: any) => {
|
||||||
|
// 根据 viewType 或 iframeUrl 判断是 route 还是 iframe
|
||||||
|
const isIframe = view.viewType === 'iframe' || !!view.iframeUrl
|
||||||
|
|
||||||
|
// 确定菜单的路由路径
|
||||||
|
let menuUrl = view.url
|
||||||
|
if (isIframe && view.url && (view.url.startsWith('http://') || view.url.startsWith('https://'))) {
|
||||||
|
// iframe 类型且 url 是外部链接,使用 viewId 作为路由路径
|
||||||
|
menuUrl = `/${view.viewId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: view.viewId || view.name,
|
||||||
|
label: view.name,
|
||||||
|
icon: view.icon || 'Grid',
|
||||||
|
url: menuUrl,
|
||||||
|
type: isIframe ? 'iframe' : 'route'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return menuItems
|
||||||
|
} catch (error) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单配置(从 LocalStorage 加载)
|
||||||
|
const menuItems = ref<MenuItem[]>(loadMenuFromStorage())
|
||||||
|
|
||||||
|
// 当前菜单项
|
||||||
|
const currentMenuItem = computed(() => {
|
||||||
|
return menuItems.value.find(item => item.key === activeMenu.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前 iframe URL(从路由 meta 读取)
|
||||||
|
const currentIframeUrl = computed(() => {
|
||||||
|
const meta = route.meta as any
|
||||||
|
return meta?.iframeUrl || null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换侧边栏
|
||||||
|
const toggleSidebar = () => {
|
||||||
|
collapsed.value = !collapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理菜单点击
|
||||||
|
const handleMenuClick = (item: MenuItem) => {
|
||||||
|
activeMenu.value = item.key
|
||||||
|
|
||||||
|
// 所有菜单都通过路由跳转
|
||||||
|
if (item.url) {
|
||||||
|
router.push(item.url)
|
||||||
|
if (item.type === 'iframe') {
|
||||||
|
iframeLoading.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iframe 加载完成
|
||||||
|
const handleIframeLoad = () => {
|
||||||
|
iframeLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新 iframe
|
||||||
|
const handleRefreshIframe = () => {
|
||||||
|
if (iframeRef.value) {
|
||||||
|
iframeLoading.value = true
|
||||||
|
iframeRef.value.src = iframeRef.value.src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户头像加载错误
|
||||||
|
const handleAvatarError = () => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回主系统(SidebarLayout)
|
||||||
|
const backToMain = () => {
|
||||||
|
// 查找第一个非admin路由
|
||||||
|
const loginDomainStr = localStorage.getItem('loginDomain')
|
||||||
|
if (loginDomainStr) {
|
||||||
|
const loginDomain = JSON.parse(loginDomainStr)
|
||||||
|
const userViews = loginDomain.userViews || []
|
||||||
|
const mainViews = userViews.filter((view: any) =>
|
||||||
|
view.service === 'platform' && !view.url?.startsWith('/admin')
|
||||||
|
)
|
||||||
|
if (mainViews.length > 0) {
|
||||||
|
// 按 orderNum 排序,跳转到第一个
|
||||||
|
mainViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
|
||||||
|
router.push(mainViews[0].url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户操作
|
||||||
|
const handleUserCommand = (command: string) => {
|
||||||
|
switch (command) {
|
||||||
|
case 'profile':
|
||||||
|
router.push('/profile')
|
||||||
|
break
|
||||||
|
case 'settings':
|
||||||
|
router.push('/settings')
|
||||||
|
break
|
||||||
|
case 'logout':
|
||||||
|
localStorage.clear()
|
||||||
|
ElMessage.success('退出成功')
|
||||||
|
router.push('/login')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化,同步激活菜单
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
(newPath) => {
|
||||||
|
// 查找匹配的菜单项(route 或 iframe 类型)
|
||||||
|
const menuItem = menuItems.value.find((item: MenuItem) => item.url === newPath)
|
||||||
|
if (menuItem) {
|
||||||
|
activeMenu.value = menuItem.key
|
||||||
|
} else {
|
||||||
|
// 如果路径不匹配,尝试通过 route.name 匹配 viewId
|
||||||
|
const menuByName = menuItems.value.find((item: MenuItem) => item.key === route.name)
|
||||||
|
if (menuByName) {
|
||||||
|
activeMenu.value = menuByName.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import url("./SidebarLayout.scss");
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
# SidebarLayout 侧边栏布局组件
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
### 🎯 核心功能
|
||||||
|
- **侧边栏菜单导航**:左侧固定侧边栏,支持折叠/展开
|
||||||
|
- **双模式内容区**:
|
||||||
|
- **路由模式**:普通路由页面渲染
|
||||||
|
- **iframe 模式**:嵌入外部应用(招标助手、泰豪小电、智能体编排)
|
||||||
|
- **用户信息展示**:底部用户头像和下拉菜单
|
||||||
|
- **响应式设计**:支持移动端适配
|
||||||
|
|
||||||
|
### 📱 菜单配置
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const menuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
key: 'home',
|
||||||
|
label: '工作台',
|
||||||
|
icon: 'Grid',
|
||||||
|
path: '/home',
|
||||||
|
type: 'route'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bidding',
|
||||||
|
label: '招标助手',
|
||||||
|
icon: 'Document',
|
||||||
|
iframeUrl: 'http://localhost:5002',
|
||||||
|
type: 'iframe'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'service',
|
||||||
|
label: '泰豪小电',
|
||||||
|
icon: 'Service',
|
||||||
|
iframeUrl: 'http://localhost:5003',
|
||||||
|
type: 'iframe'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'workflow',
|
||||||
|
label: '智能体编排',
|
||||||
|
icon: 'Connection',
|
||||||
|
iframeUrl: 'http://localhost:3000', // Dify 地址
|
||||||
|
type: 'iframe'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 在路由中使用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// router/index.ts
|
||||||
|
import { SidebarLayout } from '@/layouts'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: SidebarLayout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
component: () => import('@/views/Home.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/chat',
|
||||||
|
component: () => import('@/views/Chat.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在 App.vue 中使用
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<SidebarLayout />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SidebarLayout } from '@/layouts'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 菜单项类型
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MenuItem {
|
||||||
|
key: string // 唯一标识
|
||||||
|
label: string // 显示名称
|
||||||
|
icon: string // Element Plus 图标组件名
|
||||||
|
path?: string // 路由路径(route 类型必需)
|
||||||
|
iframeUrl?: string // iframe URL(iframe 类型必需)
|
||||||
|
type: 'route' | 'iframe' // 菜单类型
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## iframe 应用说明
|
||||||
|
|
||||||
|
### 1. 招标助手 (Bidding)
|
||||||
|
- **端口**:5002
|
||||||
|
- **URL**:http://localhost:5002
|
||||||
|
- **说明**:招投标业务管理系统
|
||||||
|
|
||||||
|
### 2. 泰豪小电 (Service)
|
||||||
|
- **端口**:5003
|
||||||
|
- **URL**:http://localhost:5003
|
||||||
|
- **说明**:智能客服工单管理系统
|
||||||
|
|
||||||
|
### 3. 智能体编排 (Workflow)
|
||||||
|
- **端口**:3000
|
||||||
|
- **URL**:http://localhost:3000
|
||||||
|
- **说明**:Dify 智能体编排界面
|
||||||
|
|
||||||
|
## 样式自定义
|
||||||
|
|
||||||
|
### 主题色调整
|
||||||
|
|
||||||
|
```scss
|
||||||
|
// 修改侧边栏背景色
|
||||||
|
.sidebar {
|
||||||
|
background: #F0EAF4; // 当前淡紫色背景
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改激活项颜色
|
||||||
|
.nav-item.active {
|
||||||
|
background: rgba(124, 58, 237, 0.15);
|
||||||
|
color: #7c3aed;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 侧边栏宽度
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.sidebar {
|
||||||
|
width: 220px; // 展开宽度
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 64px; // 折叠宽度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能说明
|
||||||
|
|
||||||
|
### 侧边栏折叠
|
||||||
|
- 点击头部箭头图标可切换折叠/展开状态
|
||||||
|
- 折叠后只显示图标,展开后显示图标+文字
|
||||||
|
|
||||||
|
### iframe 加载
|
||||||
|
- 自动显示加载中状态
|
||||||
|
- 支持刷新按钮重新加载
|
||||||
|
- 显示当前应用标题
|
||||||
|
|
||||||
|
### 用户操作
|
||||||
|
- **个人中心**:跳转到 /profile
|
||||||
|
- **系统设置**:跳转到 /settings
|
||||||
|
- **退出登录**:跳转到 /login
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **跨域问题**:确保 iframe 应用允许被嵌入
|
||||||
|
```nginx
|
||||||
|
# nginx 配置
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
# 或者
|
||||||
|
add_header Content-Security-Policy "frame-ancestors 'self' http://localhost:5001";
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **端口配置**:确保对应服务已启动
|
||||||
|
- platform: 5001
|
||||||
|
- bidding: 5002
|
||||||
|
- workcase: 5003
|
||||||
|
- dify: 3000
|
||||||
|
|
||||||
|
3. **路由同步**:iframe 模式不会改变浏览器 URL
|
||||||
|
|
||||||
|
4. **通信机制**:如需与 iframe 通信,使用 postMessage API
|
||||||
|
|
||||||
|
## 扩展建议
|
||||||
|
|
||||||
|
### 添加新菜单项
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在 menuItems 数组中添加
|
||||||
|
{
|
||||||
|
key: 'new-app',
|
||||||
|
label: '新应用',
|
||||||
|
icon: 'Plus',
|
||||||
|
iframeUrl: 'http://localhost:5004',
|
||||||
|
type: 'iframe'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动态菜单加载
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 从 API 获取菜单配置
|
||||||
|
const loadMenus = async () => {
|
||||||
|
const response = await fetch('/api/menus')
|
||||||
|
menuItems.value = await response.json()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 权限控制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const menuItems = computed(() => {
|
||||||
|
return allMenuItems.filter(item =>
|
||||||
|
hasPermission(item.key)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -44,9 +44,9 @@
|
|||||||
<el-icon><User /></el-icon>
|
<el-icon><User /></el-icon>
|
||||||
个人中心
|
个人中心
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item command="settings" divided>
|
<el-dropdown-item v-if="hasAdmin" command="settings" divided>
|
||||||
<el-icon><Setting /></el-icon>
|
<el-icon><Setting /></el-icon>
|
||||||
系统设置
|
管理后台
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item command="logout" divided>
|
<el-dropdown-item command="logout" divided>
|
||||||
<el-icon><SwitchButton /></el-icon>
|
<el-icon><SwitchButton /></el-icon>
|
||||||
@@ -125,6 +125,7 @@ const collapsed = ref(false)
|
|||||||
const activeMenu = ref('home')
|
const activeMenu = ref('home')
|
||||||
const iframeLoading = ref(false)
|
const iframeLoading = ref(false)
|
||||||
const iframeRef = ref<HTMLIFrameElement>()
|
const iframeRef = ref<HTMLIFrameElement>()
|
||||||
|
const hasAdmin = ref(false)
|
||||||
|
|
||||||
// 从 LocalStorage 获取用户名
|
// 从 LocalStorage 获取用户名
|
||||||
function getUserName(): string {
|
function getUserName(): string {
|
||||||
@@ -163,6 +164,13 @@ function loadMenuFromStorage(): MenuItem[] {
|
|||||||
view.service === 'platform' && // 只显示 platform 服务的视图
|
view.service === 'platform' && // 只显示 platform 服务的视图
|
||||||
!view.url?.startsWith('/admin') // 排除 admin 路由(由 AdminSidebar 管理)
|
!view.url?.startsWith('/admin') // 排除 admin 路由(由 AdminSidebar 管理)
|
||||||
)
|
)
|
||||||
|
hasAdmin.value = userViews.filter((view: any) =>
|
||||||
|
view.layout === 'SidebarLayout' &&
|
||||||
|
!view.parentId &&
|
||||||
|
view.type === 1 && // type 1 是侧边栏菜单
|
||||||
|
view.service === 'platform' && // 只显示 platform 服务的视图
|
||||||
|
view.url?.startsWith('/admin') // 排除 admin 路由(由 AdminSidebar 管理)
|
||||||
|
).length>0
|
||||||
|
|
||||||
// 按 orderNum 排序
|
// 按 orderNum 排序
|
||||||
sidebarViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
|
sidebarViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
|
||||||
@@ -251,7 +259,21 @@ const handleUserCommand = (command: string) => {
|
|||||||
router.push('/profile')
|
router.push('/profile')
|
||||||
break
|
break
|
||||||
case 'settings':
|
case 'settings':
|
||||||
router.push('/settings')
|
// 跳转到管理后台(AdminSidebarLayout)
|
||||||
|
// 查找第一个 admin 路由
|
||||||
|
const loginDomainStr = localStorage.getItem('loginDomain')
|
||||||
|
if (loginDomainStr) {
|
||||||
|
const loginDomain = JSON.parse(loginDomainStr)
|
||||||
|
const userViews = loginDomain.userViews || []
|
||||||
|
const adminViews = userViews.filter((view: any) =>
|
||||||
|
view.service === 'platform' && view.url?.startsWith('/admin')
|
||||||
|
)
|
||||||
|
if (adminViews.length > 0) {
|
||||||
|
// 按 orderNum 排序,跳转到第一个
|
||||||
|
adminViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
|
||||||
|
router.push(adminViews[0].url)
|
||||||
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case 'logout':
|
case 'logout':
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
|
|
||||||
<diagram name="第 1 页" id="5ECD0EICUl6v9GFSHp1a">
|
|
||||||
<mxGraphModel dx="2767" dy="980" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-1" value="应用层\Web" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-500" y="200" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-14" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-9" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-15" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-8" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-16" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-7" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-18" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-6" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-2" value="<span style="font-family: 仿宋_GB2312; letter-spacing: 0pt; font-size: 12pt; background: rgb(252, 252, 252);"><font face="Times New Roman Regular">AI </font><font face="仿宋_GB2312">数智化平台</font></span>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-10" y="200" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-21" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-7" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-22" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-8" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-23" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-9" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-26" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-19" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-28" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-27" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-3" value="<span style="font-family: 仿宋_GB2312; letter-spacing: 0pt; font-size: 12pt; background: rgb(252, 252, 252);"><font face="仿宋_GB2312">招投标智能体</font></span>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="660" y="200" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-30" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-29" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-33" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-10" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-34" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-9" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-36" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-7" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-37" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-6" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-4" value="<span style="font-family: 仿宋_GB2312; letter-spacing: 0pt; font-size: 12pt; background: rgb(252, 252, 252);"><font face="仿宋_GB2312">智能客服智能体</font></span>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1250" y="200" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-5" value="服务层\Serv" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-500" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-6" value="gateway" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-250" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-7" value="filecenter" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-40" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-8" value="log" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="150" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-9" value="system" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="353" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-10" value="auth" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="530" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-11" value="admin" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1050" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-12" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.064;entryY=-0.01;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-11" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-13" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.162;entryY=-0.049;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-10" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-19" value="agent" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1240" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-20" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.682;entryY=0.01;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-6" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-24" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.368;entryY=-0.049;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-10" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-27" value="bidding" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1420" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-29" value="case" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1610" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-40" value="message" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="690" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-41" value="crontab" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="884" y="470" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-32" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.912;entryY=-0.029;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-19" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-35" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.623;entryY=0.01;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-8" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-42" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-40" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-43" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-2" target="A6Q9NJanCqLCFo-gPTNW-41" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-44" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-40" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-45" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-3" target="A6Q9NJanCqLCFo-gPTNW-41" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="A6Q9NJanCqLCFo-gPTNW-46" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-40" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="oQNHS9vF_fVvW0YvuqGe-1" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.394;entryY=-0.049;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="A6Q9NJanCqLCFo-gPTNW-4" target="A6Q9NJanCqLCFo-gPTNW-41">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
||||||
Reference in New Issue
Block a user